##// END OF EJS Templates
some more typechekcing
M Bussonnier -
Show More
@@ -1,133 +1,133
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Tests for testing.tools
3 Tests for testing.tools
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import os
17 import os
18 import unittest
18 import unittest
19
19
20 from IPython.testing import decorators as dec
20 from IPython.testing import decorators as dec
21 from IPython.testing import tools as tt
21 from IPython.testing import tools as tt
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Tests
24 # Tests
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 @dec.skip_win32
27 @dec.skip_win32
28 def test_full_path_posix():
28 def test_full_path_posix():
29 spath = "/foo/bar.py"
29 spath = "/foo/bar.py"
30 result = tt.full_path(spath, ["a.txt", "b.txt"])
30 result = tt.full_path(spath, ["a.txt", "b.txt"])
31 assert result, ["/foo/a.txt" == "/foo/b.txt"]
31 assert result, ["/foo/a.txt" == "/foo/b.txt"]
32 spath = "/foo"
32 spath = "/foo"
33 result = tt.full_path(spath, ["a.txt", "b.txt"])
33 result = tt.full_path(spath, ["a.txt", "b.txt"])
34 assert result, ["/a.txt" == "/b.txt"]
34 assert result, ["/a.txt" == "/b.txt"]
35 result = tt.full_path(spath, "a.txt")
35 result = tt.full_path(spath, ["a.txt"])
36 assert result == ["/a.txt"]
36 assert result == ["/a.txt"]
37
37
38
38
39 @dec.skip_if_not_win32
39 @dec.skip_if_not_win32
40 def test_full_path_win32():
40 def test_full_path_win32():
41 spath = "c:\\foo\\bar.py"
41 spath = "c:\\foo\\bar.py"
42 result = tt.full_path(spath, ["a.txt", "b.txt"])
42 result = tt.full_path(spath, ["a.txt", "b.txt"])
43 assert result, ["c:\\foo\\a.txt" == "c:\\foo\\b.txt"]
43 assert result, ["c:\\foo\\a.txt" == "c:\\foo\\b.txt"]
44 spath = "c:\\foo"
44 spath = "c:\\foo"
45 result = tt.full_path(spath, ["a.txt", "b.txt"])
45 result = tt.full_path(spath, ["a.txt", "b.txt"])
46 assert result, ["c:\\a.txt" == "c:\\b.txt"]
46 assert result, ["c:\\a.txt" == "c:\\b.txt"]
47 result = tt.full_path(spath, "a.txt")
47 result = tt.full_path(spath, ["a.txt"])
48 assert result == ["c:\\a.txt"]
48 assert result == ["c:\\a.txt"]
49
49
50
50
51 def test_parser():
51 def test_parser():
52 err = ("FAILED (errors=1)", 1, 0)
52 err = ("FAILED (errors=1)", 1, 0)
53 fail = ("FAILED (failures=1)", 0, 1)
53 fail = ("FAILED (failures=1)", 0, 1)
54 both = ("FAILED (errors=1, failures=1)", 1, 1)
54 both = ("FAILED (errors=1, failures=1)", 1, 1)
55 for txt, nerr, nfail in [err, fail, both]:
55 for txt, nerr, nfail in [err, fail, both]:
56 nerr1, nfail1 = tt.parse_test_output(txt)
56 nerr1, nfail1 = tt.parse_test_output(txt)
57 assert nerr == nerr1
57 assert nerr == nerr1
58 assert nfail == nfail1
58 assert nfail == nfail1
59
59
60
60
61 def test_temp_pyfile():
61 def test_temp_pyfile():
62 src = 'pass\n'
62 src = 'pass\n'
63 fname = tt.temp_pyfile(src)
63 fname = tt.temp_pyfile(src)
64 assert os.path.isfile(fname)
64 assert os.path.isfile(fname)
65 with open(fname, encoding="utf-8") as fh2:
65 with open(fname, encoding="utf-8") as fh2:
66 src2 = fh2.read()
66 src2 = fh2.read()
67 assert src2 == src
67 assert src2 == src
68
68
69 class TestAssertPrints(unittest.TestCase):
69 class TestAssertPrints(unittest.TestCase):
70 def test_passing(self):
70 def test_passing(self):
71 with tt.AssertPrints("abc"):
71 with tt.AssertPrints("abc"):
72 print("abcd")
72 print("abcd")
73 print("def")
73 print("def")
74 print(b"ghi")
74 print(b"ghi")
75
75
76 def test_failing(self):
76 def test_failing(self):
77 def func():
77 def func():
78 with tt.AssertPrints("abc"):
78 with tt.AssertPrints("abc"):
79 print("acd")
79 print("acd")
80 print("def")
80 print("def")
81 print(b"ghi")
81 print(b"ghi")
82
82
83 self.assertRaises(AssertionError, func)
83 self.assertRaises(AssertionError, func)
84
84
85
85
86 class Test_ipexec_validate(tt.TempFileMixin):
86 class Test_ipexec_validate(tt.TempFileMixin):
87 def test_main_path(self):
87 def test_main_path(self):
88 """Test with only stdout results.
88 """Test with only stdout results.
89 """
89 """
90 self.mktmp("print('A')\n"
90 self.mktmp("print('A')\n"
91 "print('B')\n"
91 "print('B')\n"
92 )
92 )
93 out = "A\nB"
93 out = "A\nB"
94 tt.ipexec_validate(self.fname, out)
94 tt.ipexec_validate(self.fname, out)
95
95
96 def test_main_path2(self):
96 def test_main_path2(self):
97 """Test with only stdout results, expecting windows line endings.
97 """Test with only stdout results, expecting windows line endings.
98 """
98 """
99 self.mktmp("print('A')\n"
99 self.mktmp("print('A')\n"
100 "print('B')\n"
100 "print('B')\n"
101 )
101 )
102 out = "A\r\nB"
102 out = "A\r\nB"
103 tt.ipexec_validate(self.fname, out)
103 tt.ipexec_validate(self.fname, out)
104
104
105 def test_exception_path(self):
105 def test_exception_path(self):
106 """Test exception path in exception_validate.
106 """Test exception path in exception_validate.
107 """
107 """
108 self.mktmp("import sys\n"
108 self.mktmp("import sys\n"
109 "print('A')\n"
109 "print('A')\n"
110 "print('B')\n"
110 "print('B')\n"
111 "print('C', file=sys.stderr)\n"
111 "print('C', file=sys.stderr)\n"
112 "print('D', file=sys.stderr)\n"
112 "print('D', file=sys.stderr)\n"
113 )
113 )
114 out = "A\nB"
114 out = "A\nB"
115 tt.ipexec_validate(self.fname, expected_out=out, expected_err="C\nD")
115 tt.ipexec_validate(self.fname, expected_out=out, expected_err="C\nD")
116
116
117 def test_exception_path2(self):
117 def test_exception_path2(self):
118 """Test exception path in exception_validate, expecting windows line endings.
118 """Test exception path in exception_validate, expecting windows line endings.
119 """
119 """
120 self.mktmp("import sys\n"
120 self.mktmp("import sys\n"
121 "print('A')\n"
121 "print('A')\n"
122 "print('B')\n"
122 "print('B')\n"
123 "print('C', file=sys.stderr)\n"
123 "print('C', file=sys.stderr)\n"
124 "print('D', file=sys.stderr)\n"
124 "print('D', file=sys.stderr)\n"
125 )
125 )
126 out = "A\r\nB"
126 out = "A\r\nB"
127 tt.ipexec_validate(self.fname, expected_out=out, expected_err="C\r\nD")
127 tt.ipexec_validate(self.fname, expected_out=out, expected_err="C\r\nD")
128
128
129
129
130 def tearDown(self):
130 def tearDown(self):
131 # tear down correctly the mixin,
131 # tear down correctly the mixin,
132 # unittest.TestCase.tearDown does nothing
132 # unittest.TestCase.tearDown does nothing
133 tt.TempFileMixin.tearDown(self)
133 tt.TempFileMixin.tearDown(self)
@@ -1,476 +1,471
1 """Generic testing tools.
1 """Generic testing tools.
2
2
3 Authors
3 Authors
4 -------
4 -------
5 - Fernando Perez <Fernando.Perez@berkeley.edu>
5 - Fernando Perez <Fernando.Perez@berkeley.edu>
6 """
6 """
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 os
12 import os
13 from pathlib import Path
13 from pathlib import Path
14 import re
14 import re
15 import sys
15 import sys
16 import tempfile
16 import tempfile
17 import unittest
17 import unittest
18
18
19 from contextlib import contextmanager
19 from contextlib import contextmanager
20 from io import StringIO
20 from io import StringIO
21 from subprocess import Popen, PIPE
21 from subprocess import Popen, PIPE
22 from unittest.mock import patch
22 from unittest.mock import patch
23
23
24 from traitlets.config.loader import Config
24 from traitlets.config.loader import Config
25 from IPython.utils.process import get_output_error_code
25 from IPython.utils.process import get_output_error_code
26 from IPython.utils.text import list_strings
26 from IPython.utils.text import list_strings
27 from IPython.utils.io import temp_pyfile, Tee
27 from IPython.utils.io import temp_pyfile, Tee
28 from IPython.utils import py3compat
28 from IPython.utils import py3compat
29
29
30 from . import decorators as dec
30 from . import decorators as dec
31 from . import skipdoctest
31 from . import skipdoctest
32
32
33
33
34 # The docstring for full_path doctests differently on win32 (different path
34 # The docstring for full_path doctests differently on win32 (different path
35 # separator) so just skip the doctest there. The example remains informative.
35 # separator) so just skip the doctest there. The example remains informative.
36 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
36 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
37
37
38 @doctest_deco
38 @doctest_deco
39 def full_path(startPath,files):
39 def full_path(startPath: str, files: list[str]) -> list[str]:
40 """Make full paths for all the listed files, based on startPath.
40 """Make full paths for all the listed files, based on startPath.
41
41
42 Only the base part of startPath is kept, since this routine is typically
42 Only the base part of startPath is kept, since this routine is typically
43 used with a script's ``__file__`` variable as startPath. The base of startPath
43 used with a script's ``__file__`` variable as startPath. The base of startPath
44 is then prepended to all the listed files, forming the output list.
44 is then prepended to all the listed files, forming the output list.
45
45
46 Parameters
46 Parameters
47 ----------
47 ----------
48 startPath : string
48 startPath : string
49 Initial path to use as the base for the results. This path is split
49 Initial path to use as the base for the results. This path is split
50 using os.path.split() and only its first component is kept.
50 using os.path.split() and only its first component is kept.
51
51
52 files : string or list
52 files : list
53 One or more files.
53 One or more files.
54
54
55 Examples
55 Examples
56 --------
56 --------
57
57
58 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
58 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
59 ['/foo/a.txt', '/foo/b.txt']
59 ['/foo/a.txt', '/foo/b.txt']
60
60
61 >>> full_path('/foo',['a.txt','b.txt'])
61 >>> full_path('/foo',['a.txt','b.txt'])
62 ['/a.txt', '/b.txt']
62 ['/a.txt', '/b.txt']
63
63
64 If a single file is given, the output is still a list::
65
66 >>> full_path('/foo','a.txt')
67 ['/a.txt']
68 """
64 """
69
65 assert isinstance(files, list)
70 files = list_strings(files)
71 base = os.path.split(startPath)[0]
66 base = os.path.split(startPath)[0]
72 return [ os.path.join(base,f) for f in files ]
67 return [ os.path.join(base,f) for f in files ]
73
68
74
69
75 def parse_test_output(txt):
70 def parse_test_output(txt):
76 """Parse the output of a test run and return errors, failures.
71 """Parse the output of a test run and return errors, failures.
77
72
78 Parameters
73 Parameters
79 ----------
74 ----------
80 txt : str
75 txt : str
81 Text output of a test run, assumed to contain a line of one of the
76 Text output of a test run, assumed to contain a line of one of the
82 following forms::
77 following forms::
83
78
84 'FAILED (errors=1)'
79 'FAILED (errors=1)'
85 'FAILED (failures=1)'
80 'FAILED (failures=1)'
86 'FAILED (errors=1, failures=1)'
81 'FAILED (errors=1, failures=1)'
87
82
88 Returns
83 Returns
89 -------
84 -------
90 nerr, nfail
85 nerr, nfail
91 number of errors and failures.
86 number of errors and failures.
92 """
87 """
93
88
94 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
89 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
95 if err_m:
90 if err_m:
96 nerr = int(err_m.group(1))
91 nerr = int(err_m.group(1))
97 nfail = 0
92 nfail = 0
98 return nerr, nfail
93 return nerr, nfail
99
94
100 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
95 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
101 if fail_m:
96 if fail_m:
102 nerr = 0
97 nerr = 0
103 nfail = int(fail_m.group(1))
98 nfail = int(fail_m.group(1))
104 return nerr, nfail
99 return nerr, nfail
105
100
106 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
101 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
107 re.MULTILINE)
102 re.MULTILINE)
108 if both_m:
103 if both_m:
109 nerr = int(both_m.group(1))
104 nerr = int(both_m.group(1))
110 nfail = int(both_m.group(2))
105 nfail = int(both_m.group(2))
111 return nerr, nfail
106 return nerr, nfail
112
107
113 # If the input didn't match any of these forms, assume no error/failures
108 # If the input didn't match any of these forms, assume no error/failures
114 return 0, 0
109 return 0, 0
115
110
116
111
117 # So nose doesn't think this is a test
112 # So nose doesn't think this is a test
118 parse_test_output.__test__ = False
113 parse_test_output.__test__ = False
119
114
120
115
121 def default_argv():
116 def default_argv():
122 """Return a valid default argv for creating testing instances of ipython"""
117 """Return a valid default argv for creating testing instances of ipython"""
123
118
124 return ['--quick', # so no config file is loaded
119 return ['--quick', # so no config file is loaded
125 # Other defaults to minimize side effects on stdout
120 # Other defaults to minimize side effects on stdout
126 '--colors=NoColor', '--no-term-title','--no-banner',
121 '--colors=NoColor', '--no-term-title','--no-banner',
127 '--autocall=0']
122 '--autocall=0']
128
123
129
124
130 def default_config():
125 def default_config():
131 """Return a config object with good defaults for testing."""
126 """Return a config object with good defaults for testing."""
132 config = Config()
127 config = Config()
133 config.TerminalInteractiveShell.colors = 'NoColor'
128 config.TerminalInteractiveShell.colors = 'NoColor'
134 config.TerminalTerminalInteractiveShell.term_title = False,
129 config.TerminalTerminalInteractiveShell.term_title = False,
135 config.TerminalInteractiveShell.autocall = 0
130 config.TerminalInteractiveShell.autocall = 0
136 f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False)
131 f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False)
137 config.HistoryManager.hist_file = Path(f.name)
132 config.HistoryManager.hist_file = Path(f.name)
138 f.close()
133 f.close()
139 config.HistoryManager.db_cache_size = 10000
134 config.HistoryManager.db_cache_size = 10000
140 return config
135 return config
141
136
142
137
143 def get_ipython_cmd(as_string=False):
138 def get_ipython_cmd(as_string=False):
144 """
139 """
145 Return appropriate IPython command line name. By default, this will return
140 Return appropriate IPython command line name. By default, this will return
146 a list that can be used with subprocess.Popen, for example, but passing
141 a list that can be used with subprocess.Popen, for example, but passing
147 `as_string=True` allows for returning the IPython command as a string.
142 `as_string=True` allows for returning the IPython command as a string.
148
143
149 Parameters
144 Parameters
150 ----------
145 ----------
151 as_string: bool
146 as_string: bool
152 Flag to allow to return the command as a string.
147 Flag to allow to return the command as a string.
153 """
148 """
154 ipython_cmd = [sys.executable, "-m", "IPython"]
149 ipython_cmd = [sys.executable, "-m", "IPython"]
155
150
156 if as_string:
151 if as_string:
157 ipython_cmd = " ".join(ipython_cmd)
152 ipython_cmd = " ".join(ipython_cmd)
158
153
159 return ipython_cmd
154 return ipython_cmd
160
155
161 def ipexec(fname, options=None, commands=()):
156 def ipexec(fname, options=None, commands=()):
162 """Utility to call 'ipython filename'.
157 """Utility to call 'ipython filename'.
163
158
164 Starts IPython with a minimal and safe configuration to make startup as fast
159 Starts IPython with a minimal and safe configuration to make startup as fast
165 as possible.
160 as possible.
166
161
167 Note that this starts IPython in a subprocess!
162 Note that this starts IPython in a subprocess!
168
163
169 Parameters
164 Parameters
170 ----------
165 ----------
171 fname : str, Path
166 fname : str, Path
172 Name of file to be executed (should have .py or .ipy extension).
167 Name of file to be executed (should have .py or .ipy extension).
173
168
174 options : optional, list
169 options : optional, list
175 Extra command-line flags to be passed to IPython.
170 Extra command-line flags to be passed to IPython.
176
171
177 commands : optional, list
172 commands : optional, list
178 Commands to send in on stdin
173 Commands to send in on stdin
179
174
180 Returns
175 Returns
181 -------
176 -------
182 ``(stdout, stderr)`` of ipython subprocess.
177 ``(stdout, stderr)`` of ipython subprocess.
183 """
178 """
184 __tracebackhide__ = True
179 __tracebackhide__ = True
185
180
186 if options is None:
181 if options is None:
187 options = []
182 options = []
188
183
189 cmdargs = default_argv() + options
184 cmdargs = default_argv() + options
190
185
191 test_dir = os.path.dirname(__file__)
186 test_dir = os.path.dirname(__file__)
192
187
193 ipython_cmd = get_ipython_cmd()
188 ipython_cmd = get_ipython_cmd()
194 # Absolute path for filename
189 # Absolute path for filename
195 full_fname = os.path.join(test_dir, fname)
190 full_fname = os.path.join(test_dir, fname)
196 full_cmd = ipython_cmd + cmdargs + ['--', full_fname]
191 full_cmd = ipython_cmd + cmdargs + ['--', full_fname]
197 env = os.environ.copy()
192 env = os.environ.copy()
198 # FIXME: ignore all warnings in ipexec while we have shims
193 # FIXME: ignore all warnings in ipexec while we have shims
199 # should we keep suppressing warnings here, even after removing shims?
194 # should we keep suppressing warnings here, even after removing shims?
200 env['PYTHONWARNINGS'] = 'ignore'
195 env['PYTHONWARNINGS'] = 'ignore'
201 # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr
196 # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr
202 # Prevent coloring under PyCharm ("\x1b[0m" at the end of the stdout)
197 # Prevent coloring under PyCharm ("\x1b[0m" at the end of the stdout)
203 env.pop("PYCHARM_HOSTED", None)
198 env.pop("PYCHARM_HOSTED", None)
204 for k, v in env.items():
199 for k, v in env.items():
205 # Debug a bizarre failure we've seen on Windows:
200 # Debug a bizarre failure we've seen on Windows:
206 # TypeError: environment can only contain strings
201 # TypeError: environment can only contain strings
207 if not isinstance(v, str):
202 if not isinstance(v, str):
208 print(k, v)
203 print(k, v)
209 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env)
204 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env)
210 out, err = p.communicate(input=py3compat.encode('\n'.join(commands)) or None)
205 out, err = p.communicate(input=py3compat.encode('\n'.join(commands)) or None)
211 out, err = py3compat.decode(out), py3compat.decode(err)
206 out, err = py3compat.decode(out), py3compat.decode(err)
212 # `import readline` causes 'ESC[?1034h' to be output sometimes,
207 # `import readline` causes 'ESC[?1034h' to be output sometimes,
213 # so strip that out before doing comparisons
208 # so strip that out before doing comparisons
214 if out:
209 if out:
215 out = re.sub(r'\x1b\[[^h]+h', '', out)
210 out = re.sub(r'\x1b\[[^h]+h', '', out)
216 return out, err
211 return out, err
217
212
218
213
219 def ipexec_validate(fname, expected_out, expected_err='',
214 def ipexec_validate(fname, expected_out, expected_err='',
220 options=None, commands=()):
215 options=None, commands=()):
221 """Utility to call 'ipython filename' and validate output/error.
216 """Utility to call 'ipython filename' and validate output/error.
222
217
223 This function raises an AssertionError if the validation fails.
218 This function raises an AssertionError if the validation fails.
224
219
225 Note that this starts IPython in a subprocess!
220 Note that this starts IPython in a subprocess!
226
221
227 Parameters
222 Parameters
228 ----------
223 ----------
229 fname : str, Path
224 fname : str, Path
230 Name of the file to be executed (should have .py or .ipy extension).
225 Name of the file to be executed (should have .py or .ipy extension).
231
226
232 expected_out : str
227 expected_out : str
233 Expected stdout of the process.
228 Expected stdout of the process.
234
229
235 expected_err : optional, str
230 expected_err : optional, str
236 Expected stderr of the process.
231 Expected stderr of the process.
237
232
238 options : optional, list
233 options : optional, list
239 Extra command-line flags to be passed to IPython.
234 Extra command-line flags to be passed to IPython.
240
235
241 Returns
236 Returns
242 -------
237 -------
243 None
238 None
244 """
239 """
245 __tracebackhide__ = True
240 __tracebackhide__ = True
246
241
247 out, err = ipexec(fname, options, commands)
242 out, err = ipexec(fname, options, commands)
248 # print('OUT', out) # dbg
243 # print('OUT', out) # dbg
249 # print('ERR', err) # dbg
244 # print('ERR', err) # dbg
250 # If there are any errors, we must check those before stdout, as they may be
245 # If there are any errors, we must check those before stdout, as they may be
251 # more informative than simply having an empty stdout.
246 # more informative than simply having an empty stdout.
252 if err:
247 if err:
253 if expected_err:
248 if expected_err:
254 assert "\n".join(err.strip().splitlines()) == "\n".join(
249 assert "\n".join(err.strip().splitlines()) == "\n".join(
255 expected_err.strip().splitlines()
250 expected_err.strip().splitlines()
256 )
251 )
257 else:
252 else:
258 raise ValueError('Running file %r produced error: %r' %
253 raise ValueError('Running file %r produced error: %r' %
259 (fname, err))
254 (fname, err))
260 # If no errors or output on stderr was expected, match stdout
255 # If no errors or output on stderr was expected, match stdout
261 assert "\n".join(out.strip().splitlines()) == "\n".join(
256 assert "\n".join(out.strip().splitlines()) == "\n".join(
262 expected_out.strip().splitlines()
257 expected_out.strip().splitlines()
263 )
258 )
264
259
265
260
266 class TempFileMixin(unittest.TestCase):
261 class TempFileMixin(unittest.TestCase):
267 """Utility class to create temporary Python/IPython files.
262 """Utility class to create temporary Python/IPython files.
268
263
269 Meant as a mixin class for test cases."""
264 Meant as a mixin class for test cases."""
270
265
271 def mktmp(self, src, ext='.py'):
266 def mktmp(self, src, ext='.py'):
272 """Make a valid python temp file."""
267 """Make a valid python temp file."""
273 fname = temp_pyfile(src, ext)
268 fname = temp_pyfile(src, ext)
274 if not hasattr(self, 'tmps'):
269 if not hasattr(self, 'tmps'):
275 self.tmps=[]
270 self.tmps=[]
276 self.tmps.append(fname)
271 self.tmps.append(fname)
277 self.fname = fname
272 self.fname = fname
278
273
279 def tearDown(self):
274 def tearDown(self):
280 # If the tmpfile wasn't made because of skipped tests, like in
275 # If the tmpfile wasn't made because of skipped tests, like in
281 # win32, there's nothing to cleanup.
276 # win32, there's nothing to cleanup.
282 if hasattr(self, 'tmps'):
277 if hasattr(self, 'tmps'):
283 for fname in self.tmps:
278 for fname in self.tmps:
284 # If the tmpfile wasn't made because of skipped tests, like in
279 # If the tmpfile wasn't made because of skipped tests, like in
285 # win32, there's nothing to cleanup.
280 # win32, there's nothing to cleanup.
286 try:
281 try:
287 os.unlink(fname)
282 os.unlink(fname)
288 except:
283 except:
289 # On Windows, even though we close the file, we still can't
284 # On Windows, even though we close the file, we still can't
290 # delete it. I have no clue why
285 # delete it. I have no clue why
291 pass
286 pass
292
287
293 def __enter__(self):
288 def __enter__(self):
294 return self
289 return self
295
290
296 def __exit__(self, exc_type, exc_value, traceback):
291 def __exit__(self, exc_type, exc_value, traceback):
297 self.tearDown()
292 self.tearDown()
298
293
299
294
300 pair_fail_msg = ("Testing {0}\n\n"
295 pair_fail_msg = ("Testing {0}\n\n"
301 "In:\n"
296 "In:\n"
302 " {1!r}\n"
297 " {1!r}\n"
303 "Expected:\n"
298 "Expected:\n"
304 " {2!r}\n"
299 " {2!r}\n"
305 "Got:\n"
300 "Got:\n"
306 " {3!r}\n")
301 " {3!r}\n")
307 def check_pairs(func, pairs):
302 def check_pairs(func, pairs):
308 """Utility function for the common case of checking a function with a
303 """Utility function for the common case of checking a function with a
309 sequence of input/output pairs.
304 sequence of input/output pairs.
310
305
311 Parameters
306 Parameters
312 ----------
307 ----------
313 func : callable
308 func : callable
314 The function to be tested. Should accept a single argument.
309 The function to be tested. Should accept a single argument.
315 pairs : iterable
310 pairs : iterable
316 A list of (input, expected_output) tuples.
311 A list of (input, expected_output) tuples.
317
312
318 Returns
313 Returns
319 -------
314 -------
320 None. Raises an AssertionError if any output does not match the expected
315 None. Raises an AssertionError if any output does not match the expected
321 value.
316 value.
322 """
317 """
323 __tracebackhide__ = True
318 __tracebackhide__ = True
324
319
325 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
320 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
326 for inp, expected in pairs:
321 for inp, expected in pairs:
327 out = func(inp)
322 out = func(inp)
328 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
323 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
329
324
330
325
331 MyStringIO = StringIO
326 MyStringIO = StringIO
332
327
333 _re_type = type(re.compile(r''))
328 _re_type = type(re.compile(r''))
334
329
335 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
330 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
336 -------
331 -------
337 {2!s}
332 {2!s}
338 -------
333 -------
339 """
334 """
340
335
341 class AssertPrints(object):
336 class AssertPrints(object):
342 """Context manager for testing that code prints certain text.
337 """Context manager for testing that code prints certain text.
343
338
344 Examples
339 Examples
345 --------
340 --------
346 >>> with AssertPrints("abc", suppress=False):
341 >>> with AssertPrints("abc", suppress=False):
347 ... print("abcd")
342 ... print("abcd")
348 ... print("def")
343 ... print("def")
349 ...
344 ...
350 abcd
345 abcd
351 def
346 def
352 """
347 """
353 def __init__(self, s, channel='stdout', suppress=True):
348 def __init__(self, s, channel='stdout', suppress=True):
354 self.s = s
349 self.s = s
355 if isinstance(self.s, (str, _re_type)):
350 if isinstance(self.s, (str, _re_type)):
356 self.s = [self.s]
351 self.s = [self.s]
357 self.channel = channel
352 self.channel = channel
358 self.suppress = suppress
353 self.suppress = suppress
359
354
360 def __enter__(self):
355 def __enter__(self):
361 self.orig_stream = getattr(sys, self.channel)
356 self.orig_stream = getattr(sys, self.channel)
362 self.buffer = MyStringIO()
357 self.buffer = MyStringIO()
363 self.tee = Tee(self.buffer, channel=self.channel)
358 self.tee = Tee(self.buffer, channel=self.channel)
364 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
359 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
365
360
366 def __exit__(self, etype, value, traceback):
361 def __exit__(self, etype, value, traceback):
367 __tracebackhide__ = True
362 __tracebackhide__ = True
368
363
369 try:
364 try:
370 if value is not None:
365 if value is not None:
371 # If an error was raised, don't check anything else
366 # If an error was raised, don't check anything else
372 return False
367 return False
373 self.tee.flush()
368 self.tee.flush()
374 setattr(sys, self.channel, self.orig_stream)
369 setattr(sys, self.channel, self.orig_stream)
375 printed = self.buffer.getvalue()
370 printed = self.buffer.getvalue()
376 for s in self.s:
371 for s in self.s:
377 if isinstance(s, _re_type):
372 if isinstance(s, _re_type):
378 assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
373 assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
379 else:
374 else:
380 assert s in printed, notprinted_msg.format(s, self.channel, printed)
375 assert s in printed, notprinted_msg.format(s, self.channel, printed)
381 return False
376 return False
382 finally:
377 finally:
383 self.tee.close()
378 self.tee.close()
384
379
385 printed_msg = """Found {0!r} in printed output (on {1}):
380 printed_msg = """Found {0!r} in printed output (on {1}):
386 -------
381 -------
387 {2!s}
382 {2!s}
388 -------
383 -------
389 """
384 """
390
385
391 class AssertNotPrints(AssertPrints):
386 class AssertNotPrints(AssertPrints):
392 """Context manager for checking that certain output *isn't* produced.
387 """Context manager for checking that certain output *isn't* produced.
393
388
394 Counterpart of AssertPrints"""
389 Counterpart of AssertPrints"""
395 def __exit__(self, etype, value, traceback):
390 def __exit__(self, etype, value, traceback):
396 __tracebackhide__ = True
391 __tracebackhide__ = True
397
392
398 try:
393 try:
399 if value is not None:
394 if value is not None:
400 # If an error was raised, don't check anything else
395 # If an error was raised, don't check anything else
401 self.tee.close()
396 self.tee.close()
402 return False
397 return False
403 self.tee.flush()
398 self.tee.flush()
404 setattr(sys, self.channel, self.orig_stream)
399 setattr(sys, self.channel, self.orig_stream)
405 printed = self.buffer.getvalue()
400 printed = self.buffer.getvalue()
406 for s in self.s:
401 for s in self.s:
407 if isinstance(s, _re_type):
402 if isinstance(s, _re_type):
408 assert not s.search(printed),printed_msg.format(
403 assert not s.search(printed),printed_msg.format(
409 s.pattern, self.channel, printed)
404 s.pattern, self.channel, printed)
410 else:
405 else:
411 assert s not in printed, printed_msg.format(
406 assert s not in printed, printed_msg.format(
412 s, self.channel, printed)
407 s, self.channel, printed)
413 return False
408 return False
414 finally:
409 finally:
415 self.tee.close()
410 self.tee.close()
416
411
417 @contextmanager
412 @contextmanager
418 def mute_warn():
413 def mute_warn():
419 from IPython.utils import warn
414 from IPython.utils import warn
420 save_warn = warn.warn
415 save_warn = warn.warn
421 warn.warn = lambda *a, **kw: None
416 warn.warn = lambda *a, **kw: None
422 try:
417 try:
423 yield
418 yield
424 finally:
419 finally:
425 warn.warn = save_warn
420 warn.warn = save_warn
426
421
427 @contextmanager
422 @contextmanager
428 def make_tempfile(name):
423 def make_tempfile(name):
429 """Create an empty, named, temporary file for the duration of the context."""
424 """Create an empty, named, temporary file for the duration of the context."""
430 open(name, "w", encoding="utf-8").close()
425 open(name, "w", encoding="utf-8").close()
431 try:
426 try:
432 yield
427 yield
433 finally:
428 finally:
434 os.unlink(name)
429 os.unlink(name)
435
430
436 def fake_input(inputs):
431 def fake_input(inputs):
437 """Temporarily replace the input() function to return the given values
432 """Temporarily replace the input() function to return the given values
438
433
439 Use as a context manager:
434 Use as a context manager:
440
435
441 with fake_input(['result1', 'result2']):
436 with fake_input(['result1', 'result2']):
442 ...
437 ...
443
438
444 Values are returned in order. If input() is called again after the last value
439 Values are returned in order. If input() is called again after the last value
445 was used, EOFError is raised.
440 was used, EOFError is raised.
446 """
441 """
447 it = iter(inputs)
442 it = iter(inputs)
448 def mock_input(prompt=''):
443 def mock_input(prompt=''):
449 try:
444 try:
450 return next(it)
445 return next(it)
451 except StopIteration as e:
446 except StopIteration as e:
452 raise EOFError('No more inputs given') from e
447 raise EOFError('No more inputs given') from e
453
448
454 return patch('builtins.input', mock_input)
449 return patch('builtins.input', mock_input)
455
450
456 def help_output_test(subcommand=''):
451 def help_output_test(subcommand=''):
457 """test that `ipython [subcommand] -h` works"""
452 """test that `ipython [subcommand] -h` works"""
458 cmd = get_ipython_cmd() + [subcommand, '-h']
453 cmd = get_ipython_cmd() + [subcommand, '-h']
459 out, err, rc = get_output_error_code(cmd)
454 out, err, rc = get_output_error_code(cmd)
460 assert rc == 0, err
455 assert rc == 0, err
461 assert "Traceback" not in err
456 assert "Traceback" not in err
462 assert "Options" in out
457 assert "Options" in out
463 assert "--help-all" in out
458 assert "--help-all" in out
464 return out, err
459 return out, err
465
460
466
461
467 def help_all_output_test(subcommand=''):
462 def help_all_output_test(subcommand=''):
468 """test that `ipython [subcommand] --help-all` works"""
463 """test that `ipython [subcommand] --help-all` works"""
469 cmd = get_ipython_cmd() + [subcommand, '--help-all']
464 cmd = get_ipython_cmd() + [subcommand, '--help-all']
470 out, err, rc = get_output_error_code(cmd)
465 out, err, rc = get_output_error_code(cmd)
471 assert rc == 0, err
466 assert rc == 0, err
472 assert "Traceback" not in err
467 assert "Traceback" not in err
473 assert "Options" in out
468 assert "Options" in out
474 assert "Class" in out
469 assert "Class" in out
475 return out, err
470 return out, err
476
471
@@ -1,828 +1,886
1 """
1 """
2 Utilities for working with strings and text.
2 Utilities for working with strings and text.
3
3
4 Inheritance diagram:
4 Inheritance diagram:
5
5
6 .. inheritance-diagram:: IPython.utils.text
6 .. inheritance-diagram:: IPython.utils.text
7 :parts: 3
7 :parts: 3
8 """
8 """
9
9
10 import os
10 import os
11 import re
11 import re
12 import string
12 import string
13 import sys
13 import sys
14 import textwrap
14 import textwrap
15 import warnings
15 import warnings
16 from string import Formatter
16 from string import Formatter
17 from pathlib import Path
17 from pathlib import Path
18
18
19 from typing import List, Dict, Tuple, Optional, cast, Sequence, Mapping, Any
19 from typing import (
20 List,
21 Dict,
22 Tuple,
23 Optional,
24 cast,
25 Sequence,
26 Mapping,
27 Any,
28 Union,
29 Callable,
30 Iterator,
31 TypeVar,
32 )
20
33
21 if sys.version_info < (3, 12):
34 if sys.version_info < (3, 12):
22 from typing_extensions import Self
35 from typing_extensions import Self
23 else:
36 else:
24 from typing import Self
37 from typing import Self
25
38
26
39
27 class LSString(str):
40 class LSString(str):
28 """String derivative with a special access attributes.
41 """String derivative with a special access attributes.
29
42
30 These are normal strings, but with the special attributes:
43 These are normal strings, but with the special attributes:
31
44
32 .l (or .list) : value as list (split on newlines).
45 .l (or .list) : value as list (split on newlines).
33 .n (or .nlstr): original value (the string itself).
46 .n (or .nlstr): original value (the string itself).
34 .s (or .spstr): value as whitespace-separated string.
47 .s (or .spstr): value as whitespace-separated string.
35 .p (or .paths): list of path objects (requires path.py package)
48 .p (or .paths): list of path objects (requires path.py package)
36
49
37 Any values which require transformations are computed only once and
50 Any values which require transformations are computed only once and
38 cached.
51 cached.
39
52
40 Such strings are very useful to efficiently interact with the shell, which
53 Such strings are very useful to efficiently interact with the shell, which
41 typically only understands whitespace-separated options for commands."""
54 typically only understands whitespace-separated options for commands."""
42
55
43 __list: List[str]
56 __list: List[str]
44 __spstr: str
57 __spstr: str
45 __paths: List[Path]
58 __paths: List[Path]
46
59
47 def get_list(self) -> List[str]:
60 def get_list(self) -> List[str]:
48 try:
61 try:
49 return self.__list
62 return self.__list
50 except AttributeError:
63 except AttributeError:
51 self.__list = self.split('\n')
64 self.__list = self.split('\n')
52 return self.__list
65 return self.__list
53
66
54 l = list = property(get_list)
67 l = list = property(get_list)
55
68
56 def get_spstr(self) -> str:
69 def get_spstr(self) -> str:
57 try:
70 try:
58 return self.__spstr
71 return self.__spstr
59 except AttributeError:
72 except AttributeError:
60 self.__spstr = self.replace('\n',' ')
73 self.__spstr = self.replace('\n',' ')
61 return self.__spstr
74 return self.__spstr
62
75
63 s = spstr = property(get_spstr)
76 s = spstr = property(get_spstr)
64
77
65 def get_nlstr(self) -> Self:
78 def get_nlstr(self) -> Self:
66 return self
79 return self
67
80
68 n = nlstr = property(get_nlstr)
81 n = nlstr = property(get_nlstr)
69
82
70 def get_paths(self) -> List[Path]:
83 def get_paths(self) -> List[Path]:
71 try:
84 try:
72 return self.__paths
85 return self.__paths
73 except AttributeError:
86 except AttributeError:
74 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
87 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
75 return self.__paths
88 return self.__paths
76
89
77 p = paths = property(get_paths)
90 p = paths = property(get_paths)
78
91
79 # FIXME: We need to reimplement type specific displayhook and then add this
92 # FIXME: We need to reimplement type specific displayhook and then add this
80 # back as a custom printer. This should also be moved outside utils into the
93 # back as a custom printer. This should also be moved outside utils into the
81 # core.
94 # core.
82
95
83 # def print_lsstring(arg):
96 # def print_lsstring(arg):
84 # """ Prettier (non-repr-like) and more informative printer for LSString """
97 # """ Prettier (non-repr-like) and more informative printer for LSString """
85 # print("LSString (.p, .n, .l, .s available). Value:")
98 # print("LSString (.p, .n, .l, .s available). Value:")
86 # print(arg)
99 # print(arg)
87 #
100 #
88 #
101 #
89 # print_lsstring = result_display.register(LSString)(print_lsstring)
102 # print_lsstring = result_display.register(LSString)(print_lsstring)
90
103
91
104
92 class SList(list):
105 class SList(list):
93 """List derivative with a special access attributes.
106 """List derivative with a special access attributes.
94
107
95 These are normal lists, but with the special attributes:
108 These are normal lists, but with the special attributes:
96
109
97 * .l (or .list) : value as list (the list itself).
110 * .l (or .list) : value as list (the list itself).
98 * .n (or .nlstr): value as a string, joined on newlines.
111 * .n (or .nlstr): value as a string, joined on newlines.
99 * .s (or .spstr): value as a string, joined on spaces.
112 * .s (or .spstr): value as a string, joined on spaces.
100 * .p (or .paths): list of path objects (requires path.py package)
113 * .p (or .paths): list of path objects (requires path.py package)
101
114
102 Any values which require transformations are computed only once and
115 Any values which require transformations are computed only once and
103 cached."""
116 cached."""
104
117
105 __spstr: str
118 __spstr: str
106 __nlstr: str
119 __nlstr: str
107 __paths: List[Path]
120 __paths: List[Path]
108
121
109 def get_list(self) -> Self:
122 def get_list(self) -> Self:
110 return self
123 return self
111
124
112 l = list = property(get_list)
125 l = list = property(get_list)
113
126
114 def get_spstr(self) -> str:
127 def get_spstr(self) -> str:
115 try:
128 try:
116 return self.__spstr
129 return self.__spstr
117 except AttributeError:
130 except AttributeError:
118 self.__spstr = ' '.join(self)
131 self.__spstr = ' '.join(self)
119 return self.__spstr
132 return self.__spstr
120
133
121 s = spstr = property(get_spstr)
134 s = spstr = property(get_spstr)
122
135
123 def get_nlstr(self) -> str:
136 def get_nlstr(self) -> str:
124 try:
137 try:
125 return self.__nlstr
138 return self.__nlstr
126 except AttributeError:
139 except AttributeError:
127 self.__nlstr = '\n'.join(self)
140 self.__nlstr = '\n'.join(self)
128 return self.__nlstr
141 return self.__nlstr
129
142
130 n = nlstr = property(get_nlstr)
143 n = nlstr = property(get_nlstr)
131
144
132 def get_paths(self) -> List[Path]:
145 def get_paths(self) -> List[Path]:
133 try:
146 try:
134 return self.__paths
147 return self.__paths
135 except AttributeError:
148 except AttributeError:
136 self.__paths = [Path(p) for p in self if os.path.exists(p)]
149 self.__paths = [Path(p) for p in self if os.path.exists(p)]
137 return self.__paths
150 return self.__paths
138
151
139 p = paths = property(get_paths)
152 p = paths = property(get_paths)
140
153
141 def grep(self, pattern, prune = False, field = None):
154 def grep(
142 """ Return all strings matching 'pattern' (a regex or callable)
155 self,
156 pattern: Union[str, Callable[[Any], re.Match[str] | None]],
157 prune: bool = False,
158 field: Optional[int] = None,
159 ) -> Self:
160 """Return all strings matching 'pattern' (a regex or callable)
143
161
144 This is case-insensitive. If prune is true, return all items
162 This is case-insensitive. If prune is true, return all items
145 NOT matching the pattern.
163 NOT matching the pattern.
146
164
147 If field is specified, the match must occur in the specified
165 If field is specified, the match must occur in the specified
148 whitespace-separated field.
166 whitespace-separated field.
149
167
150 Examples::
168 Examples::
151
169
152 a.grep( lambda x: x.startswith('C') )
170 a.grep( lambda x: x.startswith('C') )
153 a.grep('Cha.*log', prune=1)
171 a.grep('Cha.*log', prune=1)
154 a.grep('chm', field=-1)
172 a.grep('chm', field=-1)
155 """
173 """
156
174
157 def match_target(s):
175 def match_target(s: str) -> str:
158 if field is None:
176 if field is None:
159 return s
177 return s
160 parts = s.split()
178 parts = s.split()
161 try:
179 try:
162 tgt = parts[field]
180 tgt = parts[field]
163 return tgt
181 return tgt
164 except IndexError:
182 except IndexError:
165 return ""
183 return ""
166
184
167 if isinstance(pattern, str):
185 if isinstance(pattern, str):
168 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
186 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
169 else:
187 else:
170 pred = pattern
188 pred = pattern
171 if not prune:
189 if not prune:
172 return SList([el for el in self if pred(match_target(el))])
190 return type(self)([el for el in self if pred(match_target(el))])
173 else:
191 else:
174 return SList([el for el in self if not pred(match_target(el))])
192 return type(self)([el for el in self if not pred(match_target(el))])
175
193
176 def fields(self, *fields):
194 def fields(self, *fields: List[str]) -> List[List[str]]:
177 """ Collect whitespace-separated fields from string list
195 """Collect whitespace-separated fields from string list
178
196
179 Allows quick awk-like usage of string lists.
197 Allows quick awk-like usage of string lists.
180
198
181 Example data (in var a, created by 'a = !ls -l')::
199 Example data (in var a, created by 'a = !ls -l')::
182
200
183 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
201 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
184 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
202 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
185
203
186 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
204 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
187 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
205 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
188 (note the joining by space).
206 (note the joining by space).
189 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
207 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
190
208
191 IndexErrors are ignored.
209 IndexErrors are ignored.
192
210
193 Without args, fields() just split()'s the strings.
211 Without args, fields() just split()'s the strings.
194 """
212 """
195 if len(fields) == 0:
213 if len(fields) == 0:
196 return [el.split() for el in self]
214 return [el.split() for el in self]
197
215
198 res = SList()
216 res = SList()
199 for el in [f.split() for f in self]:
217 for el in [f.split() for f in self]:
200 lineparts = []
218 lineparts = []
201
219
202 for fd in fields:
220 for fd in fields:
203 try:
221 try:
204 lineparts.append(el[fd])
222 lineparts.append(el[fd])
205 except IndexError:
223 except IndexError:
206 pass
224 pass
207 if lineparts:
225 if lineparts:
208 res.append(" ".join(lineparts))
226 res.append(" ".join(lineparts))
209
227
210 return res
228 return res
211
229
212 def sort(self,field= None, nums = False):
230 def sort( # type:ignore[override]
213 """ sort by specified fields (see fields())
231 self,
232 field: Optional[List[str]] = None,
233 nums: bool = False,
234 ) -> Self:
235 """sort by specified fields (see fields())
214
236
215 Example::
237 Example::
216
238
217 a.sort(1, nums = True)
239 a.sort(1, nums = True)
218
240
219 Sorts a by second field, in numerical order (so that 21 > 3)
241 Sorts a by second field, in numerical order (so that 21 > 3)
220
242
221 """
243 """
222
244
223 #decorate, sort, undecorate
245 #decorate, sort, undecorate
224 if field is not None:
246 if field is not None:
225 dsu = [[SList([line]).fields(field), line] for line in self]
247 dsu = [[SList([line]).fields(field), line] for line in self]
226 else:
248 else:
227 dsu = [[line, line] for line in self]
249 dsu = [[line, line] for line in self]
228 if nums:
250 if nums:
229 for i in range(len(dsu)):
251 for i in range(len(dsu)):
230 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
252 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
231 try:
253 try:
232 n = int(numstr)
254 n = int(numstr)
233 except ValueError:
255 except ValueError:
234 n = 0
256 n = 0
235 dsu[i][0] = n
257 dsu[i][0] = n
236
258
237
259
238 dsu.sort()
260 dsu.sort()
239 return SList([t[1] for t in dsu])
261 return type(self)([t[1] for t in dsu])
240
262
241
263
242 # FIXME: We need to reimplement type specific displayhook and then add this
264 # FIXME: We need to reimplement type specific displayhook and then add this
243 # back as a custom printer. This should also be moved outside utils into the
265 # back as a custom printer. This should also be moved outside utils into the
244 # core.
266 # core.
245
267
246 # def print_slist(arg):
268 # def print_slist(arg):
247 # """ Prettier (non-repr-like) and more informative printer for SList """
269 # """ Prettier (non-repr-like) and more informative printer for SList """
248 # print("SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):")
270 # print("SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):")
249 # if hasattr(arg, 'hideonce') and arg.hideonce:
271 # if hasattr(arg, 'hideonce') and arg.hideonce:
250 # arg.hideonce = False
272 # arg.hideonce = False
251 # return
273 # return
252 #
274 #
253 # nlprint(arg) # This was a nested list printer, now removed.
275 # nlprint(arg) # This was a nested list printer, now removed.
254 #
276 #
255 # print_slist = result_display.register(SList)(print_slist)
277 # print_slist = result_display.register(SList)(print_slist)
256
278
257
279
258 def indent(instr,nspaces=4, ntabs=0, flatten=False):
280 def indent(instr: str, nspaces: int = 4, ntabs: int = 0, flatten: bool = False) -> str:
259 """Indent a string a given number of spaces or tabstops.
281 """Indent a string a given number of spaces or tabstops.
260
282
261 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
283 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
262
284
263 Parameters
285 Parameters
264 ----------
286 ----------
265 instr : basestring
287 instr : basestring
266 The string to be indented.
288 The string to be indented.
267 nspaces : int (default: 4)
289 nspaces : int (default: 4)
268 The number of spaces to be indented.
290 The number of spaces to be indented.
269 ntabs : int (default: 0)
291 ntabs : int (default: 0)
270 The number of tabs to be indented.
292 The number of tabs to be indented.
271 flatten : bool (default: False)
293 flatten : bool (default: False)
272 Whether to scrub existing indentation. If True, all lines will be
294 Whether to scrub existing indentation. If True, all lines will be
273 aligned to the same indentation. If False, existing indentation will
295 aligned to the same indentation. If False, existing indentation will
274 be strictly increased.
296 be strictly increased.
275
297
276 Returns
298 Returns
277 -------
299 -------
278 str|unicode : string indented by ntabs and nspaces.
300 str : string indented by ntabs and nspaces.
279
301
280 """
302 """
281 if instr is None:
303 if instr is None:
282 return
304 return
283 ind = '\t'*ntabs+' '*nspaces
305 ind = '\t'*ntabs+' '*nspaces
284 if flatten:
306 if flatten:
285 pat = re.compile(r'^\s*', re.MULTILINE)
307 pat = re.compile(r'^\s*', re.MULTILINE)
286 else:
308 else:
287 pat = re.compile(r'^', re.MULTILINE)
309 pat = re.compile(r'^', re.MULTILINE)
288 outstr = re.sub(pat, ind, instr)
310 outstr = re.sub(pat, ind, instr)
289 if outstr.endswith(os.linesep+ind):
311 if outstr.endswith(os.linesep+ind):
290 return outstr[:-len(ind)]
312 return outstr[:-len(ind)]
291 else:
313 else:
292 return outstr
314 return outstr
293
315
294
316
295 def list_strings(arg):
317 def list_strings(arg: Union[str, List[str]]) -> List[str]:
296 """Always return a list of strings, given a string or list of strings
318 """Always return a list of strings, given a string or list of strings
297 as input.
319 as input.
298
320
299 Examples
321 Examples
300 --------
322 --------
301 ::
323 ::
302
324
303 In [7]: list_strings('A single string')
325 In [7]: list_strings('A single string')
304 Out[7]: ['A single string']
326 Out[7]: ['A single string']
305
327
306 In [8]: list_strings(['A single string in a list'])
328 In [8]: list_strings(['A single string in a list'])
307 Out[8]: ['A single string in a list']
329 Out[8]: ['A single string in a list']
308
330
309 In [9]: list_strings(['A','list','of','strings'])
331 In [9]: list_strings(['A','list','of','strings'])
310 Out[9]: ['A', 'list', 'of', 'strings']
332 Out[9]: ['A', 'list', 'of', 'strings']
311 """
333 """
312
334
313 if isinstance(arg, str):
335 if isinstance(arg, str):
314 return [arg]
336 return [arg]
315 else:
337 else:
316 return arg
338 return arg
317
339
318
340
319 def marquee(txt='',width=78,mark='*'):
341 def marquee(txt: str = "", width: int = 78, mark: str = "*") -> str:
320 """Return the input string centered in a 'marquee'.
342 """Return the input string centered in a 'marquee'.
321
343
322 Examples
344 Examples
323 --------
345 --------
324 ::
346 ::
325
347
326 In [16]: marquee('A test',40)
348 In [16]: marquee('A test',40)
327 Out[16]: '**************** A test ****************'
349 Out[16]: '**************** A test ****************'
328
350
329 In [17]: marquee('A test',40,'-')
351 In [17]: marquee('A test',40,'-')
330 Out[17]: '---------------- A test ----------------'
352 Out[17]: '---------------- A test ----------------'
331
353
332 In [18]: marquee('A test',40,' ')
354 In [18]: marquee('A test',40,' ')
333 Out[18]: ' A test '
355 Out[18]: ' A test '
334
356
335 """
357 """
336 if not txt:
358 if not txt:
337 return (mark*width)[:width]
359 return (mark*width)[:width]
338 nmark = (width-len(txt)-2)//len(mark)//2
360 nmark = (width-len(txt)-2)//len(mark)//2
339 if nmark < 0: nmark =0
361 if nmark < 0: nmark =0
340 marks = mark*nmark
362 marks = mark*nmark
341 return '%s %s %s' % (marks,txt,marks)
363 return '%s %s %s' % (marks,txt,marks)
342
364
343
365
344 ini_spaces_re = re.compile(r'^(\s+)')
366 ini_spaces_re = re.compile(r'^(\s+)')
345
367
346 def num_ini_spaces(strng):
368
369 def num_ini_spaces(strng: str) -> int:
347 """Return the number of initial spaces in a string"""
370 """Return the number of initial spaces in a string"""
348 warnings.warn(
371 warnings.warn(
349 "`num_ini_spaces` is Pending Deprecation since IPython 8.17."
372 "`num_ini_spaces` is Pending Deprecation since IPython 8.17."
350 "It is considered fro removal in in future version. "
373 "It is considered fro removal in in future version. "
351 "Please open an issue if you believe it should be kept.",
374 "Please open an issue if you believe it should be kept.",
352 stacklevel=2,
375 stacklevel=2,
353 category=PendingDeprecationWarning,
376 category=PendingDeprecationWarning,
354 )
377 )
355 ini_spaces = ini_spaces_re.match(strng)
378 ini_spaces = ini_spaces_re.match(strng)
356 if ini_spaces:
379 if ini_spaces:
357 return ini_spaces.end()
380 return ini_spaces.end()
358 else:
381 else:
359 return 0
382 return 0
360
383
361
384
362 def format_screen(strng):
385 def format_screen(strng: str) -> str:
363 """Format a string for screen printing.
386 """Format a string for screen printing.
364
387
365 This removes some latex-type format codes."""
388 This removes some latex-type format codes."""
366 # Paragraph continue
389 # Paragraph continue
367 par_re = re.compile(r'\\$',re.MULTILINE)
390 par_re = re.compile(r'\\$',re.MULTILINE)
368 strng = par_re.sub('',strng)
391 strng = par_re.sub('',strng)
369 return strng
392 return strng
370
393
371
394
372 def dedent(text: str) -> str:
395 def dedent(text: str) -> str:
373 """Equivalent of textwrap.dedent that ignores unindented first line.
396 """Equivalent of textwrap.dedent that ignores unindented first line.
374
397
375 This means it will still dedent strings like:
398 This means it will still dedent strings like:
376 '''foo
399 '''foo
377 is a bar
400 is a bar
378 '''
401 '''
379
402
380 For use in wrap_paragraphs.
403 For use in wrap_paragraphs.
381 """
404 """
382
405
383 if text.startswith('\n'):
406 if text.startswith('\n'):
384 # text starts with blank line, don't ignore the first line
407 # text starts with blank line, don't ignore the first line
385 return textwrap.dedent(text)
408 return textwrap.dedent(text)
386
409
387 # split first line
410 # split first line
388 splits = text.split('\n',1)
411 splits = text.split('\n',1)
389 if len(splits) == 1:
412 if len(splits) == 1:
390 # only one line
413 # only one line
391 return textwrap.dedent(text)
414 return textwrap.dedent(text)
392
415
393 first, rest = splits
416 first, rest = splits
394 # dedent everything but the first line
417 # dedent everything but the first line
395 rest = textwrap.dedent(rest)
418 rest = textwrap.dedent(rest)
396 return '\n'.join([first, rest])
419 return '\n'.join([first, rest])
397
420
398
421
399 def wrap_paragraphs(text, ncols=80):
422 def wrap_paragraphs(text: str, ncols: int = 80) -> List[str]:
400 """Wrap multiple paragraphs to fit a specified width.
423 """Wrap multiple paragraphs to fit a specified width.
401
424
402 This is equivalent to textwrap.wrap, but with support for multiple
425 This is equivalent to textwrap.wrap, but with support for multiple
403 paragraphs, as separated by empty lines.
426 paragraphs, as separated by empty lines.
404
427
405 Returns
428 Returns
406 -------
429 -------
407 list of complete paragraphs, wrapped to fill `ncols` columns.
430 list of complete paragraphs, wrapped to fill `ncols` columns.
408 """
431 """
409 warnings.warn(
432 warnings.warn(
410 "`wrap_paragraphs` is Pending Deprecation since IPython 8.17."
433 "`wrap_paragraphs` is Pending Deprecation since IPython 8.17."
411 "It is considered fro removal in in future version. "
434 "It is considered fro removal in in future version. "
412 "Please open an issue if you believe it should be kept.",
435 "Please open an issue if you believe it should be kept.",
413 stacklevel=2,
436 stacklevel=2,
414 category=PendingDeprecationWarning,
437 category=PendingDeprecationWarning,
415 )
438 )
416 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
439 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
417 text = dedent(text).strip()
440 text = dedent(text).strip()
418 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
441 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
419 out_ps = []
442 out_ps = []
420 indent_re = re.compile(r'\n\s+', re.MULTILINE)
443 indent_re = re.compile(r'\n\s+', re.MULTILINE)
421 for p in paragraphs:
444 for p in paragraphs:
422 # presume indentation that survives dedent is meaningful formatting,
445 # presume indentation that survives dedent is meaningful formatting,
423 # so don't fill unless text is flush.
446 # so don't fill unless text is flush.
424 if indent_re.search(p) is None:
447 if indent_re.search(p) is None:
425 # wrap paragraph
448 # wrap paragraph
426 p = textwrap.fill(p, ncols)
449 p = textwrap.fill(p, ncols)
427 out_ps.append(p)
450 out_ps.append(p)
428 return out_ps
451 return out_ps
429
452
430
453
431 def strip_email_quotes(text):
454 def strip_email_quotes(text: str) -> str:
432 """Strip leading email quotation characters ('>').
455 """Strip leading email quotation characters ('>').
433
456
434 Removes any combination of leading '>' interspersed with whitespace that
457 Removes any combination of leading '>' interspersed with whitespace that
435 appears *identically* in all lines of the input text.
458 appears *identically* in all lines of the input text.
436
459
437 Parameters
460 Parameters
438 ----------
461 ----------
439 text : str
462 text : str
440
463
441 Examples
464 Examples
442 --------
465 --------
443
466
444 Simple uses::
467 Simple uses::
445
468
446 In [2]: strip_email_quotes('> > text')
469 In [2]: strip_email_quotes('> > text')
447 Out[2]: 'text'
470 Out[2]: 'text'
448
471
449 In [3]: strip_email_quotes('> > text\\n> > more')
472 In [3]: strip_email_quotes('> > text\\n> > more')
450 Out[3]: 'text\\nmore'
473 Out[3]: 'text\\nmore'
451
474
452 Note how only the common prefix that appears in all lines is stripped::
475 Note how only the common prefix that appears in all lines is stripped::
453
476
454 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
477 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
455 Out[4]: '> text\\n> more\\nmore...'
478 Out[4]: '> text\\n> more\\nmore...'
456
479
457 So if any line has no quote marks ('>'), then none are stripped from any
480 So if any line has no quote marks ('>'), then none are stripped from any
458 of them ::
481 of them ::
459
482
460 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
483 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
461 Out[5]: '> > text\\n> > more\\nlast different'
484 Out[5]: '> > text\\n> > more\\nlast different'
462 """
485 """
463 lines = text.splitlines()
486 lines = text.splitlines()
464 strip_len = 0
487 strip_len = 0
465
488
466 for characters in zip(*lines):
489 for characters in zip(*lines):
467 # Check if all characters in this position are the same
490 # Check if all characters in this position are the same
468 if len(set(characters)) > 1:
491 if len(set(characters)) > 1:
469 break
492 break
470 prefix_char = characters[0]
493 prefix_char = characters[0]
471
494
472 if prefix_char in string.whitespace or prefix_char == ">":
495 if prefix_char in string.whitespace or prefix_char == ">":
473 strip_len += 1
496 strip_len += 1
474 else:
497 else:
475 break
498 break
476
499
477 text = "\n".join([ln[strip_len:] for ln in lines])
500 text = "\n".join([ln[strip_len:] for ln in lines])
478 return text
501 return text
479
502
480
503
481 def strip_ansi(source):
504 def strip_ansi(source: str) -> str:
482 """
505 """
483 Remove ansi escape codes from text.
506 Remove ansi escape codes from text.
484
507
485 Parameters
508 Parameters
486 ----------
509 ----------
487 source : str
510 source : str
488 Source to remove the ansi from
511 Source to remove the ansi from
489 """
512 """
490 warnings.warn(
513 warnings.warn(
491 "`strip_ansi` is Pending Deprecation since IPython 8.17."
514 "`strip_ansi` is Pending Deprecation since IPython 8.17."
492 "It is considered fro removal in in future version. "
515 "It is considered fro removal in in future version. "
493 "Please open an issue if you believe it should be kept.",
516 "Please open an issue if you believe it should be kept.",
494 stacklevel=2,
517 stacklevel=2,
495 category=PendingDeprecationWarning,
518 category=PendingDeprecationWarning,
496 )
519 )
497
520
498 return re.sub(r'\033\[(\d|;)+?m', '', source)
521 return re.sub(r'\033\[(\d|;)+?m', '', source)
499
522
500
523
501 class EvalFormatter(Formatter):
524 class EvalFormatter(Formatter):
502 """A String Formatter that allows evaluation of simple expressions.
525 """A String Formatter that allows evaluation of simple expressions.
503
526
504 Note that this version interprets a `:` as specifying a format string (as per
527 Note that this version interprets a `:` as specifying a format string (as per
505 standard string formatting), so if slicing is required, you must explicitly
528 standard string formatting), so if slicing is required, you must explicitly
506 create a slice.
529 create a slice.
507
530
508 This is to be used in templating cases, such as the parallel batch
531 This is to be used in templating cases, such as the parallel batch
509 script templates, where simple arithmetic on arguments is useful.
532 script templates, where simple arithmetic on arguments is useful.
510
533
511 Examples
534 Examples
512 --------
535 --------
513 ::
536 ::
514
537
515 In [1]: f = EvalFormatter()
538 In [1]: f = EvalFormatter()
516 In [2]: f.format('{n//4}', n=8)
539 In [2]: f.format('{n//4}', n=8)
517 Out[2]: '2'
540 Out[2]: '2'
518
541
519 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
542 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
520 Out[3]: 'll'
543 Out[3]: 'll'
521 """
544 """
522 def get_field(self, name, args, kwargs):
545
546 def get_field(self, name: str, args: Any, kwargs: Any) -> Tuple[Any, str]:
523 v = eval(name, kwargs)
547 v = eval(name, kwargs)
524 return v, name
548 return v, name
525
549
526 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
550 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
527 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
551 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
528 # above, it should be possible to remove FullEvalFormatter.
552 # above, it should be possible to remove FullEvalFormatter.
529
553
530 class FullEvalFormatter(Formatter):
554 class FullEvalFormatter(Formatter):
531 """A String Formatter that allows evaluation of simple expressions.
555 """A String Formatter that allows evaluation of simple expressions.
532
556
533 Any time a format key is not found in the kwargs,
557 Any time a format key is not found in the kwargs,
534 it will be tried as an expression in the kwargs namespace.
558 it will be tried as an expression in the kwargs namespace.
535
559
536 Note that this version allows slicing using [1:2], so you cannot specify
560 Note that this version allows slicing using [1:2], so you cannot specify
537 a format string. Use :class:`EvalFormatter` to permit format strings.
561 a format string. Use :class:`EvalFormatter` to permit format strings.
538
562
539 Examples
563 Examples
540 --------
564 --------
541 ::
565 ::
542
566
543 In [1]: f = FullEvalFormatter()
567 In [1]: f = FullEvalFormatter()
544 In [2]: f.format('{n//4}', n=8)
568 In [2]: f.format('{n//4}', n=8)
545 Out[2]: '2'
569 Out[2]: '2'
546
570
547 In [3]: f.format('{list(range(5))[2:4]}')
571 In [3]: f.format('{list(range(5))[2:4]}')
548 Out[3]: '[2, 3]'
572 Out[3]: '[2, 3]'
549
573
550 In [4]: f.format('{3*2}')
574 In [4]: f.format('{3*2}')
551 Out[4]: '6'
575 Out[4]: '6'
552 """
576 """
553 # copied from Formatter._vformat with minor changes to allow eval
577 # copied from Formatter._vformat with minor changes to allow eval
554 # and replace the format_spec code with slicing
578 # and replace the format_spec code with slicing
555 def vformat(
579 def vformat(
556 self, format_string: str, args: Sequence[Any], kwargs: Mapping[str, Any]
580 self, format_string: str, args: Sequence[Any], kwargs: Mapping[str, Any]
557 ) -> str:
581 ) -> str:
558 result = []
582 result = []
559 conversion: Optional[str]
583 conversion: Optional[str]
560 for literal_text, field_name, format_spec, conversion in self.parse(
584 for literal_text, field_name, format_spec, conversion in self.parse(
561 format_string
585 format_string
562 ):
586 ):
563 # output the literal text
587 # output the literal text
564 if literal_text:
588 if literal_text:
565 result.append(literal_text)
589 result.append(literal_text)
566
590
567 # if there's a field, output it
591 # if there's a field, output it
568 if field_name is not None:
592 if field_name is not None:
569 # this is some markup, find the object and do
593 # this is some markup, find the object and do
570 # the formatting
594 # the formatting
571
595
572 if format_spec:
596 if format_spec:
573 # override format spec, to allow slicing:
597 # override format spec, to allow slicing:
574 field_name = ':'.join([field_name, format_spec])
598 field_name = ':'.join([field_name, format_spec])
575
599
576 # eval the contents of the field for the object
600 # eval the contents of the field for the object
577 # to be formatted
601 # to be formatted
578 obj = eval(field_name, dict(kwargs))
602 obj = eval(field_name, dict(kwargs))
579
603
580 # do any conversion on the resulting object
604 # do any conversion on the resulting object
581 # type issue in typeshed, fined in https://github.com/python/typeshed/pull/11377
605 # type issue in typeshed, fined in https://github.com/python/typeshed/pull/11377
582 obj = self.convert_field(obj, conversion) # type: ignore[arg-type]
606 obj = self.convert_field(obj, conversion) # type: ignore[arg-type]
583
607
584 # format the object and append to the result
608 # format the object and append to the result
585 result.append(self.format_field(obj, ''))
609 result.append(self.format_field(obj, ''))
586
610
587 return ''.join(result)
611 return ''.join(result)
588
612
589
613
590 class DollarFormatter(FullEvalFormatter):
614 class DollarFormatter(FullEvalFormatter):
591 """Formatter allowing Itpl style $foo replacement, for names and attribute
615 """Formatter allowing Itpl style $foo replacement, for names and attribute
592 access only. Standard {foo} replacement also works, and allows full
616 access only. Standard {foo} replacement also works, and allows full
593 evaluation of its arguments.
617 evaluation of its arguments.
594
618
595 Examples
619 Examples
596 --------
620 --------
597 ::
621 ::
598
622
599 In [1]: f = DollarFormatter()
623 In [1]: f = DollarFormatter()
600 In [2]: f.format('{n//4}', n=8)
624 In [2]: f.format('{n//4}', n=8)
601 Out[2]: '2'
625 Out[2]: '2'
602
626
603 In [3]: f.format('23 * 76 is $result', result=23*76)
627 In [3]: f.format('23 * 76 is $result', result=23*76)
604 Out[3]: '23 * 76 is 1748'
628 Out[3]: '23 * 76 is 1748'
605
629
606 In [4]: f.format('$a or {b}', a=1, b=2)
630 In [4]: f.format('$a or {b}', a=1, b=2)
607 Out[4]: '1 or 2'
631 Out[4]: '1 or 2'
608 """
632 """
609 _dollar_pattern_ignore_single_quote = re.compile(r"(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)")
633
610 def parse(self, fmt_string):
634 _dollar_pattern_ignore_single_quote = re.compile(
611 for literal_txt, field_name, format_spec, conversion \
635 r"(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)"
612 in Formatter.parse(self, fmt_string):
636 )
613
637
638 def parse(self, fmt_string: str) -> Iterator[Tuple[Any, Any, Any, Any]]: # type: ignore
639 for literal_txt, field_name, format_spec, conversion in Formatter.parse(
640 self, fmt_string
641 ):
614 # Find $foo patterns in the literal text.
642 # Find $foo patterns in the literal text.
615 continue_from = 0
643 continue_from = 0
616 txt = ""
644 txt = ""
617 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
645 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
618 new_txt, new_field = m.group(1,2)
646 new_txt, new_field = m.group(1,2)
619 # $$foo --> $foo
647 # $$foo --> $foo
620 if new_field.startswith("$"):
648 if new_field.startswith("$"):
621 txt += new_txt + new_field
649 txt += new_txt + new_field
622 else:
650 else:
623 yield (txt + new_txt, new_field, "", None)
651 yield (txt + new_txt, new_field, "", None)
624 txt = ""
652 txt = ""
625 continue_from = m.end()
653 continue_from = m.end()
626
654
627 # Re-yield the {foo} style pattern
655 # Re-yield the {foo} style pattern
628 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
656 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
629
657
630 def __repr__(self):
658 def __repr__(self) -> str:
631 return "<DollarFormatter>"
659 return "<DollarFormatter>"
632
660
633 #-----------------------------------------------------------------------------
661 #-----------------------------------------------------------------------------
634 # Utils to columnize a list of string
662 # Utils to columnize a list of string
635 #-----------------------------------------------------------------------------
663 #-----------------------------------------------------------------------------
636
664
637 def _col_chunks(l, max_rows, row_first=False):
665
666 def _col_chunks(
667 l: List[int], max_rows: int, row_first: bool = False
668 ) -> Iterator[List[int]]:
638 """Yield successive max_rows-sized column chunks from l."""
669 """Yield successive max_rows-sized column chunks from l."""
639 if row_first:
670 if row_first:
640 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
671 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
641 for i in range(ncols):
672 for i in range(ncols):
642 yield [l[j] for j in range(i, len(l), ncols)]
673 yield [l[j] for j in range(i, len(l), ncols)]
643 else:
674 else:
644 for i in range(0, len(l), max_rows):
675 for i in range(0, len(l), max_rows):
645 yield l[i:(i + max_rows)]
676 yield l[i:(i + max_rows)]
646
677
647
678
648 def _find_optimal(
679 def _find_optimal(
649 rlist: List[str], row_first: bool, separator_size: int, displaywidth: int
680 rlist: List[int], row_first: bool, separator_size: int, displaywidth: int
650 ) -> Dict[str, Any]:
681 ) -> Dict[str, Any]:
651 """Calculate optimal info to columnize a list of string"""
682 """Calculate optimal info to columnize a list of string"""
652 for max_rows in range(1, len(rlist) + 1):
683 for max_rows in range(1, len(rlist) + 1):
653 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
684 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
654 sumlength = sum(col_widths)
685 sumlength = sum(col_widths)
655 ncols = len(col_widths)
686 ncols = len(col_widths)
656 if sumlength + separator_size * (ncols - 1) <= displaywidth:
687 if sumlength + separator_size * (ncols - 1) <= displaywidth:
657 break
688 break
658 return {'num_columns': ncols,
689 return {'num_columns': ncols,
659 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
690 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
660 'max_rows': max_rows,
691 'max_rows': max_rows,
661 'column_widths': col_widths
692 'column_widths': col_widths
662 }
693 }
663
694
664
695
665 def _get_or_default(mylist, i, default=None):
696 T = TypeVar("T")
697
698
699 def _get_or_default(mylist: List[T], i: int, default: T) -> T:
666 """return list item number, or default if don't exist"""
700 """return list item number, or default if don't exist"""
667 if i >= len(mylist):
701 if i >= len(mylist):
668 return default
702 return default
669 else :
703 else :
670 return mylist[i]
704 return mylist[i]
671
705
672
706
673 def compute_item_matrix(
707 def compute_item_matrix(
674 items: List[str],
708 items: List[str],
675 row_first: bool = False,
709 row_first: bool = False,
676 empty: Optional[str] = None,
710 empty: Optional[str] = None,
677 *,
711 *,
678 separator_size: int = 2,
712 separator_size: int = 2,
679 displaywidth: int = 80,
713 displaywidth: int = 80,
680 ) -> Tuple[List[List[int]], Dict[str, int]]:
714 ) -> Tuple[List[List[int]], Dict[str, int]]:
681 """Returns a nested list, and info to columnize items
715 """Returns a nested list, and info to columnize items
682
716
683 Parameters
717 Parameters
684 ----------
718 ----------
685 items
719 items
686 list of strings to columize
720 list of strings to columize
687 row_first : (default False)
721 row_first : (default False)
688 Whether to compute columns for a row-first matrix instead of
722 Whether to compute columns for a row-first matrix instead of
689 column-first (default).
723 column-first (default).
690 empty : (default None)
724 empty : (default None)
691 default value to fill list if needed
725 default value to fill list if needed
692 separator_size : int (default=2)
726 separator_size : int (default=2)
693 How much characters will be used as a separation between each columns.
727 How much characters will be used as a separation between each columns.
694 displaywidth : int (default=80)
728 displaywidth : int (default=80)
695 The width of the area onto which the columns should enter
729 The width of the area onto which the columns should enter
696
730
697 Returns
731 Returns
698 -------
732 -------
699 strings_matrix
733 strings_matrix
700 nested list of string, the outer most list contains as many list as
734 nested list of string, the outer most list contains as many list as
701 rows, the innermost lists have each as many element as columns. If the
735 rows, the innermost lists have each as many element as columns. If the
702 total number of elements in `items` does not equal the product of
736 total number of elements in `items` does not equal the product of
703 rows*columns, the last element of some lists are filled with `None`.
737 rows*columns, the last element of some lists are filled with `None`.
704 dict_info
738 dict_info
705 some info to make columnize easier:
739 some info to make columnize easier:
706
740
707 num_columns
741 num_columns
708 number of columns
742 number of columns
709 max_rows
743 max_rows
710 maximum number of rows (final number may be less)
744 maximum number of rows (final number may be less)
711 column_widths
745 column_widths
712 list of with of each columns
746 list of with of each columns
713 optimal_separator_width
747 optimal_separator_width
714 best separator width between columns
748 best separator width between columns
715
749
716 Examples
750 Examples
717 --------
751 --------
718 ::
752 ::
719
753
720 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
754 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
721 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
755 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
722 In [3]: list
756 In [3]: list
723 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
757 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
724 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
758 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
725 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
759 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
726 Out[5]: True
760 Out[5]: True
727 """
761 """
728 warnings.warn(
762 warnings.warn(
729 "`compute_item_matrix` is Pending Deprecation since IPython 8.17."
763 "`compute_item_matrix` is Pending Deprecation since IPython 8.17."
730 "It is considered fro removal in in future version. "
764 "It is considered fro removal in in future version. "
731 "Please open an issue if you believe it should be kept.",
765 "Please open an issue if you believe it should be kept.",
732 stacklevel=2,
766 stacklevel=2,
733 category=PendingDeprecationWarning,
767 category=PendingDeprecationWarning,
734 )
768 )
735 info = _find_optimal(
769 info = _find_optimal(
736 list(map(len, items)), # type: ignore[arg-type]
770 list(map(len, items)), # type: ignore[arg-type]
737 row_first,
771 row_first,
738 separator_size=separator_size,
772 separator_size=separator_size,
739 displaywidth=displaywidth,
773 displaywidth=displaywidth,
740 )
774 )
741 nrow, ncol = info["max_rows"], info["num_columns"]
775 nrow, ncol = info["max_rows"], info["num_columns"]
742 if row_first:
776 if row_first:
743 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
777 return (
778 [
779 [
780 _get_or_default(
781 items, r * ncol + c, default=empty
782 ) # type:ignore[misc]
783 for c in range(ncol)
784 ]
785 for r in range(nrow)
786 ],
787 info,
788 )
744 else:
789 else:
745 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
790 return (
791 [
792 [
793 _get_or_default(
794 items, c * nrow + r, default=empty
795 ) # type:ignore[misc]
796 for c in range(ncol)
797 ]
798 for r in range(nrow)
799 ],
800 info,
801 )
746
802
747
803
748 def columnize(
804 def columnize(
749 items: List[str],
805 items: List[str],
750 row_first: bool = False,
806 row_first: bool = False,
751 separator: str = " ",
807 separator: str = " ",
752 displaywidth: int = 80,
808 displaywidth: int = 80,
753 spread: bool = False,
809 spread: bool = False,
754 ) -> str:
810 ) -> str:
755 """Transform a list of strings into a single string with columns.
811 """Transform a list of strings into a single string with columns.
756
812
757 Parameters
813 Parameters
758 ----------
814 ----------
759 items : sequence of strings
815 items : sequence of strings
760 The strings to process.
816 The strings to process.
761 row_first : (default False)
817 row_first : (default False)
762 Whether to compute columns for a row-first matrix instead of
818 Whether to compute columns for a row-first matrix instead of
763 column-first (default).
819 column-first (default).
764 separator : str, optional [default is two spaces]
820 separator : str, optional [default is two spaces]
765 The string that separates columns.
821 The string that separates columns.
766 displaywidth : int, optional [default is 80]
822 displaywidth : int, optional [default is 80]
767 Width of the display in number of characters.
823 Width of the display in number of characters.
768
824
769 Returns
825 Returns
770 -------
826 -------
771 The formatted string.
827 The formatted string.
772 """
828 """
773 warnings.warn(
829 warnings.warn(
774 "`columnize` is Pending Deprecation since IPython 8.17."
830 "`columnize` is Pending Deprecation since IPython 8.17."
775 "It is considered for removal in future versions. "
831 "It is considered for removal in future versions. "
776 "Please open an issue if you believe it should be kept.",
832 "Please open an issue if you believe it should be kept.",
777 stacklevel=2,
833 stacklevel=2,
778 category=PendingDeprecationWarning,
834 category=PendingDeprecationWarning,
779 )
835 )
780 if not items:
836 if not items:
781 return "\n"
837 return "\n"
782 matrix: List[List[int]]
838 matrix: List[List[int]]
783 matrix, info = compute_item_matrix(
839 matrix, info = compute_item_matrix(
784 items,
840 items,
785 row_first=row_first,
841 row_first=row_first,
786 separator_size=len(separator),
842 separator_size=len(separator),
787 displaywidth=displaywidth,
843 displaywidth=displaywidth,
788 )
844 )
789 if spread:
845 if spread:
790 separator = separator.ljust(int(info["optimal_separator_width"]))
846 separator = separator.ljust(int(info["optimal_separator_width"]))
791 fmatrix: List[filter[int]] = [filter(None, x) for x in matrix]
847 fmatrix: List[filter[int]] = [filter(None, x) for x in matrix]
792 sjoin = lambda x: separator.join(
848 sjoin = lambda x: separator.join(
793 [y.ljust(w, " ") for y, w in zip(x, cast(List[int], info["column_widths"]))]
849 [y.ljust(w, " ") for y, w in zip(x, cast(List[int], info["column_widths"]))]
794 )
850 )
795 return "\n".join(map(sjoin, fmatrix)) + "\n"
851 return "\n".join(map(sjoin, fmatrix)) + "\n"
796
852
797
853
798 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
854 def get_text_list(
855 list_: List[str], last_sep: str = " and ", sep: str = ", ", wrap_item_with: str = ""
856 ) -> str:
799 """
857 """
800 Return a string with a natural enumeration of items
858 Return a string with a natural enumeration of items
801
859
802 >>> get_text_list(['a', 'b', 'c', 'd'])
860 >>> get_text_list(['a', 'b', 'c', 'd'])
803 'a, b, c and d'
861 'a, b, c and d'
804 >>> get_text_list(['a', 'b', 'c'], ' or ')
862 >>> get_text_list(['a', 'b', 'c'], ' or ')
805 'a, b or c'
863 'a, b or c'
806 >>> get_text_list(['a', 'b', 'c'], ', ')
864 >>> get_text_list(['a', 'b', 'c'], ', ')
807 'a, b, c'
865 'a, b, c'
808 >>> get_text_list(['a', 'b'], ' or ')
866 >>> get_text_list(['a', 'b'], ' or ')
809 'a or b'
867 'a or b'
810 >>> get_text_list(['a'])
868 >>> get_text_list(['a'])
811 'a'
869 'a'
812 >>> get_text_list([])
870 >>> get_text_list([])
813 ''
871 ''
814 >>> get_text_list(['a', 'b'], wrap_item_with="`")
872 >>> get_text_list(['a', 'b'], wrap_item_with="`")
815 '`a` and `b`'
873 '`a` and `b`'
816 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
874 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
817 'a + b + c = d'
875 'a + b + c = d'
818 """
876 """
819 if len(list_) == 0:
877 if len(list_) == 0:
820 return ''
878 return ''
821 if wrap_item_with:
879 if wrap_item_with:
822 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
880 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
823 item in list_]
881 item in list_]
824 if len(list_) == 1:
882 if len(list_) == 1:
825 return list_[0]
883 return list_[0]
826 return '%s%s%s' % (
884 return '%s%s%s' % (
827 sep.join(i for i in list_[:-1]),
885 sep.join(i for i in list_[:-1]),
828 last_sep, list_[-1])
886 last_sep, list_[-1])
@@ -1,375 +1,385
1 [build-system]
1 [build-system]
2 requires = ["setuptools>=61.2"]
2 requires = ["setuptools>=61.2"]
3 # We need access to the 'setupbase' module at build time.
3 # We need access to the 'setupbase' module at build time.
4 # Hence we declare a custom build backend.
4 # Hence we declare a custom build backend.
5 build-backend = "_build_meta" # just re-exports setuptools.build_meta definitions
5 build-backend = "_build_meta" # just re-exports setuptools.build_meta definitions
6 backend-path = ["."]
6 backend-path = ["."]
7
7
8 [project]
8 [project]
9 name = "ipython"
9 name = "ipython"
10 description = "IPython: Productive Interactive Computing"
10 description = "IPython: Productive Interactive Computing"
11 keywords = ["Interactive", "Interpreter", "Shell", "Embedding"]
11 keywords = ["Interactive", "Interpreter", "Shell", "Embedding"]
12 classifiers = [
12 classifiers = [
13 "Framework :: IPython",
13 "Framework :: IPython",
14 "Framework :: Jupyter",
14 "Framework :: Jupyter",
15 "Intended Audience :: Developers",
15 "Intended Audience :: Developers",
16 "Intended Audience :: Science/Research",
16 "Intended Audience :: Science/Research",
17 "License :: OSI Approved :: BSD License",
17 "License :: OSI Approved :: BSD License",
18 "Programming Language :: Python",
18 "Programming Language :: Python",
19 "Programming Language :: Python :: 3",
19 "Programming Language :: Python :: 3",
20 "Programming Language :: Python :: 3 :: Only",
20 "Programming Language :: Python :: 3 :: Only",
21 "Topic :: System :: Shells",
21 "Topic :: System :: Shells",
22 ]
22 ]
23 requires-python = ">=3.10"
23 requires-python = ">=3.10"
24 dependencies = [
24 dependencies = [
25 'colorama; sys_platform == "win32"',
25 'colorama; sys_platform == "win32"',
26 "decorator",
26 "decorator",
27 "exceptiongroup; python_version<'3.11'",
27 "exceptiongroup; python_version<'3.11'",
28 "jedi>=0.16",
28 "jedi>=0.16",
29 "matplotlib-inline",
29 "matplotlib-inline",
30 'pexpect>4.3; sys_platform != "win32" and sys_platform != "emscripten"',
30 'pexpect>4.3; sys_platform != "win32" and sys_platform != "emscripten"',
31 "prompt_toolkit>=3.0.41,<3.1.0",
31 "prompt_toolkit>=3.0.41,<3.1.0",
32 "pygments>=2.4.0",
32 "pygments>=2.4.0",
33 "stack_data",
33 "stack_data",
34 "traitlets>=5.13.0",
34 "traitlets>=5.13.0",
35 "typing_extensions>=4.6; python_version<'3.12'",
35 "typing_extensions>=4.6; python_version<'3.12'",
36 ]
36 ]
37 dynamic = ["authors", "license", "version"]
37 dynamic = ["authors", "license", "version"]
38
38
39 [project.entry-points."pygments.lexers"]
39 [project.entry-points."pygments.lexers"]
40 ipythonconsole = "IPython.lib.lexers:IPythonConsoleLexer"
40 ipythonconsole = "IPython.lib.lexers:IPythonConsoleLexer"
41 ipython = "IPython.lib.lexers:IPythonLexer"
41 ipython = "IPython.lib.lexers:IPythonLexer"
42 ipython3 = "IPython.lib.lexers:IPython3Lexer"
42 ipython3 = "IPython.lib.lexers:IPython3Lexer"
43
43
44 [project.scripts]
44 [project.scripts]
45 ipython = "IPython:start_ipython"
45 ipython = "IPython:start_ipython"
46 ipython3 = "IPython:start_ipython"
46 ipython3 = "IPython:start_ipython"
47
47
48 [project.readme]
48 [project.readme]
49 file = "long_description.rst"
49 file = "long_description.rst"
50 content-type = "text/x-rst"
50 content-type = "text/x-rst"
51
51
52 [project.urls]
52 [project.urls]
53 Homepage = "https://ipython.org"
53 Homepage = "https://ipython.org"
54 Documentation = "https://ipython.readthedocs.io/"
54 Documentation = "https://ipython.readthedocs.io/"
55 Funding = "https://numfocus.org/"
55 Funding = "https://numfocus.org/"
56 Source = "https://github.com/ipython/ipython"
56 Source = "https://github.com/ipython/ipython"
57 Tracker = "https://github.com/ipython/ipython/issues"
57 Tracker = "https://github.com/ipython/ipython/issues"
58
58
59 [project.optional-dependencies]
59 [project.optional-dependencies]
60 black = [
60 black = [
61 "black",
61 "black",
62 ]
62 ]
63 doc = [
63 doc = [
64 "docrepr",
64 "docrepr",
65 "exceptiongroup",
65 "exceptiongroup",
66 "intersphinx_registry",
66 "intersphinx_registry",
67 "ipykernel",
67 "ipykernel",
68 "ipython[test]",
68 "ipython[test]",
69 "matplotlib",
69 "matplotlib",
70 "setuptools>=18.5",
70 "setuptools>=18.5",
71 "sphinx-rtd-theme",
71 "sphinx-rtd-theme",
72 "sphinx>=1.3",
72 "sphinx>=1.3",
73 "sphinxcontrib-jquery",
73 "sphinxcontrib-jquery",
74 "tomli ; python_version<'3.11'",
74 "tomli ; python_version<'3.11'",
75 "typing_extensions",
75 "typing_extensions",
76 ]
76 ]
77 kernel = [
77 kernel = [
78 "ipykernel",
78 "ipykernel",
79 ]
79 ]
80 nbconvert = [
80 nbconvert = [
81 "nbconvert",
81 "nbconvert",
82 ]
82 ]
83 nbformat = [
83 nbformat = [
84 "nbformat",
84 "nbformat",
85 ]
85 ]
86 notebook = [
86 notebook = [
87 "ipywidgets",
87 "ipywidgets",
88 "notebook",
88 "notebook",
89 ]
89 ]
90 parallel = [
90 parallel = [
91 "ipyparallel",
91 "ipyparallel",
92 ]
92 ]
93 qtconsole = [
93 qtconsole = [
94 "qtconsole",
94 "qtconsole",
95 ]
95 ]
96 terminal = []
96 terminal = []
97 test = [
97 test = [
98 "pytest",
98 "pytest",
99 "pytest-asyncio<0.22",
99 "pytest-asyncio<0.22",
100 "testpath",
100 "testpath",
101 "pickleshare",
101 "pickleshare",
102 "packaging",
102 "packaging",
103 ]
103 ]
104 test_extra = [
104 test_extra = [
105 "ipython[test]",
105 "ipython[test]",
106 "curio",
106 "curio",
107 "matplotlib!=3.2.0",
107 "matplotlib!=3.2.0",
108 "nbformat",
108 "nbformat",
109 "numpy>=1.23",
109 "numpy>=1.23",
110 "pandas",
110 "pandas",
111 "trio",
111 "trio",
112 ]
112 ]
113 matplotlib = [
113 matplotlib = [
114 "matplotlib"
114 "matplotlib"
115 ]
115 ]
116 all = [
116 all = [
117 "ipython[black,doc,kernel,nbconvert,nbformat,notebook,parallel,qtconsole,matplotlib]",
117 "ipython[black,doc,kernel,nbconvert,nbformat,notebook,parallel,qtconsole,matplotlib]",
118 "ipython[test,test_extra]",
118 "ipython[test,test_extra]",
119 ]
119 ]
120
120
121 [tool.mypy]
121 [tool.mypy]
122 python_version = "3.10"
122 python_version = "3.10"
123 ignore_missing_imports = true
123 ignore_missing_imports = true
124 follow_imports = 'silent'
124 follow_imports = 'silent'
125 exclude = [
125 exclude = [
126 'test_\.+\.py',
126 'test_\.+\.py',
127 'IPython.utils.tests.test_wildcard',
127 'IPython.utils.tests.test_wildcard',
128 'testing',
128 'testing',
129 'tests',
129 'tests',
130 'PyColorize.py',
130 'PyColorize.py',
131 '_process_win32_controller.py',
131 '_process_win32_controller.py',
132 'IPython/core/application.py',
132 'IPython/core/application.py',
133 'IPython/core/profileapp.py',
133 'IPython/core/profileapp.py',
134 'IPython/lib/deepreload.py',
134 'IPython/lib/deepreload.py',
135 'IPython/sphinxext/ipython_directive.py',
135 'IPython/sphinxext/ipython_directive.py',
136 'IPython/terminal/ipapp.py',
136 'IPython/terminal/ipapp.py',
137 'IPython/utils/_process_win32.py',
137 'IPython/utils/_process_win32.py',
138 'IPython/utils/path.py',
138 'IPython/utils/path.py',
139 ]
139 ]
140 disallow_untyped_defs = true
140 disallow_untyped_defs = true
141 # ignore_errors = false
141 # ignore_errors = false
142 # ignore_missing_imports = false
142 # ignore_missing_imports = false
143 # disallow_untyped_calls = true
143 # disallow_untyped_calls = true
144 disallow_incomplete_defs = true
144 disallow_incomplete_defs = true
145 # check_untyped_defs = true
145 # check_untyped_defs = true
146 # disallow_untyped_decorators = true
146 # disallow_untyped_decorators = true
147 warn_redundant_casts = true
147 warn_redundant_casts = true
148
148
149 [[tool.mypy.overrides]]
149 [[tool.mypy.overrides]]
150 module = [
150 module = [
151 "IPython.utils.text",
151 "IPython.utils.text",
152 ]
152 ]
153 disallow_untyped_defs = true
154 check_untyped_defs = false
155 disallow_untyped_decorators = true
156
157 [[tool.mypy.overrides]]
158 module = [
159 ]
153 disallow_untyped_defs = false
160 disallow_untyped_defs = false
161 ignore_errors = true
162 ignore_missing_imports = true
163 disallow_untyped_calls = false
164 disallow_incomplete_defs = false
154 check_untyped_defs = false
165 check_untyped_defs = false
155 disallow_untyped_decorators = false
166 disallow_untyped_decorators = false
156
167
157
158 # gloabl ignore error
168 # gloabl ignore error
159 [[tool.mypy.overrides]]
169 [[tool.mypy.overrides]]
160 module = [
170 module = [
161 "IPython",
171 "IPython",
162 "IPython.conftest",
172 "IPython.conftest",
163 "IPython.core.alias",
173 "IPython.core.alias",
164 "IPython.core.async_helpers",
174 "IPython.core.async_helpers",
165 "IPython.core.autocall",
175 "IPython.core.autocall",
166 "IPython.core.builtin_trap",
176 "IPython.core.builtin_trap",
167 "IPython.core.compilerop",
177 "IPython.core.compilerop",
168 "IPython.core.completer",
178 "IPython.core.completer",
169 "IPython.core.completerlib",
179 "IPython.core.completerlib",
170 "IPython.core.crashhandler",
180 "IPython.core.crashhandler",
171 "IPython.core.debugger",
181 "IPython.core.debugger",
172 "IPython.core.display",
182 "IPython.core.display",
173 "IPython.core.display_functions",
183 "IPython.core.display_functions",
174 "IPython.core.display_trap",
184 "IPython.core.display_trap",
175 "IPython.core.displayhook",
185 "IPython.core.displayhook",
176 "IPython.core.displaypub",
186 "IPython.core.displaypub",
177 "IPython.core.events",
187 "IPython.core.events",
178 "IPython.core.excolors",
188 "IPython.core.excolors",
179 "IPython.core.extensions",
189 "IPython.core.extensions",
180 "IPython.core.formatters",
190 "IPython.core.formatters",
181 "IPython.core.getipython",
191 "IPython.core.getipython",
182 "IPython.core.guarded_eval",
192 "IPython.core.guarded_eval",
183 "IPython.core.history",
193 "IPython.core.history",
184 "IPython.core.historyapp",
194 "IPython.core.historyapp",
185 "IPython.core.hooks",
195 "IPython.core.hooks",
186 "IPython.core.inputsplitter",
196 "IPython.core.inputsplitter",
187 "IPython.core.inputtransformer",
197 "IPython.core.inputtransformer",
188 "IPython.core.inputtransformer2",
198 "IPython.core.inputtransformer2",
189 "IPython.core.interactiveshell",
199 "IPython.core.interactiveshell",
190 "IPython.core.logger",
200 "IPython.core.logger",
191 "IPython.core.macro",
201 "IPython.core.macro",
192 "IPython.core.magic",
202 "IPython.core.magic",
193 "IPython.core.magic_arguments",
203 "IPython.core.magic_arguments",
194 "IPython.core.magics.ast_mod",
204 "IPython.core.magics.ast_mod",
195 "IPython.core.magics.auto",
205 "IPython.core.magics.auto",
196 "IPython.core.magics.basic",
206 "IPython.core.magics.basic",
197 "IPython.core.magics.code",
207 "IPython.core.magics.code",
198 "IPython.core.magics.config",
208 "IPython.core.magics.config",
199 "IPython.core.magics.display",
209 "IPython.core.magics.display",
200 "IPython.core.magics.execution",
210 "IPython.core.magics.execution",
201 "IPython.core.magics.extension",
211 "IPython.core.magics.extension",
202 "IPython.core.magics.history",
212 "IPython.core.magics.history",
203 "IPython.core.magics.logging",
213 "IPython.core.magics.logging",
204 "IPython.core.magics.namespace",
214 "IPython.core.magics.namespace",
205 "IPython.core.magics.osm",
215 "IPython.core.magics.osm",
206 "IPython.core.magics.packaging",
216 "IPython.core.magics.packaging",
207 "IPython.core.magics.pylab",
217 "IPython.core.magics.pylab",
208 "IPython.core.magics.script",
218 "IPython.core.magics.script",
209 "IPython.core.oinspect",
219 "IPython.core.oinspect",
210 "IPython.core.page",
220 "IPython.core.page",
211 "IPython.core.payload",
221 "IPython.core.payload",
212 "IPython.core.payloadpage",
222 "IPython.core.payloadpage",
213 "IPython.core.prefilter",
223 "IPython.core.prefilter",
214 "IPython.core.profiledir",
224 "IPython.core.profiledir",
215 "IPython.core.prompts",
225 "IPython.core.prompts",
216 "IPython.core.pylabtools",
226 "IPython.core.pylabtools",
217 "IPython.core.shellapp",
227 "IPython.core.shellapp",
218 "IPython.core.splitinput",
228 "IPython.core.splitinput",
219 "IPython.core.ultratb",
229 "IPython.core.ultratb",
220 "IPython.extensions.autoreload",
230 "IPython.extensions.autoreload",
221 "IPython.extensions.storemagic",
231 "IPython.extensions.storemagic",
222 "IPython.external.qt_for_kernel",
232 "IPython.external.qt_for_kernel",
223 "IPython.external.qt_loaders",
233 "IPython.external.qt_loaders",
224 "IPython.lib.backgroundjobs",
234 "IPython.lib.backgroundjobs",
225 "IPython.lib.clipboard",
235 "IPython.lib.clipboard",
226 "IPython.lib.demo",
236 "IPython.lib.demo",
227 "IPython.lib.display",
237 "IPython.lib.display",
228 "IPython.lib.editorhooks",
238 "IPython.lib.editorhooks",
229 "IPython.lib.guisupport",
239 "IPython.lib.guisupport",
230 "IPython.lib.latextools",
240 "IPython.lib.latextools",
231 "IPython.lib.lexers",
241 "IPython.lib.lexers",
232 "IPython.lib.pretty",
242 "IPython.lib.pretty",
233 "IPython.paths",
243 "IPython.paths",
234 "IPython.sphinxext.ipython_console_highlighting",
244 "IPython.sphinxext.ipython_console_highlighting",
235 "IPython.terminal.debugger",
245 "IPython.terminal.debugger",
236 "IPython.terminal.embed",
246 "IPython.terminal.embed",
237 "IPython.terminal.interactiveshell",
247 "IPython.terminal.interactiveshell",
238 "IPython.terminal.magics",
248 "IPython.terminal.magics",
239 "IPython.terminal.prompts",
249 "IPython.terminal.prompts",
240 "IPython.terminal.pt_inputhooks",
250 "IPython.terminal.pt_inputhooks",
241 "IPython.terminal.pt_inputhooks.asyncio",
251 "IPython.terminal.pt_inputhooks.asyncio",
242 "IPython.terminal.pt_inputhooks.glut",
252 "IPython.terminal.pt_inputhooks.glut",
243 "IPython.terminal.pt_inputhooks.gtk",
253 "IPython.terminal.pt_inputhooks.gtk",
244 "IPython.terminal.pt_inputhooks.gtk3",
254 "IPython.terminal.pt_inputhooks.gtk3",
245 "IPython.terminal.pt_inputhooks.gtk4",
255 "IPython.terminal.pt_inputhooks.gtk4",
246 "IPython.terminal.pt_inputhooks.osx",
256 "IPython.terminal.pt_inputhooks.osx",
247 "IPython.terminal.pt_inputhooks.pyglet",
257 "IPython.terminal.pt_inputhooks.pyglet",
248 "IPython.terminal.pt_inputhooks.qt",
258 "IPython.terminal.pt_inputhooks.qt",
249 "IPython.terminal.pt_inputhooks.tk",
259 "IPython.terminal.pt_inputhooks.tk",
250 "IPython.terminal.pt_inputhooks.wx",
260 "IPython.terminal.pt_inputhooks.wx",
251 "IPython.terminal.ptutils",
261 "IPython.terminal.ptutils",
252 "IPython.terminal.shortcuts",
262 "IPython.terminal.shortcuts",
253 "IPython.terminal.shortcuts.auto_match",
263 "IPython.terminal.shortcuts.auto_match",
254 "IPython.terminal.shortcuts.auto_suggest",
264 "IPython.terminal.shortcuts.auto_suggest",
255 "IPython.terminal.shortcuts.filters",
265 "IPython.terminal.shortcuts.filters",
256 "IPython.utils._process_cli",
266 "IPython.utils._process_cli",
257 "IPython.utils._process_common",
267 "IPython.utils._process_common",
258 "IPython.utils._process_emscripten",
268 "IPython.utils._process_emscripten",
259 "IPython.utils._process_posix",
269 "IPython.utils._process_posix",
260 "IPython.utils.capture",
270 "IPython.utils.capture",
261 "IPython.utils.coloransi",
271 "IPython.utils.coloransi",
262 "IPython.utils.contexts",
272 "IPython.utils.contexts",
263 "IPython.utils.data",
273 "IPython.utils.data",
264 "IPython.utils.decorators",
274 "IPython.utils.decorators",
265 "IPython.utils.dir2",
275 "IPython.utils.dir2",
266 "IPython.utils.encoding",
276 "IPython.utils.encoding",
267 "IPython.utils.frame",
277 "IPython.utils.frame",
268 "IPython.utils.generics",
278 "IPython.utils.generics",
269 "IPython.utils.importstring",
279 "IPython.utils.importstring",
270 "IPython.utils.io",
280 "IPython.utils.io",
271 "IPython.utils.ipstruct",
281 "IPython.utils.ipstruct",
272 "IPython.utils.module_paths",
282 "IPython.utils.module_paths",
273 "IPython.utils.openpy",
283 "IPython.utils.openpy",
274 "IPython.utils.process",
284 "IPython.utils.process",
275 "IPython.utils.py3compat",
285 "IPython.utils.py3compat",
276 "IPython.utils.sentinel",
286 "IPython.utils.sentinel",
277 "IPython.utils.shimmodule",
287 "IPython.utils.shimmodule",
278 "IPython.utils.strdispatch",
288 "IPython.utils.strdispatch",
279 "IPython.utils.sysinfo",
289 "IPython.utils.sysinfo",
280 "IPython.utils.syspathcontext",
290 "IPython.utils.syspathcontext",
281 "IPython.utils.tempdir",
291 "IPython.utils.tempdir",
282 "IPython.utils.terminal",
292 "IPython.utils.terminal",
283 "IPython.utils.timing",
293 "IPython.utils.timing",
284 "IPython.utils.tokenutil",
294 "IPython.utils.tokenutil",
285 "IPython.utils.tz",
295 "IPython.utils.tz",
286 "IPython.utils.ulinecache",
296 "IPython.utils.ulinecache",
287 "IPython.utils.version",
297 "IPython.utils.version",
288 "IPython.utils.wildcard",
298 "IPython.utils.wildcard",
289
299
290 ]
300 ]
291 disallow_untyped_defs = false
301 disallow_untyped_defs = false
292 ignore_errors = true
302 ignore_errors = true
293 ignore_missing_imports = true
303 ignore_missing_imports = true
294 disallow_untyped_calls = false
304 disallow_untyped_calls = false
295 disallow_incomplete_defs = false
305 disallow_incomplete_defs = false
296 check_untyped_defs = false
306 check_untyped_defs = false
297 disallow_untyped_decorators = false
307 disallow_untyped_decorators = false
298
308
299 [tool.pytest.ini_options]
309 [tool.pytest.ini_options]
300 addopts = [
310 addopts = [
301 "--durations=10",
311 "--durations=10",
302 "-pIPython.testing.plugin.pytest_ipdoctest",
312 "-pIPython.testing.plugin.pytest_ipdoctest",
303 "--ipdoctest-modules",
313 "--ipdoctest-modules",
304 "--ignore=docs",
314 "--ignore=docs",
305 "--ignore=examples",
315 "--ignore=examples",
306 "--ignore=htmlcov",
316 "--ignore=htmlcov",
307 "--ignore=ipython_kernel",
317 "--ignore=ipython_kernel",
308 "--ignore=ipython_parallel",
318 "--ignore=ipython_parallel",
309 "--ignore=results",
319 "--ignore=results",
310 "--ignore=tmp",
320 "--ignore=tmp",
311 "--ignore=tools",
321 "--ignore=tools",
312 "--ignore=traitlets",
322 "--ignore=traitlets",
313 "--ignore=IPython/core/tests/daft_extension",
323 "--ignore=IPython/core/tests/daft_extension",
314 "--ignore=IPython/sphinxext",
324 "--ignore=IPython/sphinxext",
315 "--ignore=IPython/terminal/pt_inputhooks",
325 "--ignore=IPython/terminal/pt_inputhooks",
316 "--ignore=IPython/__main__.py",
326 "--ignore=IPython/__main__.py",
317 "--ignore=IPython/external/qt_for_kernel.py",
327 "--ignore=IPython/external/qt_for_kernel.py",
318 "--ignore=IPython/html/widgets/widget_link.py",
328 "--ignore=IPython/html/widgets/widget_link.py",
319 "--ignore=IPython/html/widgets/widget_output.py",
329 "--ignore=IPython/html/widgets/widget_output.py",
320 "--ignore=IPython/terminal/console.py",
330 "--ignore=IPython/terminal/console.py",
321 "--ignore=IPython/utils/_process_cli.py",
331 "--ignore=IPython/utils/_process_cli.py",
322 "--ignore=IPython/utils/_process_posix.py",
332 "--ignore=IPython/utils/_process_posix.py",
323 "--ignore=IPython/utils/_process_win32.py",
333 "--ignore=IPython/utils/_process_win32.py",
324 "--ignore=IPython/utils/_process_win32_controller.py",
334 "--ignore=IPython/utils/_process_win32_controller.py",
325 "--ignore=IPython/utils/daemonize.py",
335 "--ignore=IPython/utils/daemonize.py",
326 "--ignore=IPython/utils/eventful.py",
336 "--ignore=IPython/utils/eventful.py",
327 "--ignore=IPython/kernel",
337 "--ignore=IPython/kernel",
328 "--ignore=IPython/consoleapp.py",
338 "--ignore=IPython/consoleapp.py",
329 "--ignore=IPython/core/inputsplitter.py",
339 "--ignore=IPython/core/inputsplitter.py",
330 "--ignore=IPython/lib/kernel.py",
340 "--ignore=IPython/lib/kernel.py",
331 "--ignore=IPython/utils/jsonutil.py",
341 "--ignore=IPython/utils/jsonutil.py",
332 "--ignore=IPython/utils/localinterfaces.py",
342 "--ignore=IPython/utils/localinterfaces.py",
333 "--ignore=IPython/utils/log.py",
343 "--ignore=IPython/utils/log.py",
334 "--ignore=IPython/utils/signatures.py",
344 "--ignore=IPython/utils/signatures.py",
335 "--ignore=IPython/utils/traitlets.py",
345 "--ignore=IPython/utils/traitlets.py",
336 "--ignore=IPython/utils/version.py"
346 "--ignore=IPython/utils/version.py"
337 ]
347 ]
338 doctest_optionflags = [
348 doctest_optionflags = [
339 "NORMALIZE_WHITESPACE",
349 "NORMALIZE_WHITESPACE",
340 "ELLIPSIS"
350 "ELLIPSIS"
341 ]
351 ]
342 ipdoctest_optionflags = [
352 ipdoctest_optionflags = [
343 "NORMALIZE_WHITESPACE",
353 "NORMALIZE_WHITESPACE",
344 "ELLIPSIS"
354 "ELLIPSIS"
345 ]
355 ]
346 asyncio_mode = "strict"
356 asyncio_mode = "strict"
347
357
348 [tool.pyright]
358 [tool.pyright]
349 pythonPlatform="All"
359 pythonPlatform="All"
350
360
351 [tool.setuptools]
361 [tool.setuptools]
352 zip-safe = false
362 zip-safe = false
353 platforms = ["Linux", "Mac OSX", "Windows"]
363 platforms = ["Linux", "Mac OSX", "Windows"]
354 license-files = ["LICENSE"]
364 license-files = ["LICENSE"]
355 include-package-data = false
365 include-package-data = false
356
366
357 [tool.setuptools.packages.find]
367 [tool.setuptools.packages.find]
358 exclude = ["setupext"]
368 exclude = ["setupext"]
359 namespaces = false
369 namespaces = false
360
370
361 [tool.setuptools.package-data]
371 [tool.setuptools.package-data]
362 "IPython" = ["py.typed"]
372 "IPython" = ["py.typed"]
363 "IPython.core" = ["profile/README*"]
373 "IPython.core" = ["profile/README*"]
364 "IPython.core.tests" = ["*.png", "*.jpg", "daft_extension/*.py"]
374 "IPython.core.tests" = ["*.png", "*.jpg", "daft_extension/*.py"]
365 "IPython.lib.tests" = ["*.wav"]
375 "IPython.lib.tests" = ["*.wav"]
366 "IPython.testing.plugin" = ["*.txt"]
376 "IPython.testing.plugin" = ["*.txt"]
367
377
368 [tool.setuptools.dynamic]
378 [tool.setuptools.dynamic]
369 version = {attr = "IPython.core.release.__version__"}
379 version = {attr = "IPython.core.release.__version__"}
370
380
371 [tool.coverage.run]
381 [tool.coverage.run]
372 omit = [
382 omit = [
373 # omit everything in /tmp as we run tempfile
383 # omit everything in /tmp as we run tempfile
374 "/tmp/*",
384 "/tmp/*",
375 ]
385 ]
General Comments 0
You need to be logged in to leave comments. Login now