Show More
@@ -1,437 +1,434 b'' | |||||
1 | """Tests for the object inspection functionality. |
|
1 | """Tests for the object inspection functionality. | |
2 | """ |
|
2 | """ | |
3 |
|
3 | |||
4 | # Copyright (c) IPython Development Team. |
|
4 | # Copyright (c) IPython Development Team. | |
5 | # Distributed under the terms of the Modified BSD License. |
|
5 | # Distributed under the terms of the Modified BSD License. | |
6 |
|
6 | |||
7 |
|
7 | |||
8 | from inspect import Signature, Parameter |
|
8 | from inspect import Signature, Parameter | |
9 | import os |
|
9 | import os | |
10 | import re |
|
10 | import re | |
11 | import sys |
|
11 | import sys | |
12 |
|
12 | |||
13 | import nose.tools as nt |
|
13 | import nose.tools as nt | |
14 |
|
14 | |||
15 | from .. import oinspect |
|
15 | from .. import oinspect | |
16 | from IPython.core.magic import (Magics, magics_class, line_magic, |
|
16 | from IPython.core.magic import (Magics, magics_class, line_magic, | |
17 | cell_magic, line_cell_magic, |
|
17 | cell_magic, line_cell_magic, | |
18 | register_line_magic, register_cell_magic, |
|
18 | register_line_magic, register_cell_magic, | |
19 | register_line_cell_magic) |
|
19 | register_line_cell_magic) | |
20 | from decorator import decorator |
|
20 | from decorator import decorator | |
21 | from IPython import get_ipython |
|
21 | from IPython import get_ipython | |
22 | from IPython.testing.tools import AssertPrints, AssertNotPrints |
|
22 | from IPython.testing.tools import AssertPrints, AssertNotPrints | |
23 | from IPython.utils.path import compress_user |
|
23 | from IPython.utils.path import compress_user | |
24 |
|
24 | |||
25 |
|
25 | |||
26 | #----------------------------------------------------------------------------- |
|
26 | #----------------------------------------------------------------------------- | |
27 | # Globals and constants |
|
27 | # Globals and constants | |
28 | #----------------------------------------------------------------------------- |
|
28 | #----------------------------------------------------------------------------- | |
29 |
|
29 | |||
30 | inspector = oinspect.Inspector() |
|
30 | inspector = oinspect.Inspector() | |
31 | ip = get_ipython() |
|
31 | ip = get_ipython() | |
32 |
|
32 | |||
33 | #----------------------------------------------------------------------------- |
|
33 | #----------------------------------------------------------------------------- | |
34 | # Local utilities |
|
34 | # Local utilities | |
35 | #----------------------------------------------------------------------------- |
|
35 | #----------------------------------------------------------------------------- | |
36 |
|
36 | |||
37 | # WARNING: since this test checks the line number where a function is |
|
37 | # WARNING: since this test checks the line number where a function is | |
38 | # defined, if any code is inserted above, the following line will need to be |
|
38 | # defined, if any code is inserted above, the following line will need to be | |
39 | # updated. Do NOT insert any whitespace between the next line and the function |
|
39 | # updated. Do NOT insert any whitespace between the next line and the function | |
40 | # definition below. |
|
40 | # definition below. | |
41 | THIS_LINE_NUMBER = 41 # Put here the actual number of this line |
|
41 | THIS_LINE_NUMBER = 41 # Put here the actual number of this line | |
42 |
|
42 | |||
43 | from unittest import TestCase |
|
43 | from unittest import TestCase | |
44 |
|
44 | |||
45 | class Test(TestCase): |
|
45 | class Test(TestCase): | |
46 |
|
46 | |||
47 | def test_find_source_lines(self): |
|
47 | def test_find_source_lines(self): | |
48 | self.assertEqual(oinspect.find_source_lines(Test.test_find_source_lines), |
|
48 | self.assertEqual(oinspect.find_source_lines(Test.test_find_source_lines), | |
49 | THIS_LINE_NUMBER+6) |
|
49 | THIS_LINE_NUMBER+6) | |
50 |
|
50 | |||
51 |
|
51 | |||
52 | # A couple of utilities to ensure these tests work the same from a source or a |
|
52 | # A couple of utilities to ensure these tests work the same from a source or a | |
53 | # binary install |
|
53 | # binary install | |
54 | def pyfile(fname): |
|
54 | def pyfile(fname): | |
55 | return os.path.normcase(re.sub('.py[co]$', '.py', fname)) |
|
55 | return os.path.normcase(re.sub('.py[co]$', '.py', fname)) | |
56 |
|
56 | |||
57 |
|
57 | |||
58 | def match_pyfiles(f1, f2): |
|
58 | def match_pyfiles(f1, f2): | |
59 | nt.assert_equal(pyfile(f1), pyfile(f2)) |
|
59 | nt.assert_equal(pyfile(f1), pyfile(f2)) | |
60 |
|
60 | |||
61 |
|
61 | |||
62 | def test_find_file(): |
|
62 | def test_find_file(): | |
63 | match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__)) |
|
63 | match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__)) | |
64 |
|
64 | |||
65 |
|
65 | |||
66 | def test_find_file_decorated1(): |
|
66 | def test_find_file_decorated1(): | |
67 |
|
67 | |||
68 | @decorator |
|
68 | @decorator | |
69 | def noop1(f): |
|
69 | def noop1(f): | |
70 | def wrapper(*a, **kw): |
|
70 | def wrapper(*a, **kw): | |
71 | return f(*a, **kw) |
|
71 | return f(*a, **kw) | |
72 | return wrapper |
|
72 | return wrapper | |
73 |
|
73 | |||
74 | @noop1 |
|
74 | @noop1 | |
75 | def f(x): |
|
75 | def f(x): | |
76 | "My docstring" |
|
76 | "My docstring" | |
77 |
|
77 | |||
78 | match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__)) |
|
78 | match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__)) | |
79 | nt.assert_equal(f.__doc__, "My docstring") |
|
79 | nt.assert_equal(f.__doc__, "My docstring") | |
80 |
|
80 | |||
81 |
|
81 | |||
82 | def test_find_file_decorated2(): |
|
82 | def test_find_file_decorated2(): | |
83 |
|
83 | |||
84 | @decorator |
|
84 | @decorator | |
85 | def noop2(f, *a, **kw): |
|
85 | def noop2(f, *a, **kw): | |
86 | return f(*a, **kw) |
|
86 | return f(*a, **kw) | |
87 |
|
87 | |||
88 | @noop2 |
|
88 | @noop2 | |
89 | @noop2 |
|
89 | @noop2 | |
90 | @noop2 |
|
90 | @noop2 | |
91 | def f(x): |
|
91 | def f(x): | |
92 | "My docstring 2" |
|
92 | "My docstring 2" | |
93 |
|
93 | |||
94 | match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__)) |
|
94 | match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__)) | |
95 | nt.assert_equal(f.__doc__, "My docstring 2") |
|
95 | nt.assert_equal(f.__doc__, "My docstring 2") | |
96 |
|
96 | |||
97 |
|
97 | |||
98 | def test_find_file_magic(): |
|
98 | def test_find_file_magic(): | |
99 | run = ip.find_line_magic('run') |
|
99 | run = ip.find_line_magic('run') | |
100 | nt.assert_not_equal(oinspect.find_file(run), None) |
|
100 | nt.assert_not_equal(oinspect.find_file(run), None) | |
101 |
|
101 | |||
102 |
|
102 | |||
103 | # A few generic objects we can then inspect in the tests below |
|
103 | # A few generic objects we can then inspect in the tests below | |
104 |
|
104 | |||
105 | class Call(object): |
|
105 | class Call(object): | |
106 | """This is the class docstring.""" |
|
106 | """This is the class docstring.""" | |
107 |
|
107 | |||
108 | def __init__(self, x, y=1): |
|
108 | def __init__(self, x, y=1): | |
109 | """This is the constructor docstring.""" |
|
109 | """This is the constructor docstring.""" | |
110 |
|
110 | |||
111 | def __call__(self, *a, **kw): |
|
111 | def __call__(self, *a, **kw): | |
112 | """This is the call docstring.""" |
|
112 | """This is the call docstring.""" | |
113 |
|
113 | |||
114 | def method(self, x, z=2): |
|
114 | def method(self, x, z=2): | |
115 | """Some method's docstring""" |
|
115 | """Some method's docstring""" | |
116 |
|
116 | |||
117 | class HasSignature(object): |
|
117 | class HasSignature(object): | |
118 | """This is the class docstring.""" |
|
118 | """This is the class docstring.""" | |
119 | __signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)]) |
|
119 | __signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)]) | |
120 |
|
120 | |||
121 | def __init__(self, *args): |
|
121 | def __init__(self, *args): | |
122 | """This is the init docstring""" |
|
122 | """This is the init docstring""" | |
123 |
|
123 | |||
124 |
|
124 | |||
125 | class SimpleClass(object): |
|
125 | class SimpleClass(object): | |
126 | def method(self, x, z=2): |
|
126 | def method(self, x, z=2): | |
127 | """Some method's docstring""" |
|
127 | """Some method's docstring""" | |
128 |
|
128 | |||
129 |
|
129 | |||
130 | class OldStyle: |
|
130 | class OldStyle: | |
131 | """An old-style class for testing.""" |
|
131 | """An old-style class for testing.""" | |
132 | pass |
|
132 | pass | |
133 |
|
133 | |||
134 |
|
134 | |||
135 | def f(x, y=2, *a, **kw): |
|
135 | def f(x, y=2, *a, **kw): | |
136 | """A simple function.""" |
|
136 | """A simple function.""" | |
137 |
|
137 | |||
138 |
|
138 | |||
139 | def g(y, z=3, *a, **kw): |
|
139 | def g(y, z=3, *a, **kw): | |
140 | pass # no docstring |
|
140 | pass # no docstring | |
141 |
|
141 | |||
142 |
|
142 | |||
143 | @register_line_magic |
|
143 | @register_line_magic | |
144 | def lmagic(line): |
|
144 | def lmagic(line): | |
145 | "A line magic" |
|
145 | "A line magic" | |
146 |
|
146 | |||
147 |
|
147 | |||
148 | @register_cell_magic |
|
148 | @register_cell_magic | |
149 | def cmagic(line, cell): |
|
149 | def cmagic(line, cell): | |
150 | "A cell magic" |
|
150 | "A cell magic" | |
151 |
|
151 | |||
152 |
|
152 | |||
153 | @register_line_cell_magic |
|
153 | @register_line_cell_magic | |
154 | def lcmagic(line, cell=None): |
|
154 | def lcmagic(line, cell=None): | |
155 | "A line/cell magic" |
|
155 | "A line/cell magic" | |
156 |
|
156 | |||
157 |
|
157 | |||
158 | @magics_class |
|
158 | @magics_class | |
159 | class SimpleMagics(Magics): |
|
159 | class SimpleMagics(Magics): | |
160 | @line_magic |
|
160 | @line_magic | |
161 | def Clmagic(self, cline): |
|
161 | def Clmagic(self, cline): | |
162 | "A class-based line magic" |
|
162 | "A class-based line magic" | |
163 |
|
163 | |||
164 | @cell_magic |
|
164 | @cell_magic | |
165 | def Ccmagic(self, cline, ccell): |
|
165 | def Ccmagic(self, cline, ccell): | |
166 | "A class-based cell magic" |
|
166 | "A class-based cell magic" | |
167 |
|
167 | |||
168 | @line_cell_magic |
|
168 | @line_cell_magic | |
169 | def Clcmagic(self, cline, ccell=None): |
|
169 | def Clcmagic(self, cline, ccell=None): | |
170 | "A class-based line/cell magic" |
|
170 | "A class-based line/cell magic" | |
171 |
|
171 | |||
172 |
|
172 | |||
173 | class Awkward(object): |
|
173 | class Awkward(object): | |
174 | def __getattr__(self, name): |
|
174 | def __getattr__(self, name): | |
175 | raise Exception(name) |
|
175 | raise Exception(name) | |
176 |
|
176 | |||
177 | class NoBoolCall: |
|
177 | class NoBoolCall: | |
178 | """ |
|
178 | """ | |
179 | callable with `__bool__` raising should still be inspect-able. |
|
179 | callable with `__bool__` raising should still be inspect-able. | |
180 | """ |
|
180 | """ | |
181 |
|
181 | |||
182 | def __call__(self): |
|
182 | def __call__(self): | |
183 | """does nothing""" |
|
183 | """does nothing""" | |
184 | pass |
|
184 | pass | |
185 |
|
185 | |||
186 | def __bool__(self): |
|
186 | def __bool__(self): | |
187 | """just raise NotImplemented""" |
|
187 | """just raise NotImplemented""" | |
188 | raise NotImplementedError('Must be implemented') |
|
188 | raise NotImplementedError('Must be implemented') | |
189 |
|
189 | |||
190 |
|
190 | |||
191 | class SerialLiar(object): |
|
191 | class SerialLiar(object): | |
192 | """Attribute accesses always get another copy of the same class. |
|
192 | """Attribute accesses always get another copy of the same class. | |
193 |
|
193 | |||
194 | unittest.mock.call does something similar, but it's not ideal for testing |
|
194 | unittest.mock.call does something similar, but it's not ideal for testing | |
195 | as the failure mode is to eat all your RAM. This gives up after 10k levels. |
|
195 | as the failure mode is to eat all your RAM. This gives up after 10k levels. | |
196 | """ |
|
196 | """ | |
197 | def __init__(self, max_fibbing_twig, lies_told=0): |
|
197 | def __init__(self, max_fibbing_twig, lies_told=0): | |
198 | if lies_told > 10000: |
|
198 | if lies_told > 10000: | |
199 | raise RuntimeError('Nose too long, honesty is the best policy') |
|
199 | raise RuntimeError('Nose too long, honesty is the best policy') | |
200 | self.max_fibbing_twig = max_fibbing_twig |
|
200 | self.max_fibbing_twig = max_fibbing_twig | |
201 | self.lies_told = lies_told |
|
201 | self.lies_told = lies_told | |
202 | max_fibbing_twig[0] = max(max_fibbing_twig[0], lies_told) |
|
202 | max_fibbing_twig[0] = max(max_fibbing_twig[0], lies_told) | |
203 |
|
203 | |||
204 | def __getattr__(self, item): |
|
204 | def __getattr__(self, item): | |
205 | return SerialLiar(self.max_fibbing_twig, self.lies_told + 1) |
|
205 | return SerialLiar(self.max_fibbing_twig, self.lies_told + 1) | |
206 |
|
206 | |||
207 | #----------------------------------------------------------------------------- |
|
207 | #----------------------------------------------------------------------------- | |
208 | # Tests |
|
208 | # Tests | |
209 | #----------------------------------------------------------------------------- |
|
209 | #----------------------------------------------------------------------------- | |
210 |
|
210 | |||
211 | def test_info(): |
|
211 | def test_info(): | |
212 | "Check that Inspector.info fills out various fields as expected." |
|
212 | "Check that Inspector.info fills out various fields as expected." | |
213 | i = inspector.info(Call, oname='Call') |
|
213 | i = inspector.info(Call, oname='Call') | |
214 | nt.assert_equal(i['type_name'], 'type') |
|
214 | nt.assert_equal(i['type_name'], 'type') | |
215 | expted_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'> |
|
215 | expted_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'> | |
216 | nt.assert_equal(i['base_class'], expted_class) |
|
216 | nt.assert_equal(i['base_class'], expted_class) | |
217 | nt.assert_regex(i['string_form'], "<class 'IPython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>") |
|
217 | nt.assert_regex(i['string_form'], "<class 'IPython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>") | |
218 | fname = __file__ |
|
218 | fname = __file__ | |
219 | if fname.endswith(".pyc"): |
|
219 | if fname.endswith(".pyc"): | |
220 | fname = fname[:-1] |
|
220 | fname = fname[:-1] | |
221 | # case-insensitive comparison needed on some filesystems |
|
221 | # case-insensitive comparison needed on some filesystems | |
222 | # e.g. Windows: |
|
222 | # e.g. Windows: | |
223 | nt.assert_equal(i['file'].lower(), compress_user(fname).lower()) |
|
223 | nt.assert_equal(i['file'].lower(), compress_user(fname).lower()) | |
224 | nt.assert_equal(i['definition'], None) |
|
224 | nt.assert_equal(i['definition'], None) | |
225 | nt.assert_equal(i['docstring'], Call.__doc__) |
|
225 | nt.assert_equal(i['docstring'], Call.__doc__) | |
226 | nt.assert_equal(i['source'], None) |
|
226 | nt.assert_equal(i['source'], None) | |
227 | nt.assert_true(i['isclass']) |
|
227 | nt.assert_true(i['isclass']) | |
228 | nt.assert_equal(i['init_definition'], "Call(x, y=1)") |
|
228 | nt.assert_equal(i['init_definition'], "Call(x, y=1)") | |
229 | nt.assert_equal(i['init_docstring'], Call.__init__.__doc__) |
|
229 | nt.assert_equal(i['init_docstring'], Call.__init__.__doc__) | |
230 |
|
230 | |||
231 | i = inspector.info(Call, detail_level=1) |
|
231 | i = inspector.info(Call, detail_level=1) | |
232 | nt.assert_not_equal(i['source'], None) |
|
232 | nt.assert_not_equal(i['source'], None) | |
233 | nt.assert_equal(i['docstring'], None) |
|
233 | nt.assert_equal(i['docstring'], None) | |
234 |
|
234 | |||
235 | c = Call(1) |
|
235 | c = Call(1) | |
236 | c.__doc__ = "Modified instance docstring" |
|
236 | c.__doc__ = "Modified instance docstring" | |
237 | i = inspector.info(c) |
|
237 | i = inspector.info(c) | |
238 | nt.assert_equal(i['type_name'], 'Call') |
|
238 | nt.assert_equal(i['type_name'], 'Call') | |
239 | nt.assert_equal(i['docstring'], "Modified instance docstring") |
|
239 | nt.assert_equal(i['docstring'], "Modified instance docstring") | |
240 | nt.assert_equal(i['class_docstring'], Call.__doc__) |
|
240 | nt.assert_equal(i['class_docstring'], Call.__doc__) | |
241 | nt.assert_equal(i['init_docstring'], Call.__init__.__doc__) |
|
241 | nt.assert_equal(i['init_docstring'], Call.__init__.__doc__) | |
242 | nt.assert_equal(i['call_docstring'], Call.__call__.__doc__) |
|
242 | nt.assert_equal(i['call_docstring'], Call.__call__.__doc__) | |
243 |
|
243 | |||
244 | def test_class_signature(): |
|
244 | def test_class_signature(): | |
245 | info = inspector.info(HasSignature, 'HasSignature') |
|
245 | info = inspector.info(HasSignature, 'HasSignature') | |
246 | nt.assert_equal(info['init_definition'], "HasSignature(test)") |
|
246 | nt.assert_equal(info['init_definition'], "HasSignature(test)") | |
247 | nt.assert_equal(info['init_docstring'], HasSignature.__init__.__doc__) |
|
247 | nt.assert_equal(info['init_docstring'], HasSignature.__init__.__doc__) | |
248 |
|
248 | |||
249 | def test_info_awkward(): |
|
249 | def test_info_awkward(): | |
250 | # Just test that this doesn't throw an error. |
|
250 | # Just test that this doesn't throw an error. | |
251 | inspector.info(Awkward()) |
|
251 | inspector.info(Awkward()) | |
252 |
|
252 | |||
253 | def test_bool_raise(): |
|
253 | def test_bool_raise(): | |
254 | inspector.info(NoBoolCall()) |
|
254 | inspector.info(NoBoolCall()) | |
255 |
|
255 | |||
256 | def test_info_serialliar(): |
|
256 | def test_info_serialliar(): | |
257 | fib_tracker = [0] |
|
257 | fib_tracker = [0] | |
258 | inspector.info(SerialLiar(fib_tracker)) |
|
258 | inspector.info(SerialLiar(fib_tracker)) | |
259 |
|
259 | |||
260 | # Nested attribute access should be cut off at 100 levels deep to avoid |
|
260 | # Nested attribute access should be cut off at 100 levels deep to avoid | |
261 | # infinite loops: https://github.com/ipython/ipython/issues/9122 |
|
261 | # infinite loops: https://github.com/ipython/ipython/issues/9122 | |
262 | nt.assert_less(fib_tracker[0], 9000) |
|
262 | nt.assert_less(fib_tracker[0], 9000) | |
263 |
|
263 | |||
264 | def test_calldef_none(): |
|
264 | def test_calldef_none(): | |
265 | # We should ignore __call__ for all of these. |
|
265 | # We should ignore __call__ for all of these. | |
266 | for obj in [f, SimpleClass().method, any, str.upper]: |
|
266 | for obj in [f, SimpleClass().method, any, str.upper]: | |
267 | print(obj) |
|
267 | print(obj) | |
268 | i = inspector.info(obj) |
|
268 | i = inspector.info(obj) | |
269 | nt.assert_is(i['call_def'], None) |
|
269 | nt.assert_is(i['call_def'], None) | |
270 |
|
270 | |||
271 | def f_kwarg(pos, *, kwonly): |
|
271 | def f_kwarg(pos, *, kwonly): | |
272 | pass |
|
272 | pass | |
273 |
|
273 | |||
274 | def test_definition_kwonlyargs(): |
|
274 | def test_definition_kwonlyargs(): | |
275 | i = inspector.info(f_kwarg, oname='f_kwarg') # analysis:ignore |
|
275 | i = inspector.info(f_kwarg, oname='f_kwarg') # analysis:ignore | |
276 | nt.assert_equal(i['definition'], "f_kwarg(pos, *, kwonly)") |
|
276 | nt.assert_equal(i['definition'], "f_kwarg(pos, *, kwonly)") | |
277 |
|
277 | |||
278 | def test_getdoc(): |
|
278 | def test_getdoc(): | |
279 | class A(object): |
|
279 | class A(object): | |
280 | """standard docstring""" |
|
280 | """standard docstring""" | |
281 | pass |
|
281 | pass | |
282 |
|
282 | |||
283 | class B(object): |
|
283 | class B(object): | |
284 | """standard docstring""" |
|
284 | """standard docstring""" | |
285 | def getdoc(self): |
|
285 | def getdoc(self): | |
286 | return "custom docstring" |
|
286 | return "custom docstring" | |
287 |
|
287 | |||
288 | class C(object): |
|
288 | class C(object): | |
289 | """standard docstring""" |
|
289 | """standard docstring""" | |
290 | def getdoc(self): |
|
290 | def getdoc(self): | |
291 | return None |
|
291 | return None | |
292 |
|
292 | |||
293 | a = A() |
|
293 | a = A() | |
294 | b = B() |
|
294 | b = B() | |
295 | c = C() |
|
295 | c = C() | |
296 |
|
296 | |||
297 | nt.assert_equal(oinspect.getdoc(a), "standard docstring") |
|
297 | nt.assert_equal(oinspect.getdoc(a), "standard docstring") | |
298 | nt.assert_equal(oinspect.getdoc(b), "custom docstring") |
|
298 | nt.assert_equal(oinspect.getdoc(b), "custom docstring") | |
299 | nt.assert_equal(oinspect.getdoc(c), "standard docstring") |
|
299 | nt.assert_equal(oinspect.getdoc(c), "standard docstring") | |
300 |
|
300 | |||
301 |
|
301 | |||
302 | def test_empty_property_has_no_source(): |
|
302 | def test_empty_property_has_no_source(): | |
303 | i = inspector.info(property(), detail_level=1) |
|
303 | i = inspector.info(property(), detail_level=1) | |
304 | nt.assert_is(i['source'], None) |
|
304 | nt.assert_is(i['source'], None) | |
305 |
|
305 | |||
306 |
|
306 | |||
307 | def test_property_sources(): |
|
307 | def test_property_sources(): | |
308 | import posixpath |
|
308 | import posixpath | |
309 | # A simple adder whose source and signature stays |
|
309 | # A simple adder whose source and signature stays | |
310 | # the same across Python distributions |
|
310 | # the same across Python distributions | |
311 | def simple_add(a, b): |
|
311 | def simple_add(a, b): | |
312 | "Adds two numbers" |
|
312 | "Adds two numbers" | |
313 | return a + b |
|
313 | return a + b | |
314 |
|
314 | |||
315 | class A(object): |
|
315 | class A(object): | |
316 | @property |
|
316 | @property | |
317 | def foo(self): |
|
317 | def foo(self): | |
318 | return 'bar' |
|
318 | return 'bar' | |
319 |
|
319 | |||
320 | foo = foo.setter(lambda self, v: setattr(self, 'bar', v)) |
|
320 | foo = foo.setter(lambda self, v: setattr(self, 'bar', v)) | |
321 |
|
321 | |||
322 | dname = property(posixpath.dirname) |
|
322 | dname = property(posixpath.dirname) | |
323 | adder = property(simple_add) |
|
323 | adder = property(simple_add) | |
324 |
|
324 | |||
325 | i = inspector.info(A.foo, detail_level=1) |
|
325 | i = inspector.info(A.foo, detail_level=1) | |
326 | nt.assert_in('def foo(self):', i['source']) |
|
326 | nt.assert_in('def foo(self):', i['source']) | |
327 | nt.assert_in('lambda self, v:', i['source']) |
|
327 | nt.assert_in('lambda self, v:', i['source']) | |
328 |
|
328 | |||
329 | i = inspector.info(A.dname, detail_level=1) |
|
329 | i = inspector.info(A.dname, detail_level=1) | |
330 | nt.assert_in('def dirname(p)', i['source']) |
|
330 | nt.assert_in('def dirname(p)', i['source']) | |
331 |
|
331 | |||
332 | i = inspector.info(A.adder, detail_level=1) |
|
332 | i = inspector.info(A.adder, detail_level=1) | |
333 | nt.assert_in('def simple_add(a, b)', i['source']) |
|
333 | nt.assert_in('def simple_add(a, b)', i['source']) | |
334 |
|
334 | |||
335 |
|
335 | |||
336 | def test_property_docstring_is_in_info_for_detail_level_0(): |
|
336 | def test_property_docstring_is_in_info_for_detail_level_0(): | |
337 | class A(object): |
|
337 | class A(object): | |
338 | @property |
|
338 | @property | |
339 | def foobar(self): |
|
339 | def foobar(self): | |
340 | """This is `foobar` property.""" |
|
340 | """This is `foobar` property.""" | |
341 | pass |
|
341 | pass | |
342 |
|
342 | |||
343 | ip.user_ns['a_obj'] = A() |
|
343 | ip.user_ns['a_obj'] = A() | |
344 | nt.assert_equal( |
|
344 | nt.assert_equal( | |
345 | 'This is `foobar` property.', |
|
345 | 'This is `foobar` property.', | |
346 | ip.object_inspect('a_obj.foobar', detail_level=0)['docstring']) |
|
346 | ip.object_inspect('a_obj.foobar', detail_level=0)['docstring']) | |
347 |
|
347 | |||
348 | ip.user_ns['a_cls'] = A |
|
348 | ip.user_ns['a_cls'] = A | |
349 | nt.assert_equal( |
|
349 | nt.assert_equal( | |
350 | 'This is `foobar` property.', |
|
350 | 'This is `foobar` property.', | |
351 | ip.object_inspect('a_cls.foobar', detail_level=0)['docstring']) |
|
351 | ip.object_inspect('a_cls.foobar', detail_level=0)['docstring']) | |
352 |
|
352 | |||
353 |
|
353 | |||
354 | def test_pdef(): |
|
354 | def test_pdef(): | |
355 | # See gh-1914 |
|
355 | # See gh-1914 | |
356 | def foo(): pass |
|
356 | def foo(): pass | |
357 | inspector.pdef(foo, 'foo') |
|
357 | inspector.pdef(foo, 'foo') | |
358 |
|
358 | |||
359 |
|
359 | |||
360 | def test_pinfo_nonascii(): |
|
360 | def test_pinfo_nonascii(): | |
361 | # See gh-1177 |
|
361 | # See gh-1177 | |
362 | from . import nonascii2 |
|
362 | from . import nonascii2 | |
363 | ip.user_ns['nonascii2'] = nonascii2 |
|
363 | ip.user_ns['nonascii2'] = nonascii2 | |
364 | ip._inspect('pinfo', 'nonascii2', detail_level=1) |
|
364 | ip._inspect('pinfo', 'nonascii2', detail_level=1) | |
365 |
|
365 | |||
366 |
|
366 | |||
367 | def test_pinfo_docstring_no_source(): |
|
367 | def test_pinfo_docstring_no_source(): | |
368 | """Docstring should be included with detail_level=1 if there is no source""" |
|
368 | """Docstring should be included with detail_level=1 if there is no source""" | |
369 | with AssertPrints('Docstring:'): |
|
369 | with AssertPrints('Docstring:'): | |
370 | ip._inspect('pinfo', 'str.format', detail_level=0) |
|
370 | ip._inspect('pinfo', 'str.format', detail_level=0) | |
371 | with AssertPrints('Docstring:'): |
|
371 | with AssertPrints('Docstring:'): | |
372 | ip._inspect('pinfo', 'str.format', detail_level=1) |
|
372 | ip._inspect('pinfo', 'str.format', detail_level=1) | |
373 |
|
373 | |||
374 |
|
374 | |||
375 | def test_pinfo_no_docstring_if_source(): |
|
375 | def test_pinfo_no_docstring_if_source(): | |
376 | """Docstring should not be included with detail_level=1 if source is found""" |
|
376 | """Docstring should not be included with detail_level=1 if source is found""" | |
377 | def foo(): |
|
377 | def foo(): | |
378 | """foo has a docstring""" |
|
378 | """foo has a docstring""" | |
379 |
|
379 | |||
380 | ip.user_ns['foo'] = foo |
|
380 | ip.user_ns['foo'] = foo | |
381 |
|
381 | |||
382 | with AssertPrints('Docstring:'): |
|
382 | with AssertPrints('Docstring:'): | |
383 | ip._inspect('pinfo', 'foo', detail_level=0) |
|
383 | ip._inspect('pinfo', 'foo', detail_level=0) | |
384 | with AssertPrints('Source:'): |
|
384 | with AssertPrints('Source:'): | |
385 | ip._inspect('pinfo', 'foo', detail_level=1) |
|
385 | ip._inspect('pinfo', 'foo', detail_level=1) | |
386 | with AssertNotPrints('Docstring:'): |
|
386 | with AssertNotPrints('Docstring:'): | |
387 | ip._inspect('pinfo', 'foo', detail_level=1) |
|
387 | ip._inspect('pinfo', 'foo', detail_level=1) | |
388 |
|
388 | |||
389 |
|
389 | |||
390 | def test_pinfo_docstring_if_detail_and_no_source(): |
|
390 | def test_pinfo_docstring_if_detail_and_no_source(): | |
391 | """ Docstring should be displayed if source info not available """ |
|
391 | """ Docstring should be displayed if source info not available """ | |
392 | obj_def = '''class Foo(object): |
|
392 | obj_def = '''class Foo(object): | |
393 | """ This is a docstring for Foo """ |
|
393 | """ This is a docstring for Foo """ | |
394 | def bar(self): |
|
394 | def bar(self): | |
395 | """ This is a docstring for Foo.bar """ |
|
395 | """ This is a docstring for Foo.bar """ | |
396 | pass |
|
396 | pass | |
397 | ''' |
|
397 | ''' | |
398 |
|
398 | |||
399 | ip.run_cell(obj_def) |
|
399 | ip.run_cell(obj_def) | |
400 | ip.run_cell('foo = Foo()') |
|
400 | ip.run_cell('foo = Foo()') | |
401 |
|
401 | |||
402 | with AssertNotPrints("Source:"): |
|
402 | with AssertNotPrints("Source:"): | |
403 | with AssertPrints('Docstring:'): |
|
403 | with AssertPrints('Docstring:'): | |
404 | ip._inspect('pinfo', 'foo', detail_level=0) |
|
404 | ip._inspect('pinfo', 'foo', detail_level=0) | |
405 | with AssertPrints('Docstring:'): |
|
405 | with AssertPrints('Docstring:'): | |
406 | ip._inspect('pinfo', 'foo', detail_level=1) |
|
406 | ip._inspect('pinfo', 'foo', detail_level=1) | |
407 | with AssertPrints('Docstring:'): |
|
407 | with AssertPrints('Docstring:'): | |
408 | ip._inspect('pinfo', 'foo.bar', detail_level=0) |
|
408 | ip._inspect('pinfo', 'foo.bar', detail_level=0) | |
409 |
|
409 | |||
410 | with AssertNotPrints('Docstring:'): |
|
410 | with AssertNotPrints('Docstring:'): | |
411 | with AssertPrints('Source:'): |
|
411 | with AssertPrints('Source:'): | |
412 | ip._inspect('pinfo', 'foo.bar', detail_level=1) |
|
412 | ip._inspect('pinfo', 'foo.bar', detail_level=1) | |
413 |
|
413 | |||
414 |
|
414 | |||
415 | def test_pinfo_magic(): |
|
415 | def test_pinfo_magic(): | |
416 | with AssertPrints('Docstring:'): |
|
416 | with AssertPrints('Docstring:'): | |
417 | ip._inspect('pinfo', 'lsmagic', detail_level=0) |
|
417 | ip._inspect('pinfo', 'lsmagic', detail_level=0) | |
418 |
|
418 | |||
419 | with AssertPrints('Source:'): |
|
419 | with AssertPrints('Source:'): | |
420 | ip._inspect('pinfo', 'lsmagic', detail_level=1) |
|
420 | ip._inspect('pinfo', 'lsmagic', detail_level=1) | |
421 |
|
421 | |||
422 |
|
422 | |||
423 | def test_init_colors(): |
|
423 | def test_init_colors(): | |
424 | # ensure colors are not present in signature info |
|
424 | # ensure colors are not present in signature info | |
425 | info = inspector.info(HasSignature) |
|
425 | info = inspector.info(HasSignature) | |
426 | init_def = info['init_definition'] |
|
426 | init_def = info['init_definition'] | |
427 | nt.assert_not_in('[0m', init_def) |
|
427 | nt.assert_not_in('[0m', init_def) | |
428 |
|
428 | |||
429 |
|
429 | |||
430 | def test_builtin_init(): |
|
430 | def test_builtin_init(): | |
431 | info = inspector.info(list) |
|
431 | info = inspector.info(list) | |
432 | init_def = info['init_definition'] |
|
432 | init_def = info['init_definition'] | |
433 | # Python < 3.4 can't get init definition from builtins, |
|
433 | nt.assert_is_not_none(init_def) | |
434 | # but still exercise the inspection in case of error-raising bugs. |
|
|||
435 | if sys.version_info >= (3,4): |
|
|||
436 | nt.assert_is_not_none(init_def) |
|
|||
437 |
|
434 |
@@ -1,373 +1,330 b'' | |||||
1 | """ |
|
1 | """ | |
2 | This module contains factory functions that attempt |
|
2 | This module contains factory functions that attempt | |
3 | to return Qt submodules from the various python Qt bindings. |
|
3 | to return Qt submodules from the various python Qt bindings. | |
4 |
|
4 | |||
5 | It also protects against double-importing Qt with different |
|
5 | It also protects against double-importing Qt with different | |
6 | bindings, which is unstable and likely to crash |
|
6 | bindings, which is unstable and likely to crash | |
7 |
|
7 | |||
8 | This is used primarily by qt and qt_for_kernel, and shouldn't |
|
8 | This is used primarily by qt and qt_for_kernel, and shouldn't | |
9 | be accessed directly from the outside |
|
9 | be accessed directly from the outside | |
10 | """ |
|
10 | """ | |
11 | import sys |
|
11 | import sys | |
12 | import types |
|
12 | import types | |
13 | from functools import partial |
|
13 | from functools import partial | |
14 | from importlib import import_module |
|
14 | from importlib import import_module | |
15 |
|
15 | |||
16 | from IPython.utils.version import check_version |
|
16 | from IPython.utils.version import check_version | |
17 |
|
17 | |||
18 | # Available APIs. |
|
18 | # Available APIs. | |
19 | QT_API_PYQT = 'pyqt' # Force version 2 |
|
19 | QT_API_PYQT = 'pyqt' # Force version 2 | |
20 | QT_API_PYQT5 = 'pyqt5' |
|
20 | QT_API_PYQT5 = 'pyqt5' | |
21 | QT_API_PYQTv1 = 'pyqtv1' # Force version 2 |
|
21 | QT_API_PYQTv1 = 'pyqtv1' # Force version 2 | |
22 | QT_API_PYQT_DEFAULT = 'pyqtdefault' # use system default for version 1 vs. 2 |
|
22 | QT_API_PYQT_DEFAULT = 'pyqtdefault' # use system default for version 1 vs. 2 | |
23 | QT_API_PYSIDE = 'pyside' |
|
23 | QT_API_PYSIDE = 'pyside' | |
24 | QT_API_PYSIDE2 = 'pyside2' |
|
24 | QT_API_PYSIDE2 = 'pyside2' | |
25 |
|
25 | |||
26 | api_to_module = {QT_API_PYSIDE2: 'PySide2', |
|
26 | api_to_module = {QT_API_PYSIDE2: 'PySide2', | |
27 | QT_API_PYSIDE: 'PySide', |
|
27 | QT_API_PYSIDE: 'PySide', | |
28 | QT_API_PYQT: 'PyQt4', |
|
28 | QT_API_PYQT: 'PyQt4', | |
29 | QT_API_PYQTv1: 'PyQt4', |
|
29 | QT_API_PYQTv1: 'PyQt4', | |
30 | QT_API_PYQT5: 'PyQt5', |
|
30 | QT_API_PYQT5: 'PyQt5', | |
31 | QT_API_PYQT_DEFAULT: 'PyQt4', |
|
31 | QT_API_PYQT_DEFAULT: 'PyQt4', | |
32 | } |
|
32 | } | |
33 |
|
33 | |||
34 |
|
34 | |||
35 | class ImportDenier(object): |
|
35 | class ImportDenier(object): | |
36 | """Import Hook that will guard against bad Qt imports |
|
36 | """Import Hook that will guard against bad Qt imports | |
37 | once IPython commits to a specific binding |
|
37 | once IPython commits to a specific binding | |
38 | """ |
|
38 | """ | |
39 |
|
39 | |||
40 | def __init__(self): |
|
40 | def __init__(self): | |
41 | self.__forbidden = set() |
|
41 | self.__forbidden = set() | |
42 |
|
42 | |||
43 | def forbid(self, module_name): |
|
43 | def forbid(self, module_name): | |
44 | sys.modules.pop(module_name, None) |
|
44 | sys.modules.pop(module_name, None) | |
45 | self.__forbidden.add(module_name) |
|
45 | self.__forbidden.add(module_name) | |
46 |
|
46 | |||
47 | def find_module(self, fullname, path=None): |
|
47 | def find_module(self, fullname, path=None): | |
48 | if path: |
|
48 | if path: | |
49 | return |
|
49 | return | |
50 | if fullname in self.__forbidden: |
|
50 | if fullname in self.__forbidden: | |
51 | return self |
|
51 | return self | |
52 |
|
52 | |||
53 | def load_module(self, fullname): |
|
53 | def load_module(self, fullname): | |
54 | raise ImportError(""" |
|
54 | raise ImportError(""" | |
55 | Importing %s disabled by IPython, which has |
|
55 | Importing %s disabled by IPython, which has | |
56 | already imported an Incompatible QT Binding: %s |
|
56 | already imported an Incompatible QT Binding: %s | |
57 | """ % (fullname, loaded_api())) |
|
57 | """ % (fullname, loaded_api())) | |
58 |
|
58 | |||
59 | ID = ImportDenier() |
|
59 | ID = ImportDenier() | |
60 | sys.meta_path.insert(0, ID) |
|
60 | sys.meta_path.insert(0, ID) | |
61 |
|
61 | |||
62 |
|
62 | |||
63 | def commit_api(api): |
|
63 | def commit_api(api): | |
64 | """Commit to a particular API, and trigger ImportErrors on subsequent |
|
64 | """Commit to a particular API, and trigger ImportErrors on subsequent | |
65 | dangerous imports""" |
|
65 | dangerous imports""" | |
66 |
|
66 | |||
67 | if api == QT_API_PYSIDE2: |
|
67 | if api == QT_API_PYSIDE2: | |
68 | ID.forbid('PySide') |
|
68 | ID.forbid('PySide') | |
69 | ID.forbid('PyQt4') |
|
69 | ID.forbid('PyQt4') | |
70 | ID.forbid('PyQt5') |
|
70 | ID.forbid('PyQt5') | |
71 | if api == QT_API_PYSIDE: |
|
71 | if api == QT_API_PYSIDE: | |
72 | ID.forbid('PySide2') |
|
72 | ID.forbid('PySide2') | |
73 | ID.forbid('PyQt4') |
|
73 | ID.forbid('PyQt4') | |
74 | ID.forbid('PyQt5') |
|
74 | ID.forbid('PyQt5') | |
75 | elif api == QT_API_PYQT5: |
|
75 | elif api == QT_API_PYQT5: | |
76 | ID.forbid('PySide2') |
|
76 | ID.forbid('PySide2') | |
77 | ID.forbid('PySide') |
|
77 | ID.forbid('PySide') | |
78 | ID.forbid('PyQt4') |
|
78 | ID.forbid('PyQt4') | |
79 | else: # There are three other possibilities, all representing PyQt4 |
|
79 | else: # There are three other possibilities, all representing PyQt4 | |
80 | ID.forbid('PyQt5') |
|
80 | ID.forbid('PyQt5') | |
81 | ID.forbid('PySide2') |
|
81 | ID.forbid('PySide2') | |
82 | ID.forbid('PySide') |
|
82 | ID.forbid('PySide') | |
83 |
|
83 | |||
84 |
|
84 | |||
85 | def loaded_api(): |
|
85 | def loaded_api(): | |
86 | """Return which API is loaded, if any |
|
86 | """Return which API is loaded, if any | |
87 |
|
87 | |||
88 | If this returns anything besides None, |
|
88 | If this returns anything besides None, | |
89 | importing any other Qt binding is unsafe. |
|
89 | importing any other Qt binding is unsafe. | |
90 |
|
90 | |||
91 | Returns |
|
91 | Returns | |
92 | ------- |
|
92 | ------- | |
93 | None, 'pyside2', 'pyside', 'pyqt', 'pyqt5', or 'pyqtv1' |
|
93 | None, 'pyside2', 'pyside', 'pyqt', 'pyqt5', or 'pyqtv1' | |
94 | """ |
|
94 | """ | |
95 | if 'PyQt4.QtCore' in sys.modules: |
|
95 | if 'PyQt4.QtCore' in sys.modules: | |
96 | if qtapi_version() == 2: |
|
96 | if qtapi_version() == 2: | |
97 | return QT_API_PYQT |
|
97 | return QT_API_PYQT | |
98 | else: |
|
98 | else: | |
99 | return QT_API_PYQTv1 |
|
99 | return QT_API_PYQTv1 | |
100 | elif 'PySide.QtCore' in sys.modules: |
|
100 | elif 'PySide.QtCore' in sys.modules: | |
101 | return QT_API_PYSIDE |
|
101 | return QT_API_PYSIDE | |
102 | elif 'PySide2.QtCore' in sys.modules: |
|
102 | elif 'PySide2.QtCore' in sys.modules: | |
103 | return QT_API_PYSIDE2 |
|
103 | return QT_API_PYSIDE2 | |
104 | elif 'PyQt5.QtCore' in sys.modules: |
|
104 | elif 'PyQt5.QtCore' in sys.modules: | |
105 | return QT_API_PYQT5 |
|
105 | return QT_API_PYQT5 | |
106 | return None |
|
106 | return None | |
107 |
|
107 | |||
108 |
|
108 | |||
109 | def has_binding(api): |
|
109 | def has_binding(api): | |
110 | """Safely check for PyQt4/5, PySide or PySide2, without importing submodules |
|
110 | """Safely check for PyQt4/5, PySide or PySide2, without importing submodules | |
111 |
|
111 | |||
112 | Supports Python <= 3.3 |
|
|||
113 |
|
||||
114 | Parameters |
|
|||
115 | ---------- |
|
|||
116 | api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyside2' | 'pyqtdefault'] |
|
|||
117 | Which module to check for |
|
|||
118 |
|
||||
119 | Returns |
|
|||
120 | ------- |
|
|||
121 | True if the relevant module appears to be importable |
|
|||
122 | """ |
|
|||
123 | # we can't import an incomplete pyside and pyqt4 |
|
|||
124 | # this will cause a crash in sip (#1431) |
|
|||
125 | # check for complete presence before importing |
|
|||
126 | module_name = api_to_module[api] |
|
|||
127 |
|
||||
128 | import imp |
|
|||
129 | try: |
|
|||
130 | #importing top level PyQt4/PySide module is ok... |
|
|||
131 | mod = import_module(module_name) |
|
|||
132 | #...importing submodules is not |
|
|||
133 | imp.find_module('QtCore', mod.__path__) |
|
|||
134 | imp.find_module('QtGui', mod.__path__) |
|
|||
135 | imp.find_module('QtSvg', mod.__path__) |
|
|||
136 | if api in (QT_API_PYQT5, QT_API_PYSIDE2): |
|
|||
137 | # QT5 requires QtWidgets too |
|
|||
138 | imp.find_module('QtWidgets', mod.__path__) |
|
|||
139 |
|
||||
140 | #we can also safely check PySide version |
|
|||
141 | if api == QT_API_PYSIDE: |
|
|||
142 | return check_version(mod.__version__, '1.0.3') |
|
|||
143 | else: |
|
|||
144 | return True |
|
|||
145 | except ImportError: |
|
|||
146 | return False |
|
|||
147 |
|
||||
148 | def has_binding_new(api): |
|
|||
149 | """Safely check for PyQt4/5, PySide or PySide2, without importing submodules |
|
|||
150 |
|
||||
151 | Supports Python >= 3.4 |
|
|||
152 |
|
||||
153 | Parameters |
|
112 | Parameters | |
154 | ---------- |
|
113 | ---------- | |
155 | api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyside2' | 'pyqtdefault'] |
|
114 | api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyside2' | 'pyqtdefault'] | |
156 | Which module to check for |
|
115 | Which module to check for | |
157 |
|
116 | |||
158 | Returns |
|
117 | Returns | |
159 | ------- |
|
118 | ------- | |
160 | True if the relevant module appears to be importable |
|
119 | True if the relevant module appears to be importable | |
161 | """ |
|
120 | """ | |
162 | module_name = api_to_module[api] |
|
121 | module_name = api_to_module[api] | |
163 | from importlib.util import find_spec |
|
122 | from importlib.util import find_spec | |
164 |
|
123 | |||
165 | required = ['QtCore', 'QtGui', 'QtSvg'] |
|
124 | required = ['QtCore', 'QtGui', 'QtSvg'] | |
166 | if api in (QT_API_PYQT5, QT_API_PYSIDE2): |
|
125 | if api in (QT_API_PYQT5, QT_API_PYSIDE2): | |
167 | # QT5 requires QtWidgets too |
|
126 | # QT5 requires QtWidgets too | |
168 | required.append('QtWidgets') |
|
127 | required.append('QtWidgets') | |
169 |
|
128 | |||
170 | for submod in required: |
|
129 | for submod in required: | |
171 | try: |
|
130 | try: | |
172 | spec = find_spec('%s.%s' % (module_name, submod)) |
|
131 | spec = find_spec('%s.%s' % (module_name, submod)) | |
173 | except ImportError: |
|
132 | except ImportError: | |
174 | # Package (e.g. PyQt5) not found |
|
133 | # Package (e.g. PyQt5) not found | |
175 | return False |
|
134 | return False | |
176 | else: |
|
135 | else: | |
177 | if spec is None: |
|
136 | if spec is None: | |
178 | # Submodule (e.g. PyQt5.QtCore) not found |
|
137 | # Submodule (e.g. PyQt5.QtCore) not found | |
179 | return False |
|
138 | return False | |
180 |
|
139 | |||
181 | if api == QT_API_PYSIDE: |
|
140 | if api == QT_API_PYSIDE: | |
182 | # We can also safely check PySide version |
|
141 | # We can also safely check PySide version | |
183 | import PySide |
|
142 | import PySide | |
184 | return check_version(PySide.__version__, '1.0.3') |
|
143 | return check_version(PySide.__version__, '1.0.3') | |
185 |
|
144 | |||
186 | return True |
|
145 | return True | |
187 |
|
146 | |||
188 | if sys.version_info >= (3, 4): |
|
|||
189 | has_binding = has_binding_new |
|
|||
190 |
|
147 | |||
191 | def qtapi_version(): |
|
148 | def qtapi_version(): | |
192 | """Return which QString API has been set, if any |
|
149 | """Return which QString API has been set, if any | |
193 |
|
150 | |||
194 | Returns |
|
151 | Returns | |
195 | ------- |
|
152 | ------- | |
196 | The QString API version (1 or 2), or None if not set |
|
153 | The QString API version (1 or 2), or None if not set | |
197 | """ |
|
154 | """ | |
198 | try: |
|
155 | try: | |
199 | import sip |
|
156 | import sip | |
200 | except ImportError: |
|
157 | except ImportError: | |
201 | return |
|
158 | return | |
202 | try: |
|
159 | try: | |
203 | return sip.getapi('QString') |
|
160 | return sip.getapi('QString') | |
204 | except ValueError: |
|
161 | except ValueError: | |
205 | return |
|
162 | return | |
206 |
|
163 | |||
207 |
|
164 | |||
208 | def can_import(api): |
|
165 | def can_import(api): | |
209 | """Safely query whether an API is importable, without importing it""" |
|
166 | """Safely query whether an API is importable, without importing it""" | |
210 | if not has_binding(api): |
|
167 | if not has_binding(api): | |
211 | return False |
|
168 | return False | |
212 |
|
169 | |||
213 | current = loaded_api() |
|
170 | current = loaded_api() | |
214 | if api == QT_API_PYQT_DEFAULT: |
|
171 | if api == QT_API_PYQT_DEFAULT: | |
215 | return current in [QT_API_PYQT, QT_API_PYQTv1, None] |
|
172 | return current in [QT_API_PYQT, QT_API_PYQTv1, None] | |
216 | else: |
|
173 | else: | |
217 | return current in [api, None] |
|
174 | return current in [api, None] | |
218 |
|
175 | |||
219 |
|
176 | |||
220 | def import_pyqt4(version=2): |
|
177 | def import_pyqt4(version=2): | |
221 | """ |
|
178 | """ | |
222 | Import PyQt4 |
|
179 | Import PyQt4 | |
223 |
|
180 | |||
224 | Parameters |
|
181 | Parameters | |
225 | ---------- |
|
182 | ---------- | |
226 | version : 1, 2, or None |
|
183 | version : 1, 2, or None | |
227 | Which QString/QVariant API to use. Set to None to use the system |
|
184 | Which QString/QVariant API to use. Set to None to use the system | |
228 | default |
|
185 | default | |
229 |
|
186 | |||
230 | ImportErrors rasied within this function are non-recoverable |
|
187 | ImportErrors rasied within this function are non-recoverable | |
231 | """ |
|
188 | """ | |
232 | # The new-style string API (version=2) automatically |
|
189 | # The new-style string API (version=2) automatically | |
233 | # converts QStrings to Unicode Python strings. Also, automatically unpacks |
|
190 | # converts QStrings to Unicode Python strings. Also, automatically unpacks | |
234 | # QVariants to their underlying objects. |
|
191 | # QVariants to their underlying objects. | |
235 | import sip |
|
192 | import sip | |
236 |
|
193 | |||
237 | if version is not None: |
|
194 | if version is not None: | |
238 | sip.setapi('QString', version) |
|
195 | sip.setapi('QString', version) | |
239 | sip.setapi('QVariant', version) |
|
196 | sip.setapi('QVariant', version) | |
240 |
|
197 | |||
241 | from PyQt4 import QtGui, QtCore, QtSvg |
|
198 | from PyQt4 import QtGui, QtCore, QtSvg | |
242 |
|
199 | |||
243 | if not check_version(QtCore.PYQT_VERSION_STR, '4.7'): |
|
200 | if not check_version(QtCore.PYQT_VERSION_STR, '4.7'): | |
244 | raise ImportError("IPython requires PyQt4 >= 4.7, found %s" % |
|
201 | raise ImportError("IPython requires PyQt4 >= 4.7, found %s" % | |
245 | QtCore.PYQT_VERSION_STR) |
|
202 | QtCore.PYQT_VERSION_STR) | |
246 |
|
203 | |||
247 | # Alias PyQt-specific functions for PySide compatibility. |
|
204 | # Alias PyQt-specific functions for PySide compatibility. | |
248 | QtCore.Signal = QtCore.pyqtSignal |
|
205 | QtCore.Signal = QtCore.pyqtSignal | |
249 | QtCore.Slot = QtCore.pyqtSlot |
|
206 | QtCore.Slot = QtCore.pyqtSlot | |
250 |
|
207 | |||
251 | # query for the API version (in case version == None) |
|
208 | # query for the API version (in case version == None) | |
252 | version = sip.getapi('QString') |
|
209 | version = sip.getapi('QString') | |
253 | api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT |
|
210 | api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT | |
254 | return QtCore, QtGui, QtSvg, api |
|
211 | return QtCore, QtGui, QtSvg, api | |
255 |
|
212 | |||
256 |
|
213 | |||
257 | def import_pyqt5(): |
|
214 | def import_pyqt5(): | |
258 | """ |
|
215 | """ | |
259 | Import PyQt5 |
|
216 | Import PyQt5 | |
260 |
|
217 | |||
261 | ImportErrors rasied within this function are non-recoverable |
|
218 | ImportErrors rasied within this function are non-recoverable | |
262 | """ |
|
219 | """ | |
263 | import sip |
|
220 | import sip | |
264 |
|
221 | |||
265 | from PyQt5 import QtCore, QtSvg, QtWidgets, QtGui |
|
222 | from PyQt5 import QtCore, QtSvg, QtWidgets, QtGui | |
266 |
|
223 | |||
267 | # Alias PyQt-specific functions for PySide compatibility. |
|
224 | # Alias PyQt-specific functions for PySide compatibility. | |
268 | QtCore.Signal = QtCore.pyqtSignal |
|
225 | QtCore.Signal = QtCore.pyqtSignal | |
269 | QtCore.Slot = QtCore.pyqtSlot |
|
226 | QtCore.Slot = QtCore.pyqtSlot | |
270 |
|
227 | |||
271 | # Join QtGui and QtWidgets for Qt4 compatibility. |
|
228 | # Join QtGui and QtWidgets for Qt4 compatibility. | |
272 | QtGuiCompat = types.ModuleType('QtGuiCompat') |
|
229 | QtGuiCompat = types.ModuleType('QtGuiCompat') | |
273 | QtGuiCompat.__dict__.update(QtGui.__dict__) |
|
230 | QtGuiCompat.__dict__.update(QtGui.__dict__) | |
274 | QtGuiCompat.__dict__.update(QtWidgets.__dict__) |
|
231 | QtGuiCompat.__dict__.update(QtWidgets.__dict__) | |
275 |
|
232 | |||
276 | api = QT_API_PYQT5 |
|
233 | api = QT_API_PYQT5 | |
277 | return QtCore, QtGuiCompat, QtSvg, api |
|
234 | return QtCore, QtGuiCompat, QtSvg, api | |
278 |
|
235 | |||
279 |
|
236 | |||
280 | def import_pyside(): |
|
237 | def import_pyside(): | |
281 | """ |
|
238 | """ | |
282 | Import PySide |
|
239 | Import PySide | |
283 |
|
240 | |||
284 | ImportErrors raised within this function are non-recoverable |
|
241 | ImportErrors raised within this function are non-recoverable | |
285 | """ |
|
242 | """ | |
286 | from PySide import QtGui, QtCore, QtSvg |
|
243 | from PySide import QtGui, QtCore, QtSvg | |
287 | return QtCore, QtGui, QtSvg, QT_API_PYSIDE |
|
244 | return QtCore, QtGui, QtSvg, QT_API_PYSIDE | |
288 |
|
245 | |||
289 | def import_pyside2(): |
|
246 | def import_pyside2(): | |
290 | """ |
|
247 | """ | |
291 | Import PySide2 |
|
248 | Import PySide2 | |
292 |
|
249 | |||
293 | ImportErrors raised within this function are non-recoverable |
|
250 | ImportErrors raised within this function are non-recoverable | |
294 | """ |
|
251 | """ | |
295 | from PySide2 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport |
|
252 | from PySide2 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport | |
296 |
|
253 | |||
297 | # Join QtGui and QtWidgets for Qt4 compatibility. |
|
254 | # Join QtGui and QtWidgets for Qt4 compatibility. | |
298 | QtGuiCompat = types.ModuleType('QtGuiCompat') |
|
255 | QtGuiCompat = types.ModuleType('QtGuiCompat') | |
299 | QtGuiCompat.__dict__.update(QtGui.__dict__) |
|
256 | QtGuiCompat.__dict__.update(QtGui.__dict__) | |
300 | QtGuiCompat.__dict__.update(QtWidgets.__dict__) |
|
257 | QtGuiCompat.__dict__.update(QtWidgets.__dict__) | |
301 | QtGuiCompat.__dict__.update(QtPrintSupport.__dict__) |
|
258 | QtGuiCompat.__dict__.update(QtPrintSupport.__dict__) | |
302 |
|
259 | |||
303 | return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE2 |
|
260 | return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE2 | |
304 |
|
261 | |||
305 |
|
262 | |||
306 | def load_qt(api_options): |
|
263 | def load_qt(api_options): | |
307 | """ |
|
264 | """ | |
308 | Attempt to import Qt, given a preference list |
|
265 | Attempt to import Qt, given a preference list | |
309 | of permissible bindings |
|
266 | of permissible bindings | |
310 |
|
267 | |||
311 | It is safe to call this function multiple times. |
|
268 | It is safe to call this function multiple times. | |
312 |
|
269 | |||
313 | Parameters |
|
270 | Parameters | |
314 | ---------- |
|
271 | ---------- | |
315 | api_options: List of strings |
|
272 | api_options: List of strings | |
316 | The order of APIs to try. Valid items are 'pyside', 'pyside2', |
|
273 | The order of APIs to try. Valid items are 'pyside', 'pyside2', | |
317 | 'pyqt', 'pyqt5', 'pyqtv1' and 'pyqtdefault' |
|
274 | 'pyqt', 'pyqt5', 'pyqtv1' and 'pyqtdefault' | |
318 |
|
275 | |||
319 | Returns |
|
276 | Returns | |
320 | ------- |
|
277 | ------- | |
321 |
|
278 | |||
322 | A tuple of QtCore, QtGui, QtSvg, QT_API |
|
279 | A tuple of QtCore, QtGui, QtSvg, QT_API | |
323 | The first three are the Qt modules. The last is the |
|
280 | The first three are the Qt modules. The last is the | |
324 | string indicating which module was loaded. |
|
281 | string indicating which module was loaded. | |
325 |
|
282 | |||
326 | Raises |
|
283 | Raises | |
327 | ------ |
|
284 | ------ | |
328 | ImportError, if it isn't possible to import any requested |
|
285 | ImportError, if it isn't possible to import any requested | |
329 | bindings (either becaues they aren't installed, or because |
|
286 | bindings (either becaues they aren't installed, or because | |
330 | an incompatible library has already been installed) |
|
287 | an incompatible library has already been installed) | |
331 | """ |
|
288 | """ | |
332 | loaders = { |
|
289 | loaders = { | |
333 | QT_API_PYSIDE2: import_pyside2, |
|
290 | QT_API_PYSIDE2: import_pyside2, | |
334 | QT_API_PYSIDE: import_pyside, |
|
291 | QT_API_PYSIDE: import_pyside, | |
335 | QT_API_PYQT: import_pyqt4, |
|
292 | QT_API_PYQT: import_pyqt4, | |
336 | QT_API_PYQT5: import_pyqt5, |
|
293 | QT_API_PYQT5: import_pyqt5, | |
337 | QT_API_PYQTv1: partial(import_pyqt4, version=1), |
|
294 | QT_API_PYQTv1: partial(import_pyqt4, version=1), | |
338 | QT_API_PYQT_DEFAULT: partial(import_pyqt4, version=None) |
|
295 | QT_API_PYQT_DEFAULT: partial(import_pyqt4, version=None) | |
339 | } |
|
296 | } | |
340 |
|
297 | |||
341 | for api in api_options: |
|
298 | for api in api_options: | |
342 |
|
299 | |||
343 | if api not in loaders: |
|
300 | if api not in loaders: | |
344 | raise RuntimeError( |
|
301 | raise RuntimeError( | |
345 | "Invalid Qt API %r, valid values are: %s" % |
|
302 | "Invalid Qt API %r, valid values are: %s" % | |
346 | (api, ", ".join(["%r" % k for k in loaders.keys()]))) |
|
303 | (api, ", ".join(["%r" % k for k in loaders.keys()]))) | |
347 |
|
304 | |||
348 | if not can_import(api): |
|
305 | if not can_import(api): | |
349 | continue |
|
306 | continue | |
350 |
|
307 | |||
351 | #cannot safely recover from an ImportError during this |
|
308 | #cannot safely recover from an ImportError during this | |
352 | result = loaders[api]() |
|
309 | result = loaders[api]() | |
353 | api = result[-1] # changed if api = QT_API_PYQT_DEFAULT |
|
310 | api = result[-1] # changed if api = QT_API_PYQT_DEFAULT | |
354 | commit_api(api) |
|
311 | commit_api(api) | |
355 | return result |
|
312 | return result | |
356 | else: |
|
313 | else: | |
357 | raise ImportError(""" |
|
314 | raise ImportError(""" | |
358 | Could not load requested Qt binding. Please ensure that |
|
315 | Could not load requested Qt binding. Please ensure that | |
359 | PyQt4 >= 4.7, PyQt5, PySide >= 1.0.3 or PySide2 is available, |
|
316 | PyQt4 >= 4.7, PyQt5, PySide >= 1.0.3 or PySide2 is available, | |
360 | and only one is imported per session. |
|
317 | and only one is imported per session. | |
361 |
|
318 | |||
362 | Currently-imported Qt library: %r |
|
319 | Currently-imported Qt library: %r | |
363 | PyQt4 available (requires QtCore, QtGui, QtSvg): %s |
|
320 | PyQt4 available (requires QtCore, QtGui, QtSvg): %s | |
364 | PyQt5 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s |
|
321 | PyQt5 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s | |
365 | PySide >= 1.0.3 installed: %s |
|
322 | PySide >= 1.0.3 installed: %s | |
366 | PySide2 installed: %s |
|
323 | PySide2 installed: %s | |
367 | Tried to load: %r |
|
324 | Tried to load: %r | |
368 | """ % (loaded_api(), |
|
325 | """ % (loaded_api(), | |
369 | has_binding(QT_API_PYQT), |
|
326 | has_binding(QT_API_PYQT), | |
370 | has_binding(QT_API_PYQT5), |
|
327 | has_binding(QT_API_PYQT5), | |
371 | has_binding(QT_API_PYSIDE), |
|
328 | has_binding(QT_API_PYSIDE), | |
372 | has_binding(QT_API_PYSIDE2), |
|
329 | has_binding(QT_API_PYSIDE2), | |
373 | api_options)) |
|
330 | api_options)) |
@@ -1,217 +1,213 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """Tests for IPython.utils.text""" |
|
2 | """Tests for IPython.utils.text""" | |
3 |
|
3 | |||
4 | #----------------------------------------------------------------------------- |
|
4 | #----------------------------------------------------------------------------- | |
5 | # Copyright (C) 2011 The IPython Development Team |
|
5 | # Copyright (C) 2011 The IPython Development Team | |
6 | # |
|
6 | # | |
7 | # Distributed under the terms of the BSD License. The full license is in |
|
7 | # Distributed under the terms of the BSD License. The full license is in | |
8 | # the file COPYING, distributed as part of this software. |
|
8 | # the file COPYING, distributed as part of this software. | |
9 | #----------------------------------------------------------------------------- |
|
9 | #----------------------------------------------------------------------------- | |
10 |
|
10 | |||
11 | #----------------------------------------------------------------------------- |
|
11 | #----------------------------------------------------------------------------- | |
12 | # Imports |
|
12 | # Imports | |
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 |
|
14 | |||
15 | import os |
|
15 | import os | |
16 | import math |
|
16 | import math | |
17 | import random |
|
17 | import random | |
18 | import sys |
|
18 | import sys | |
19 |
|
19 | |||
20 | import nose.tools as nt |
|
20 | import nose.tools as nt | |
21 | from pathlib import Path |
|
21 | from pathlib import Path | |
22 |
|
22 | |||
23 | from IPython.utils import text |
|
23 | from IPython.utils import text | |
24 |
|
24 | |||
25 | #----------------------------------------------------------------------------- |
|
25 | #----------------------------------------------------------------------------- | |
26 | # Globals |
|
26 | # Globals | |
27 | #----------------------------------------------------------------------------- |
|
27 | #----------------------------------------------------------------------------- | |
28 |
|
28 | |||
29 | def test_columnize(): |
|
29 | def test_columnize(): | |
30 | """Basic columnize tests.""" |
|
30 | """Basic columnize tests.""" | |
31 | size = 5 |
|
31 | size = 5 | |
32 | items = [l*size for l in 'abcd'] |
|
32 | items = [l*size for l in 'abcd'] | |
33 |
|
33 | |||
34 | out = text.columnize(items, displaywidth=80) |
|
34 | out = text.columnize(items, displaywidth=80) | |
35 | nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n') |
|
35 | nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n') | |
36 | out = text.columnize(items, displaywidth=25) |
|
36 | out = text.columnize(items, displaywidth=25) | |
37 | nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n') |
|
37 | nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n') | |
38 | out = text.columnize(items, displaywidth=12) |
|
38 | out = text.columnize(items, displaywidth=12) | |
39 | nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n') |
|
39 | nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n') | |
40 | out = text.columnize(items, displaywidth=10) |
|
40 | out = text.columnize(items, displaywidth=10) | |
41 | nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n') |
|
41 | nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n') | |
42 |
|
42 | |||
43 | out = text.columnize(items, row_first=True, displaywidth=80) |
|
43 | out = text.columnize(items, row_first=True, displaywidth=80) | |
44 | nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n') |
|
44 | nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n') | |
45 | out = text.columnize(items, row_first=True, displaywidth=25) |
|
45 | out = text.columnize(items, row_first=True, displaywidth=25) | |
46 | nt.assert_equal(out, 'aaaaa bbbbb\nccccc ddddd\n') |
|
46 | nt.assert_equal(out, 'aaaaa bbbbb\nccccc ddddd\n') | |
47 | out = text.columnize(items, row_first=True, displaywidth=12) |
|
47 | out = text.columnize(items, row_first=True, displaywidth=12) | |
48 | nt.assert_equal(out, 'aaaaa bbbbb\nccccc ddddd\n') |
|
48 | nt.assert_equal(out, 'aaaaa bbbbb\nccccc ddddd\n') | |
49 | out = text.columnize(items, row_first=True, displaywidth=10) |
|
49 | out = text.columnize(items, row_first=True, displaywidth=10) | |
50 | nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n') |
|
50 | nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n') | |
51 |
|
51 | |||
52 | out = text.columnize(items, displaywidth=40, spread=True) |
|
52 | out = text.columnize(items, displaywidth=40, spread=True) | |
53 | nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n') |
|
53 | nt.assert_equal(out, 'aaaaa bbbbb ccccc ddddd\n') | |
54 | out = text.columnize(items, displaywidth=20, spread=True) |
|
54 | out = text.columnize(items, displaywidth=20, spread=True) | |
55 | nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n') |
|
55 | nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n') | |
56 | out = text.columnize(items, displaywidth=12, spread=True) |
|
56 | out = text.columnize(items, displaywidth=12, spread=True) | |
57 | nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n') |
|
57 | nt.assert_equal(out, 'aaaaa ccccc\nbbbbb ddddd\n') | |
58 | out = text.columnize(items, displaywidth=10, spread=True) |
|
58 | out = text.columnize(items, displaywidth=10, spread=True) | |
59 | nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n') |
|
59 | nt.assert_equal(out, 'aaaaa\nbbbbb\nccccc\nddddd\n') | |
60 |
|
60 | |||
61 |
|
61 | |||
62 | def test_columnize_random(): |
|
62 | def test_columnize_random(): | |
63 | """Test with random input to hopefully catch edge case """ |
|
63 | """Test with random input to hopefully catch edge case """ | |
64 | for row_first in [True, False]: |
|
64 | for row_first in [True, False]: | |
65 | for nitems in [random.randint(2,70) for i in range(2,20)]: |
|
65 | for nitems in [random.randint(2,70) for i in range(2,20)]: | |
66 | displaywidth = random.randint(20,200) |
|
66 | displaywidth = random.randint(20,200) | |
67 | rand_len = [random.randint(2,displaywidth) for i in range(nitems)] |
|
67 | rand_len = [random.randint(2,displaywidth) for i in range(nitems)] | |
68 | items = ['x'*l for l in rand_len] |
|
68 | items = ['x'*l for l in rand_len] | |
69 | out = text.columnize(items, row_first=row_first, displaywidth=displaywidth) |
|
69 | out = text.columnize(items, row_first=row_first, displaywidth=displaywidth) | |
70 | longer_line = max([len(x) for x in out.split('\n')]) |
|
70 | longer_line = max([len(x) for x in out.split('\n')]) | |
71 | longer_element = max(rand_len) |
|
71 | longer_element = max(rand_len) | |
72 | if longer_line > displaywidth: |
|
72 | if longer_line > displaywidth: | |
73 | print("Columnize displayed something lager than displaywidth : %s " % longer_line) |
|
73 | print("Columnize displayed something lager than displaywidth : %s " % longer_line) | |
74 | print("longer element : %s " % longer_element) |
|
74 | print("longer element : %s " % longer_element) | |
75 | print("displaywidth : %s " % displaywidth) |
|
75 | print("displaywidth : %s " % displaywidth) | |
76 | print("number of element : %s " % nitems) |
|
76 | print("number of element : %s " % nitems) | |
77 | print("size of each element :\n %s" % rand_len) |
|
77 | print("size of each element :\n %s" % rand_len) | |
78 | assert False, "row_first={0}".format(row_first) |
|
78 | assert False, "row_first={0}".format(row_first) | |
79 |
|
79 | |||
80 | def test_columnize_medium(): |
|
80 | def test_columnize_medium(): | |
81 | """Test with inputs than shouldn't be wider than 80""" |
|
81 | """Test with inputs than shouldn't be wider than 80""" | |
82 | size = 40 |
|
82 | size = 40 | |
83 | items = [l*size for l in 'abc'] |
|
83 | items = [l*size for l in 'abc'] | |
84 | for row_first in [True, False]: |
|
84 | for row_first in [True, False]: | |
85 | out = text.columnize(items, row_first=row_first, displaywidth=80) |
|
85 | out = text.columnize(items, row_first=row_first, displaywidth=80) | |
86 | nt.assert_equal(out, '\n'.join(items+['']), "row_first={0}".format(row_first)) |
|
86 | nt.assert_equal(out, '\n'.join(items+['']), "row_first={0}".format(row_first)) | |
87 |
|
87 | |||
88 | def test_columnize_long(): |
|
88 | def test_columnize_long(): | |
89 | """Test columnize with inputs longer than the display window""" |
|
89 | """Test columnize with inputs longer than the display window""" | |
90 | size = 11 |
|
90 | size = 11 | |
91 | items = [l*size for l in 'abc'] |
|
91 | items = [l*size for l in 'abc'] | |
92 | for row_first in [True, False]: |
|
92 | for row_first in [True, False]: | |
93 | out = text.columnize(items, row_first=row_first, displaywidth=size-1) |
|
93 | out = text.columnize(items, row_first=row_first, displaywidth=size-1) | |
94 | nt.assert_equal(out, '\n'.join(items+['']), "row_first={0}".format(row_first)) |
|
94 | nt.assert_equal(out, '\n'.join(items+['']), "row_first={0}".format(row_first)) | |
95 |
|
95 | |||
96 | def eval_formatter_check(f): |
|
96 | def eval_formatter_check(f): | |
97 | ns = dict(n=12, pi=math.pi, stuff='hello there', os=os, u=u"cafΓ©", b="cafΓ©") |
|
97 | ns = dict(n=12, pi=math.pi, stuff='hello there', os=os, u=u"cafΓ©", b="cafΓ©") | |
98 | s = f.format("{n} {n//4} {stuff.split()[0]}", **ns) |
|
98 | s = f.format("{n} {n//4} {stuff.split()[0]}", **ns) | |
99 | nt.assert_equal(s, "12 3 hello") |
|
99 | nt.assert_equal(s, "12 3 hello") | |
100 | s = f.format(' '.join(['{n//%i}'%i for i in range(1,8)]), **ns) |
|
100 | s = f.format(' '.join(['{n//%i}'%i for i in range(1,8)]), **ns) | |
101 | nt.assert_equal(s, "12 6 4 3 2 2 1") |
|
101 | nt.assert_equal(s, "12 6 4 3 2 2 1") | |
102 | s = f.format('{[n//i for i in range(1,8)]}', **ns) |
|
102 | s = f.format('{[n//i for i in range(1,8)]}', **ns) | |
103 | nt.assert_equal(s, "[12, 6, 4, 3, 2, 2, 1]") |
|
103 | nt.assert_equal(s, "[12, 6, 4, 3, 2, 2, 1]") | |
104 | s = f.format("{stuff!s}", **ns) |
|
104 | s = f.format("{stuff!s}", **ns) | |
105 | nt.assert_equal(s, ns['stuff']) |
|
105 | nt.assert_equal(s, ns['stuff']) | |
106 | s = f.format("{stuff!r}", **ns) |
|
106 | s = f.format("{stuff!r}", **ns) | |
107 | nt.assert_equal(s, repr(ns['stuff'])) |
|
107 | nt.assert_equal(s, repr(ns['stuff'])) | |
108 |
|
108 | |||
109 | # Check with unicode: |
|
109 | # Check with unicode: | |
110 | s = f.format("{u}", **ns) |
|
110 | s = f.format("{u}", **ns) | |
111 | nt.assert_equal(s, ns['u']) |
|
111 | nt.assert_equal(s, ns['u']) | |
112 | # This decodes in a platform dependent manner, but it shouldn't error out |
|
112 | # This decodes in a platform dependent manner, but it shouldn't error out | |
113 | s = f.format("{b}", **ns) |
|
113 | s = f.format("{b}", **ns) | |
114 |
|
114 | |||
115 | nt.assert_raises(NameError, f.format, '{dne}', **ns) |
|
115 | nt.assert_raises(NameError, f.format, '{dne}', **ns) | |
116 |
|
116 | |||
117 | def eval_formatter_slicing_check(f): |
|
117 | def eval_formatter_slicing_check(f): | |
118 | ns = dict(n=12, pi=math.pi, stuff='hello there', os=os) |
|
118 | ns = dict(n=12, pi=math.pi, stuff='hello there', os=os) | |
119 | s = f.format(" {stuff.split()[:]} ", **ns) |
|
119 | s = f.format(" {stuff.split()[:]} ", **ns) | |
120 | nt.assert_equal(s, " ['hello', 'there'] ") |
|
120 | nt.assert_equal(s, " ['hello', 'there'] ") | |
121 | s = f.format(" {stuff.split()[::-1]} ", **ns) |
|
121 | s = f.format(" {stuff.split()[::-1]} ", **ns) | |
122 | nt.assert_equal(s, " ['there', 'hello'] ") |
|
122 | nt.assert_equal(s, " ['there', 'hello'] ") | |
123 | s = f.format("{stuff[::2]}", **ns) |
|
123 | s = f.format("{stuff[::2]}", **ns) | |
124 | nt.assert_equal(s, ns['stuff'][::2]) |
|
124 | nt.assert_equal(s, ns['stuff'][::2]) | |
125 |
|
125 | |||
126 | nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns) |
|
126 | nt.assert_raises(SyntaxError, f.format, "{n:x}", **ns) | |
127 |
|
127 | |||
128 | def eval_formatter_no_slicing_check(f): |
|
128 | def eval_formatter_no_slicing_check(f): | |
129 | ns = dict(n=12, pi=math.pi, stuff='hello there', os=os) |
|
129 | ns = dict(n=12, pi=math.pi, stuff='hello there', os=os) | |
130 |
|
130 | |||
131 | s = f.format('{n:x} {pi**2:+f}', **ns) |
|
131 | s = f.format('{n:x} {pi**2:+f}', **ns) | |
132 | nt.assert_equal(s, "c +9.869604") |
|
132 | nt.assert_equal(s, "c +9.869604") | |
133 |
|
133 | |||
134 | s = f.format('{stuff[slice(1,4)]}', **ns) |
|
134 | s = f.format('{stuff[slice(1,4)]}', **ns) | |
135 | nt.assert_equal(s, 'ell') |
|
135 | nt.assert_equal(s, 'ell') | |
136 |
|
136 | |||
137 | if sys.version_info >= (3, 4): |
|
137 | s = f.format("{a[:]}", a=[1, 2]) | |
138 | # String formatting has changed in Python 3.4, so this now works. |
|
138 | nt.assert_equal(s, "[1, 2]") | |
139 | s = f.format("{a[:]}", a=[1, 2]) |
|
|||
140 | nt.assert_equal(s, "[1, 2]") |
|
|||
141 | else: |
|
|||
142 | nt.assert_raises(SyntaxError, f.format, "{a[:]}") |
|
|||
143 |
|
139 | |||
144 | def test_eval_formatter(): |
|
140 | def test_eval_formatter(): | |
145 | f = text.EvalFormatter() |
|
141 | f = text.EvalFormatter() | |
146 | eval_formatter_check(f) |
|
142 | eval_formatter_check(f) | |
147 | eval_formatter_no_slicing_check(f) |
|
143 | eval_formatter_no_slicing_check(f) | |
148 |
|
144 | |||
149 | def test_full_eval_formatter(): |
|
145 | def test_full_eval_formatter(): | |
150 | f = text.FullEvalFormatter() |
|
146 | f = text.FullEvalFormatter() | |
151 | eval_formatter_check(f) |
|
147 | eval_formatter_check(f) | |
152 | eval_formatter_slicing_check(f) |
|
148 | eval_formatter_slicing_check(f) | |
153 |
|
149 | |||
154 | def test_dollar_formatter(): |
|
150 | def test_dollar_formatter(): | |
155 | f = text.DollarFormatter() |
|
151 | f = text.DollarFormatter() | |
156 | eval_formatter_check(f) |
|
152 | eval_formatter_check(f) | |
157 | eval_formatter_slicing_check(f) |
|
153 | eval_formatter_slicing_check(f) | |
158 |
|
154 | |||
159 | ns = dict(n=12, pi=math.pi, stuff='hello there', os=os) |
|
155 | ns = dict(n=12, pi=math.pi, stuff='hello there', os=os) | |
160 | s = f.format("$n", **ns) |
|
156 | s = f.format("$n", **ns) | |
161 | nt.assert_equal(s, "12") |
|
157 | nt.assert_equal(s, "12") | |
162 | s = f.format("$n.real", **ns) |
|
158 | s = f.format("$n.real", **ns) | |
163 | nt.assert_equal(s, "12") |
|
159 | nt.assert_equal(s, "12") | |
164 | s = f.format("$n/{stuff[:5]}", **ns) |
|
160 | s = f.format("$n/{stuff[:5]}", **ns) | |
165 | nt.assert_equal(s, "12/hello") |
|
161 | nt.assert_equal(s, "12/hello") | |
166 | s = f.format("$n $$HOME", **ns) |
|
162 | s = f.format("$n $$HOME", **ns) | |
167 | nt.assert_equal(s, "12 $HOME") |
|
163 | nt.assert_equal(s, "12 $HOME") | |
168 | s = f.format("${foo}", foo="HOME") |
|
164 | s = f.format("${foo}", foo="HOME") | |
169 | nt.assert_equal(s, "$HOME") |
|
165 | nt.assert_equal(s, "$HOME") | |
170 |
|
166 | |||
171 |
|
167 | |||
172 | def test_long_substr(): |
|
168 | def test_long_substr(): | |
173 | data = ['hi'] |
|
169 | data = ['hi'] | |
174 | nt.assert_equal(text.long_substr(data), 'hi') |
|
170 | nt.assert_equal(text.long_substr(data), 'hi') | |
175 |
|
171 | |||
176 |
|
172 | |||
177 | def test_long_substr2(): |
|
173 | def test_long_substr2(): | |
178 | data = ['abc', 'abd', 'abf', 'ab'] |
|
174 | data = ['abc', 'abd', 'abf', 'ab'] | |
179 | nt.assert_equal(text.long_substr(data), 'ab') |
|
175 | nt.assert_equal(text.long_substr(data), 'ab') | |
180 |
|
176 | |||
181 | def test_long_substr_empty(): |
|
177 | def test_long_substr_empty(): | |
182 | data = [] |
|
178 | data = [] | |
183 | nt.assert_equal(text.long_substr(data), '') |
|
179 | nt.assert_equal(text.long_substr(data), '') | |
184 |
|
180 | |||
185 | def test_strip_email(): |
|
181 | def test_strip_email(): | |
186 | src = """\ |
|
182 | src = """\ | |
187 | >> >>> def f(x): |
|
183 | >> >>> def f(x): | |
188 | >> ... return x+1 |
|
184 | >> ... return x+1 | |
189 | >> ... |
|
185 | >> ... | |
190 | >> >>> zz = f(2.5)""" |
|
186 | >> >>> zz = f(2.5)""" | |
191 | cln = """\ |
|
187 | cln = """\ | |
192 | >>> def f(x): |
|
188 | >>> def f(x): | |
193 | ... return x+1 |
|
189 | ... return x+1 | |
194 | ... |
|
190 | ... | |
195 | >>> zz = f(2.5)""" |
|
191 | >>> zz = f(2.5)""" | |
196 | nt.assert_equal(text.strip_email_quotes(src), cln) |
|
192 | nt.assert_equal(text.strip_email_quotes(src), cln) | |
197 |
|
193 | |||
198 |
|
194 | |||
199 | def test_strip_email2(): |
|
195 | def test_strip_email2(): | |
200 | src = '> > > list()' |
|
196 | src = '> > > list()' | |
201 | cln = 'list()' |
|
197 | cln = 'list()' | |
202 | nt.assert_equal(text.strip_email_quotes(src), cln) |
|
198 | nt.assert_equal(text.strip_email_quotes(src), cln) | |
203 |
|
199 | |||
204 | def test_LSString(): |
|
200 | def test_LSString(): | |
205 | lss = text.LSString("abc\ndef") |
|
201 | lss = text.LSString("abc\ndef") | |
206 | nt.assert_equal(lss.l, ['abc', 'def']) |
|
202 | nt.assert_equal(lss.l, ['abc', 'def']) | |
207 | nt.assert_equal(lss.s, 'abc def') |
|
203 | nt.assert_equal(lss.s, 'abc def') | |
208 | lss = text.LSString(os.getcwd()) |
|
204 | lss = text.LSString(os.getcwd()) | |
209 | nt.assert_is_instance(lss.p[0], Path) |
|
205 | nt.assert_is_instance(lss.p[0], Path) | |
210 |
|
206 | |||
211 | def test_SList(): |
|
207 | def test_SList(): | |
212 | sl = text.SList(['a 11', 'b 1', 'a 2']) |
|
208 | sl = text.SList(['a 11', 'b 1', 'a 2']) | |
213 | nt.assert_equal(sl.n, 'a 11\nb 1\na 2') |
|
209 | nt.assert_equal(sl.n, 'a 11\nb 1\na 2') | |
214 | nt.assert_equal(sl.s, 'a 11 b 1 a 2') |
|
210 | nt.assert_equal(sl.s, 'a 11 b 1 a 2') | |
215 | nt.assert_equal(sl.grep(lambda x: x.startswith('a')), text.SList(['a 11', 'a 2'])) |
|
211 | nt.assert_equal(sl.grep(lambda x: x.startswith('a')), text.SList(['a 11', 'a 2'])) | |
216 | nt.assert_equal(sl.fields(0), text.SList(['a', 'b', 'a'])) |
|
212 | nt.assert_equal(sl.fields(0), text.SList(['a', 'b', 'a'])) | |
217 | nt.assert_equal(sl.sort(field=1, nums=True), text.SList(['b 1', 'a 2', 'a 11'])) |
|
213 | nt.assert_equal(sl.sort(field=1, nums=True), text.SList(['b 1', 'a 2', 'a 11'])) |
@@ -1,109 +1,111 b'' | |||||
1 | .. image:: https://codecov.io/github/ipython/ipython/coverage.svg?branch=master |
|
1 | .. image:: https://codecov.io/github/ipython/ipython/coverage.svg?branch=master | |
2 | :target: https://codecov.io/github/ipython/ipython?branch=master |
|
2 | :target: https://codecov.io/github/ipython/ipython?branch=master | |
3 |
|
3 | |||
4 | .. image:: https://img.shields.io/pypi/v/IPython.svg |
|
4 | .. image:: https://img.shields.io/pypi/v/IPython.svg | |
5 | :target: https://pypi.python.org/pypi/ipython |
|
5 | :target: https://pypi.python.org/pypi/ipython | |
6 |
|
6 | |||
7 | .. image:: https://img.shields.io/travis/ipython/ipython.svg |
|
7 | .. image:: https://img.shields.io/travis/ipython/ipython.svg | |
8 | :target: https://travis-ci.org/ipython/ipython |
|
8 | :target: https://travis-ci.org/ipython/ipython | |
9 |
|
9 | |||
10 | .. image:: https://www.codetriage.com/ipython/ipython/badges/users.svg |
|
10 | .. image:: https://www.codetriage.com/ipython/ipython/badges/users.svg | |
11 | :target: https://www.codetriage.com/ipython/ipython/ |
|
11 | :target: https://www.codetriage.com/ipython/ipython/ | |
12 |
|
12 | |||
13 | =========================================== |
|
13 | =========================================== | |
14 | IPython: Productive Interactive Computing |
|
14 | IPython: Productive Interactive Computing | |
15 | =========================================== |
|
15 | =========================================== | |
16 |
|
16 | |||
17 | Overview |
|
17 | Overview | |
18 | ======== |
|
18 | ======== | |
19 |
|
19 | |||
20 | Welcome to IPython. Our full documentation is available on `ipython.readthedocs.io |
|
20 | Welcome to IPython. Our full documentation is available on `ipython.readthedocs.io | |
21 | <https://ipython.readthedocs.io/en/stable/>`_ and contains information on how to install, use and |
|
21 | <https://ipython.readthedocs.io/en/stable/>`_ and contains information on how to install, use and | |
22 | contribute to the project. |
|
22 | contribute to the project. | |
23 |
|
23 | |||
24 | **IPython versions and Python Support** |
|
24 | **IPython versions and Python Support** | |
25 |
|
25 | |||
26 |
**IPython 6** requires Python version 3. |
|
26 | **IPython 6.3** requires Python version 3.4 and above. | |
|
27 | ||||
|
28 | **IPython 6.0-6.2** requires Python version 3.3 and above. | |||
27 |
|
29 | |||
28 | **IPython 5.x LTS** is the compatible release for Python 2.7. |
|
30 | **IPython 5.x LTS** is the compatible release for Python 2.7. | |
29 | If you require Python 2 support, you **must** use IPython 5.x LTS. Please |
|
31 | If you require Python 2 support, you **must** use IPython 5.x LTS. Please | |
30 | update your project configurations and requirements as necessary. |
|
32 | update your project configurations and requirements as necessary. | |
31 |
|
33 | |||
32 |
|
34 | |||
33 | The Notebook, Qt console and a number of other pieces are now parts of *Jupyter*. |
|
35 | The Notebook, Qt console and a number of other pieces are now parts of *Jupyter*. | |
34 | See the `Jupyter installation docs <http://jupyter.readthedocs.io/en/latest/install.html>`__ |
|
36 | See the `Jupyter installation docs <http://jupyter.readthedocs.io/en/latest/install.html>`__ | |
35 | if you want to use these. |
|
37 | if you want to use these. | |
36 |
|
38 | |||
37 |
|
39 | |||
38 |
|
40 | |||
39 |
|
41 | |||
40 | Development and Instant running |
|
42 | Development and Instant running | |
41 | =============================== |
|
43 | =============================== | |
42 |
|
44 | |||
43 | You can find the latest version of the development documentation on `readthedocs |
|
45 | You can find the latest version of the development documentation on `readthedocs | |
44 | <http://ipython.readthedocs.io/en/latest/>`_. |
|
46 | <http://ipython.readthedocs.io/en/latest/>`_. | |
45 |
|
47 | |||
46 | You can run IPython from this directory without even installing it system-wide |
|
48 | You can run IPython from this directory without even installing it system-wide | |
47 | by typing at the terminal:: |
|
49 | by typing at the terminal:: | |
48 |
|
50 | |||
49 | $ python -m IPython |
|
51 | $ python -m IPython | |
50 |
|
52 | |||
51 | Or see the `development installation docs |
|
53 | Or see the `development installation docs | |
52 | <http://ipython.readthedocs.io/en/latest/install/install.html#installing-the-development-version>`_ |
|
54 | <http://ipython.readthedocs.io/en/latest/install/install.html#installing-the-development-version>`_ | |
53 | for the latest revision on read the docs. |
|
55 | for the latest revision on read the docs. | |
54 |
|
56 | |||
55 | Documentation and installation instructions for older version of IPython can be |
|
57 | Documentation and installation instructions for older version of IPython can be | |
56 | found on the `IPython website <http://ipython.org/documentation.html>`_ |
|
58 | found on the `IPython website <http://ipython.org/documentation.html>`_ | |
57 |
|
59 | |||
58 |
|
60 | |||
59 |
|
61 | |||
60 | IPython requires Python version 3 or above |
|
62 | IPython requires Python version 3 or above | |
61 | ========================================== |
|
63 | ========================================== | |
62 |
|
64 | |||
63 | Starting with version 6.0, IPython does not support Python 2.7, 3.0, 3.1, or |
|
65 | Starting with version 6.0, IPython does not support Python 2.7, 3.0, 3.1, or | |
64 | 3.2. |
|
66 | 3.2. | |
65 |
|
67 | |||
66 | For a version compatible with Python 2.7, please install the 5.x LTS Long Term |
|
68 | For a version compatible with Python 2.7, please install the 5.x LTS Long Term | |
67 | Support version. |
|
69 | Support version. | |
68 |
|
70 | |||
69 | If you are encountering this error message you are likely trying to install or |
|
71 | If you are encountering this error message you are likely trying to install or | |
70 | use IPython from source. You need to checkout the remote 5.x branch. If you are |
|
72 | use IPython from source. You need to checkout the remote 5.x branch. If you are | |
71 | using git the following should work: |
|
73 | using git the following should work: | |
72 |
|
74 | |||
73 | $ git fetch origin |
|
75 | $ git fetch origin | |
74 | $ git checkout 5.x |
|
76 | $ git checkout 5.x | |
75 |
|
77 | |||
76 | If you encounter this error message with a regular install of IPython, then you |
|
78 | If you encounter this error message with a regular install of IPython, then you | |
77 | likely need to update your package manager, for example if you are using `pip` |
|
79 | likely need to update your package manager, for example if you are using `pip` | |
78 | check the version of pip with |
|
80 | check the version of pip with | |
79 |
|
81 | |||
80 | $ pip --version |
|
82 | $ pip --version | |
81 |
|
83 | |||
82 | You will need to update pip to the version 9.0.1 or greater. If you are not using |
|
84 | You will need to update pip to the version 9.0.1 or greater. If you are not using | |
83 | pip, please inquiry with the maintainers of the package for your package |
|
85 | pip, please inquiry with the maintainers of the package for your package | |
84 | manager. |
|
86 | manager. | |
85 |
|
87 | |||
86 | For more information see one of our blog posts: |
|
88 | For more information see one of our blog posts: | |
87 |
|
89 | |||
88 | http://blog.jupyter.org/2016/07/08/ipython-5-0-released/ |
|
90 | http://blog.jupyter.org/2016/07/08/ipython-5-0-released/ | |
89 |
|
91 | |||
90 | As well as the following Pull-Request for discussion: |
|
92 | As well as the following Pull-Request for discussion: | |
91 |
|
93 | |||
92 | https://github.com/ipython/ipython/pull/9900 |
|
94 | https://github.com/ipython/ipython/pull/9900 | |
93 |
|
95 | |||
94 | This error does also occur if you are invoking ``setup.py`` directly βΒ which you |
|
96 | This error does also occur if you are invoking ``setup.py`` directly βΒ which you | |
95 | should not βΒ or are using ``easy_install`` If this is the case, use ``pip |
|
97 | should not βΒ or are using ``easy_install`` If this is the case, use ``pip | |
96 | install .`` (instead of ``setup.py install`` , and ``pip install -e .`` instead |
|
98 | install .`` (instead of ``setup.py install`` , and ``pip install -e .`` instead | |
97 | of ``setup.py develop`` If you are depending on IPython as a dependency you may |
|
99 | of ``setup.py develop`` If you are depending on IPython as a dependency you may | |
98 | also want to have a conditional dependency on IPython depending on the Python |
|
100 | also want to have a conditional dependency on IPython depending on the Python | |
99 | version:: |
|
101 | version:: | |
100 |
|
102 | |||
101 | install_req = ['ipython'] |
|
103 | install_req = ['ipython'] | |
102 | if sys.version_info[0] < 3 and 'bdist_wheel' not in sys.argv: |
|
104 | if sys.version_info[0] < 3 and 'bdist_wheel' not in sys.argv: | |
103 | install_req.remove('ipython') |
|
105 | install_req.remove('ipython') | |
104 | install_req.append('ipython<6') |
|
106 | install_req.append('ipython<6') | |
105 |
|
107 | |||
106 | setup( |
|
108 | setup( | |
107 | ... |
|
109 | ... | |
108 | install_requires=install_req |
|
110 | install_requires=install_req | |
109 | ) |
|
111 | ) |
@@ -1,256 +1,257 b'' | |||||
1 | .. _overview: |
|
1 | .. _overview: | |
2 |
|
2 | |||
3 | ======== |
|
3 | ======== | |
4 | Overview |
|
4 | Overview | |
5 | ======== |
|
5 | ======== | |
6 |
|
6 | |||
7 | One of Python's most useful features is its interactive interpreter. |
|
7 | One of Python's most useful features is its interactive interpreter. | |
8 | It allows for very fast testing of ideas without the overhead of |
|
8 | It allows for very fast testing of ideas without the overhead of | |
9 | creating test files as is typical in most programming languages. |
|
9 | creating test files as is typical in most programming languages. | |
10 | However, the interpreter supplied with the standard Python distribution |
|
10 | However, the interpreter supplied with the standard Python distribution | |
11 | is somewhat limited for extended interactive use. |
|
11 | is somewhat limited for extended interactive use. | |
12 |
|
12 | |||
13 | The goal of IPython is to create a comprehensive environment for |
|
13 | The goal of IPython is to create a comprehensive environment for | |
14 | interactive and exploratory computing. To support this goal, IPython |
|
14 | interactive and exploratory computing. To support this goal, IPython | |
15 | has three main components: |
|
15 | has three main components: | |
16 |
|
16 | |||
17 | * An enhanced interactive Python shell. |
|
17 | * An enhanced interactive Python shell. | |
18 |
|
18 | |||
19 | * A decoupled :ref:`two-process communication model <ipythonzmq>`, which |
|
19 | * A decoupled :ref:`two-process communication model <ipythonzmq>`, which | |
20 | allows for multiple clients to connect to a computation kernel, most notably |
|
20 | allows for multiple clients to connect to a computation kernel, most notably | |
21 | the web-based notebook provided with `Jupyter <https://jupyter.org>`_. |
|
21 | the web-based notebook provided with `Jupyter <https://jupyter.org>`_. | |
22 |
|
22 | |||
23 | * An architecture for interactive parallel computing now part of the |
|
23 | * An architecture for interactive parallel computing now part of the | |
24 | `ipyparallel` package. |
|
24 | `ipyparallel` package. | |
25 |
|
25 | |||
26 | All of IPython is open source (released under the revised BSD license). |
|
26 | All of IPython is open source (released under the revised BSD license). | |
27 |
|
27 | |||
28 | Enhanced interactive Python shell |
|
28 | Enhanced interactive Python shell | |
29 | ================================= |
|
29 | ================================= | |
30 |
|
30 | |||
31 | IPython's interactive shell (:command:`ipython`), has the following goals, |
|
31 | IPython's interactive shell (:command:`ipython`), has the following goals, | |
32 | amongst others: |
|
32 | amongst others: | |
33 |
|
33 | |||
34 | 1. Provide an interactive shell superior to Python's default. IPython |
|
34 | 1. Provide an interactive shell superior to Python's default. IPython | |
35 | has many features for tab-completion, object introspection, system shell |
|
35 | has many features for tab-completion, object introspection, system shell | |
36 | access, command history retrieval across sessions, and its own special |
|
36 | access, command history retrieval across sessions, and its own special | |
37 | command system for adding functionality when working interactively. It |
|
37 | command system for adding functionality when working interactively. It | |
38 | tries to be a very efficient environment both for Python code development |
|
38 | tries to be a very efficient environment both for Python code development | |
39 | and for exploration of problems using Python objects (in situations like |
|
39 | and for exploration of problems using Python objects (in situations like | |
40 | data analysis). |
|
40 | data analysis). | |
41 |
|
41 | |||
42 | 2. Serve as an embeddable, ready to use interpreter for your own |
|
42 | 2. Serve as an embeddable, ready to use interpreter for your own | |
43 | programs. An interactive IPython shell can be started with a single call |
|
43 | programs. An interactive IPython shell can be started with a single call | |
44 | from inside another program, providing access to the current namespace. |
|
44 | from inside another program, providing access to the current namespace. | |
45 | This can be very useful both for debugging purposes and for situations |
|
45 | This can be very useful both for debugging purposes and for situations | |
46 | where a blend of batch-processing and interactive exploration are needed. |
|
46 | where a blend of batch-processing and interactive exploration are needed. | |
47 |
|
47 | |||
48 | 3. Offer a flexible framework which can be used as the base |
|
48 | 3. Offer a flexible framework which can be used as the base | |
49 | environment for working with other systems, with Python as the underlying |
|
49 | environment for working with other systems, with Python as the underlying | |
50 | bridge language. Specifically scientific environments like Mathematica, |
|
50 | bridge language. Specifically scientific environments like Mathematica, | |
51 | IDL and Matlab inspired its design, but similar ideas can be |
|
51 | IDL and Matlab inspired its design, but similar ideas can be | |
52 | useful in many fields. |
|
52 | useful in many fields. | |
53 |
|
53 | |||
54 | 4. Allow interactive testing of threaded graphical toolkits. IPython |
|
54 | 4. Allow interactive testing of threaded graphical toolkits. IPython | |
55 | has support for interactive, non-blocking control of GTK, Qt, WX, GLUT, and |
|
55 | has support for interactive, non-blocking control of GTK, Qt, WX, GLUT, and | |
56 | OS X applications via special threading flags. The normal Python |
|
56 | OS X applications via special threading flags. The normal Python | |
57 | shell can only do this for Tkinter applications. |
|
57 | shell can only do this for Tkinter applications. | |
58 |
|
58 | |||
59 | Main features of the interactive shell |
|
59 | Main features of the interactive shell | |
60 | -------------------------------------- |
|
60 | -------------------------------------- | |
61 |
|
61 | |||
62 | * Dynamic object introspection. One can access docstrings, function |
|
62 | * Dynamic object introspection. One can access docstrings, function | |
63 | definition prototypes, source code, source files and other details |
|
63 | definition prototypes, source code, source files and other details | |
64 | of any object accessible to the interpreter with a single |
|
64 | of any object accessible to the interpreter with a single | |
65 | keystroke (:samp:`?`, and using :samp:`??` provides additional detail). |
|
65 | keystroke (:samp:`?`, and using :samp:`??` provides additional detail). | |
66 |
|
66 | |||
67 | * Searching through modules and namespaces with :samp:`*` wildcards, both |
|
67 | * Searching through modules and namespaces with :samp:`*` wildcards, both | |
68 | when using the :samp:`?` system and via the :samp:`%psearch` command. |
|
68 | when using the :samp:`?` system and via the :samp:`%psearch` command. | |
69 |
|
69 | |||
70 | * Completion in the local namespace, by typing :kbd:`TAB` at the prompt. |
|
70 | * Completion in the local namespace, by typing :kbd:`TAB` at the prompt. | |
71 | This works for keywords, modules, methods, variables and files in the |
|
71 | This works for keywords, modules, methods, variables and files in the | |
72 | current directory. This is supported via the ``prompt_toolkit`` library. |
|
72 | current directory. This is supported via the ``prompt_toolkit`` library. | |
73 | Custom completers can be implemented easily for different purposes |
|
73 | Custom completers can be implemented easily for different purposes | |
74 | (system commands, magic arguments etc.) |
|
74 | (system commands, magic arguments etc.) | |
75 |
|
75 | |||
76 | * Numbered input/output prompts with command history (persistent |
|
76 | * Numbered input/output prompts with command history (persistent | |
77 | across sessions and tied to each profile), full searching in this |
|
77 | across sessions and tied to each profile), full searching in this | |
78 | history and caching of all input and output. |
|
78 | history and caching of all input and output. | |
79 |
|
79 | |||
80 | * User-extensible 'magic' commands. A set of commands prefixed with |
|
80 | * User-extensible 'magic' commands. A set of commands prefixed with | |
81 | :samp:`%` or :samp:`%%` is available for controlling IPython itself and provides |
|
81 | :samp:`%` or :samp:`%%` is available for controlling IPython itself and provides | |
82 | directory control, namespace information and many aliases to |
|
82 | directory control, namespace information and many aliases to | |
83 | common system shell commands. |
|
83 | common system shell commands. | |
84 |
|
84 | |||
85 | * Alias facility for defining your own system aliases. |
|
85 | * Alias facility for defining your own system aliases. | |
86 |
|
86 | |||
87 | * Complete system shell access. Lines starting with :samp:`!` are passed |
|
87 | * Complete system shell access. Lines starting with :samp:`!` are passed | |
88 | directly to the system shell, and using :samp:`!!` or :samp:`var = !cmd` |
|
88 | directly to the system shell, and using :samp:`!!` or :samp:`var = !cmd` | |
89 | captures shell output into python variables for further use. |
|
89 | captures shell output into python variables for further use. | |
90 |
|
90 | |||
91 | * The ability to expand python variables when calling the system shell. In a |
|
91 | * The ability to expand python variables when calling the system shell. In a | |
92 | shell command, any python variable prefixed with :samp:`$` is expanded. A |
|
92 | shell command, any python variable prefixed with :samp:`$` is expanded. A | |
93 | double :samp:`$$` allows passing a literal :samp:`$` to the shell (for access |
|
93 | double :samp:`$$` allows passing a literal :samp:`$` to the shell (for access | |
94 | to shell and environment variables like :envvar:`PATH`). |
|
94 | to shell and environment variables like :envvar:`PATH`). | |
95 |
|
95 | |||
96 | * Filesystem navigation, via a magic :samp:`%cd` command, along with a |
|
96 | * Filesystem navigation, via a magic :samp:`%cd` command, along with a | |
97 | persistent bookmark system (using :samp:`%bookmark`) for fast access to |
|
97 | persistent bookmark system (using :samp:`%bookmark`) for fast access to | |
98 | frequently visited directories. |
|
98 | frequently visited directories. | |
99 |
|
99 | |||
100 | * A lightweight persistence framework via the :samp:`%store` command, which |
|
100 | * A lightweight persistence framework via the :samp:`%store` command, which | |
101 | allows you to save arbitrary Python variables. These get restored |
|
101 | allows you to save arbitrary Python variables. These get restored | |
102 | when you run the :samp:`%store -r` command. |
|
102 | when you run the :samp:`%store -r` command. | |
103 |
|
103 | |||
104 | * Automatic indentation and highlighting of code as you type (through the |
|
104 | * Automatic indentation and highlighting of code as you type (through the | |
105 | `prompt_toolkit` library). |
|
105 | `prompt_toolkit` library). | |
106 |
|
106 | |||
107 | * Macro system for quickly re-executing multiple lines of previous |
|
107 | * Macro system for quickly re-executing multiple lines of previous | |
108 | input with a single name via the :samp:`%macro` command. Macros can be |
|
108 | input with a single name via the :samp:`%macro` command. Macros can be | |
109 | stored persistently via :samp:`%store` and edited via :samp:`%edit`. |
|
109 | stored persistently via :samp:`%store` and edited via :samp:`%edit`. | |
110 |
|
110 | |||
111 | * Session logging (you can then later use these logs as code in your |
|
111 | * Session logging (you can then later use these logs as code in your | |
112 | programs). Logs can optionally timestamp all input, and also store |
|
112 | programs). Logs can optionally timestamp all input, and also store | |
113 | session output (marked as comments, so the log remains valid |
|
113 | session output (marked as comments, so the log remains valid | |
114 | Python source code). |
|
114 | Python source code). | |
115 |
|
115 | |||
116 | * Session restoring: logs can be replayed to restore a previous |
|
116 | * Session restoring: logs can be replayed to restore a previous | |
117 | session to the state where you left it. |
|
117 | session to the state where you left it. | |
118 |
|
118 | |||
119 | * Verbose and colored exception traceback printouts. Easier to parse |
|
119 | * Verbose and colored exception traceback printouts. Easier to parse | |
120 | visually, and in verbose mode they produce a lot of useful |
|
120 | visually, and in verbose mode they produce a lot of useful | |
121 | debugging information (basically a terminal version of the cgitb |
|
121 | debugging information (basically a terminal version of the cgitb | |
122 | module). |
|
122 | module). | |
123 |
|
123 | |||
124 | * Auto-parentheses via the :samp:`%autocall` command: callable objects can be |
|
124 | * Auto-parentheses via the :samp:`%autocall` command: callable objects can be | |
125 | executed without parentheses: :samp:`sin 3` is automatically converted to |
|
125 | executed without parentheses: :samp:`sin 3` is automatically converted to | |
126 | :samp:`sin(3)` |
|
126 | :samp:`sin(3)` | |
127 |
|
127 | |||
128 | * Auto-quoting: using :samp:`,`, or :samp:`;` as the first character forces |
|
128 | * Auto-quoting: using :samp:`,`, or :samp:`;` as the first character forces | |
129 | auto-quoting of the rest of the line: :samp:`,my_function a b` becomes |
|
129 | auto-quoting of the rest of the line: :samp:`,my_function a b` becomes | |
130 | automatically :samp:`my_function("a","b")`, while :samp:`;my_function a b` |
|
130 | automatically :samp:`my_function("a","b")`, while :samp:`;my_function a b` | |
131 | becomes :samp:`my_function("a b")`. |
|
131 | becomes :samp:`my_function("a b")`. | |
132 |
|
132 | |||
133 | * Extensible input syntax. You can define filters that pre-process |
|
133 | * Extensible input syntax. You can define filters that pre-process | |
134 | user input to simplify input in special situations. This allows |
|
134 | user input to simplify input in special situations. This allows | |
135 | for example pasting multi-line code fragments which start with |
|
135 | for example pasting multi-line code fragments which start with | |
136 | :samp:`>>>` or :samp:`...` such as those from other python sessions or the |
|
136 | :samp:`>>>` or :samp:`...` such as those from other python sessions or the | |
137 | standard Python documentation. |
|
137 | standard Python documentation. | |
138 |
|
138 | |||
139 | * Flexible :ref:`configuration system <config_overview>`. It uses a |
|
139 | * Flexible :ref:`configuration system <config_overview>`. It uses a | |
140 | configuration file which allows permanent setting of all command-line |
|
140 | configuration file which allows permanent setting of all command-line | |
141 | options, module loading, code and file execution. The system allows |
|
141 | options, module loading, code and file execution. The system allows | |
142 | recursive file inclusion, so you can have a base file with defaults and |
|
142 | recursive file inclusion, so you can have a base file with defaults and | |
143 | layers which load other customizations for particular projects. |
|
143 | layers which load other customizations for particular projects. | |
144 |
|
144 | |||
145 | * Embeddable. You can call IPython as a python shell inside your own |
|
145 | * Embeddable. You can call IPython as a python shell inside your own | |
146 | python programs. This can be used both for debugging code or for |
|
146 | python programs. This can be used both for debugging code or for | |
147 | providing interactive abilities to your programs with knowledge |
|
147 | providing interactive abilities to your programs with knowledge | |
148 | about the local namespaces (very useful in debugging and data |
|
148 | about the local namespaces (very useful in debugging and data | |
149 | analysis situations). |
|
149 | analysis situations). | |
150 |
|
150 | |||
151 | * Easy debugger access. You can set IPython to call up an enhanced version of |
|
151 | * Easy debugger access. You can set IPython to call up an enhanced version of | |
152 | the Python debugger (pdb) every time there is an uncaught exception. This |
|
152 | the Python debugger (pdb) every time there is an uncaught exception. This | |
153 | drops you inside the code which triggered the exception with all the data |
|
153 | drops you inside the code which triggered the exception with all the data | |
154 | live and it is possible to navigate the stack to rapidly isolate the source |
|
154 | live and it is possible to navigate the stack to rapidly isolate the source | |
155 | of a bug. The :samp:`%run` magic command (with the :samp:`-d` option) can run |
|
155 | of a bug. The :samp:`%run` magic command (with the :samp:`-d` option) can run | |
156 | any script under pdb's control, automatically setting initial breakpoints for |
|
156 | any script under pdb's control, automatically setting initial breakpoints for | |
157 | you. This version of pdb has IPython-specific improvements, including |
|
157 | you. This version of pdb has IPython-specific improvements, including | |
158 | tab-completion and traceback coloring support. For even easier debugger |
|
158 | tab-completion and traceback coloring support. For even easier debugger | |
159 | access, try :samp:`%debug` after seeing an exception. |
|
159 | access, try :samp:`%debug` after seeing an exception. | |
160 |
|
160 | |||
161 | * Profiler support. You can run single statements (similar to |
|
161 | * Profiler support. You can run single statements (similar to | |
162 | :samp:`profile.run()`) or complete programs under the profiler's control. |
|
162 | :samp:`profile.run()`) or complete programs under the profiler's control. | |
163 | While this is possible with standard cProfile or profile modules, |
|
163 | While this is possible with standard cProfile or profile modules, | |
164 | IPython wraps this functionality with magic commands (see :samp:`%prun` |
|
164 | IPython wraps this functionality with magic commands (see :samp:`%prun` | |
165 | and :samp:`%run -p`) convenient for rapid interactive work. |
|
165 | and :samp:`%run -p`) convenient for rapid interactive work. | |
166 |
|
166 | |||
167 | * Simple timing information. You can use the :samp:`%timeit` command to get |
|
167 | * Simple timing information. You can use the :samp:`%timeit` command to get | |
168 | the execution time of a Python statement or expression. This machinery is |
|
168 | the execution time of a Python statement or expression. This machinery is | |
169 | intelligent enough to do more repetitions for commands that finish very |
|
169 | intelligent enough to do more repetitions for commands that finish very | |
170 | quickly in order to get a better estimate of their running time. |
|
170 | quickly in order to get a better estimate of their running time. | |
171 |
|
171 | |||
172 | .. sourcecode:: ipython |
|
172 | .. sourcecode:: ipython | |
173 |
|
173 | |||
174 | In [1]: %timeit 1+1 |
|
174 | In [1]: %timeit 1+1 | |
175 | 10000000 loops, best of 3: 25.5 ns per loop |
|
175 | 10000000 loops, best of 3: 25.5 ns per loop | |
176 |
|
176 | |||
177 | In [2]: %timeit [math.sin(x) for x in range(5000)] |
|
177 | In [2]: %timeit [math.sin(x) for x in range(5000)] | |
178 | 1000 loops, best of 3: 719 Β΅s per loop |
|
178 | 1000 loops, best of 3: 719 Β΅s per loop | |
179 |
|
179 | |||
180 | .. |
|
180 | .. | |
181 |
|
181 | |||
182 | To get the timing information for more than one expression, use the |
|
182 | To get the timing information for more than one expression, use the | |
183 | :samp:`%%timeit` cell magic command. |
|
183 | :samp:`%%timeit` cell magic command. | |
184 |
|
184 | |||
185 |
|
185 | |||
186 | * Doctest support. The special :samp:`%doctest_mode` command toggles a mode |
|
186 | * Doctest support. The special :samp:`%doctest_mode` command toggles a mode | |
187 | to use doctest-compatible prompts, so you can use IPython sessions as |
|
187 | to use doctest-compatible prompts, so you can use IPython sessions as | |
188 | doctest code. By default, IPython also allows you to paste existing |
|
188 | doctest code. By default, IPython also allows you to paste existing | |
189 | doctests, and strips out the leading :samp:`>>>` and :samp:`...` prompts in |
|
189 | doctests, and strips out the leading :samp:`>>>` and :samp:`...` prompts in | |
190 | them. |
|
190 | them. | |
191 |
|
191 | |||
192 | .. _ipythonzmq: |
|
192 | .. _ipythonzmq: | |
193 |
|
193 | |||
194 | Decoupled two-process model |
|
194 | Decoupled two-process model | |
195 | ============================== |
|
195 | ============================== | |
196 |
|
196 | |||
197 | IPython has abstracted and extended the notion of a traditional |
|
197 | IPython has abstracted and extended the notion of a traditional | |
198 | *Read-Evaluate-Print Loop* (REPL) environment by decoupling the *evaluation* |
|
198 | *Read-Evaluate-Print Loop* (REPL) environment by decoupling the *evaluation* | |
199 | into its own process. We call this process a **kernel**: it receives execution |
|
199 | into its own process. We call this process a **kernel**: it receives execution | |
200 | instructions from clients and communicates the results back to them. |
|
200 | instructions from clients and communicates the results back to them. | |
201 |
|
201 | |||
202 | This decoupling allows us to have several clients connected to the same |
|
202 | This decoupling allows us to have several clients connected to the same | |
203 | kernel, and even allows clients and kernels to live on different machines. |
|
203 | kernel, and even allows clients and kernels to live on different machines. | |
204 | With the exclusion of the traditional single process terminal-based IPython |
|
204 | With the exclusion of the traditional single process terminal-based IPython | |
205 | (what you start if you run ``ipython`` without any subcommands), all |
|
205 | (what you start if you run ``ipython`` without any subcommands), all | |
206 | other IPython machinery uses this two-process model. Most of this is now part |
|
206 | other IPython machinery uses this two-process model. Most of this is now part | |
207 | of the `Jupyter` project, which includes ``jupyter console``, ``jupyter |
|
207 | of the `Jupyter` project, which includes ``jupyter console``, ``jupyter | |
208 | qtconsole``, and ``jupyter notebook``. |
|
208 | qtconsole``, and ``jupyter notebook``. | |
209 |
|
209 | |||
210 | As an example, this means that when you start ``jupyter qtconsole``, you're |
|
210 | As an example, this means that when you start ``jupyter qtconsole``, you're | |
211 | really starting two processes, a kernel and a Qt-based client can send |
|
211 | really starting two processes, a kernel and a Qt-based client can send | |
212 | commands to and receive results from that kernel. If there is already a kernel |
|
212 | commands to and receive results from that kernel. If there is already a kernel | |
213 | running that you want to connect to, you can pass the ``--existing`` flag |
|
213 | running that you want to connect to, you can pass the ``--existing`` flag | |
214 | which will skip initiating a new kernel and connect to the most recent kernel, |
|
214 | which will skip initiating a new kernel and connect to the most recent kernel, | |
215 | instead. To connect to a specific kernel once you have several kernels |
|
215 | instead. To connect to a specific kernel once you have several kernels | |
216 | running, use the ``%connect_info`` magic to get the unique connection file, |
|
216 | running, use the ``%connect_info`` magic to get the unique connection file, | |
217 | which will be something like ``--existing kernel-19732.json`` but with |
|
217 | which will be something like ``--existing kernel-19732.json`` but with | |
218 | different numbers which correspond to the Process ID of the kernel. |
|
218 | different numbers which correspond to the Process ID of the kernel. | |
219 |
|
219 | |||
220 |
You can read more about using `jupyter qtconsole |
|
220 | You can read more about using `jupyter qtconsole | |
221 | <http://jupyter.org/qtconsole/>`_, and |
|
221 | <http://jupyter.org/qtconsole/>`_, and | |
222 | `jupyter notebook <http://jupyter-notebook.readthedocs.io/en/latest/>`_. There |
|
222 | `jupyter notebook <http://jupyter-notebook.readthedocs.io/en/latest/>`_. There | |
223 |
is also a :ref:`message spec <messaging>` which documents the protocol for |
|
223 | is also a :ref:`message spec <messaging>` which documents the protocol for | |
224 | communication between kernels |
|
224 | communication between kernels | |
225 | and clients. |
|
225 | and clients. | |
226 |
|
226 | |||
227 | .. seealso:: |
|
227 | .. seealso:: | |
228 |
|
228 | |||
229 | `Frontend/Kernel Model`_ example notebook |
|
229 | `Frontend/Kernel Model`_ example notebook | |
230 |
|
230 | |||
231 |
|
231 | |||
232 | Interactive parallel computing |
|
232 | Interactive parallel computing | |
233 | ============================== |
|
233 | ============================== | |
234 |
|
234 | |||
235 |
|
235 | |||
236 | This functionality is optional and now part of the `ipyparallel |
|
236 | This functionality is optional and now part of the `ipyparallel | |
237 | <http://ipyparallel.readthedocs.io/>`_ project. |
|
237 | <http://ipyparallel.readthedocs.io/>`_ project. | |
238 |
|
238 | |||
239 | Portability and Python requirements |
|
239 | Portability and Python requirements | |
240 | ----------------------------------- |
|
240 | ----------------------------------- | |
241 |
|
241 | |||
242 |
Version 6. |
|
242 | Version 6.3+ supports Python 3.4 and higher. | |
|
243 | Versions 6.0 to 6.2 support Python 3.3 and higher. | |||
243 | Versions 2.0 to 5.x work with Python 2.7.x releases and Python 3.3 and higher. |
|
244 | Versions 2.0 to 5.x work with Python 2.7.x releases and Python 3.3 and higher. | |
244 | Version 1.0 additionally worked with Python 2.6 and 3.2. |
|
245 | Version 1.0 additionally worked with Python 2.6 and 3.2. | |
245 | Version 0.12 was the first version to fully support Python 3. |
|
246 | Version 0.12 was the first version to fully support Python 3. | |
246 |
|
247 | |||
247 | IPython is known to work on the following operating systems: |
|
248 | IPython is known to work on the following operating systems: | |
248 |
|
249 | |||
249 | * Linux |
|
250 | * Linux | |
250 | * Most other Unix-like OSs (AIX, Solaris, BSD, etc.) |
|
251 | * Most other Unix-like OSs (AIX, Solaris, BSD, etc.) | |
251 | * Mac OS X |
|
252 | * Mac OS X | |
252 | * Windows (CygWin, XP, Vista, etc.) |
|
253 | * Windows (CygWin, XP, Vista, etc.) | |
253 |
|
254 | |||
254 | See :ref:`here <install_index>` for instructions on how to install IPython. |
|
255 | See :ref:`here <install_index>` for instructions on how to install IPython. | |
255 |
|
256 | |||
256 | .. include:: links.txt |
|
257 | .. include:: links.txt |
@@ -1,264 +1,264 b'' | |||||
1 | #!/usr/bin/env python3 |
|
1 | #!/usr/bin/env python3 | |
2 | # -*- coding: utf-8 -*- |
|
2 | # -*- coding: utf-8 -*- | |
3 | """Setup script for IPython. |
|
3 | """Setup script for IPython. | |
4 |
|
4 | |||
5 | Under Posix environments it works like a typical setup.py script. |
|
5 | Under Posix environments it works like a typical setup.py script. | |
6 | Under Windows, the command sdist is not supported, since IPython |
|
6 | Under Windows, the command sdist is not supported, since IPython | |
7 | requires utilities which are not available under Windows.""" |
|
7 | requires utilities which are not available under Windows.""" | |
8 |
|
8 | |||
9 | #----------------------------------------------------------------------------- |
|
9 | #----------------------------------------------------------------------------- | |
10 | # Copyright (c) 2008-2011, IPython Development Team. |
|
10 | # Copyright (c) 2008-2011, IPython Development Team. | |
11 | # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu> |
|
11 | # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu> | |
12 | # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de> |
|
12 | # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de> | |
13 | # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu> |
|
13 | # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu> | |
14 | # |
|
14 | # | |
15 | # Distributed under the terms of the Modified BSD License. |
|
15 | # Distributed under the terms of the Modified BSD License. | |
16 | # |
|
16 | # | |
17 | # The full license is in the file COPYING.rst, distributed with this software. |
|
17 | # The full license is in the file COPYING.rst, distributed with this software. | |
18 | #----------------------------------------------------------------------------- |
|
18 | #----------------------------------------------------------------------------- | |
19 |
|
19 | |||
20 | from __future__ import print_function |
|
20 | from __future__ import print_function | |
21 |
|
21 | |||
22 | import os |
|
22 | import os | |
23 | import sys |
|
23 | import sys | |
24 |
|
24 | |||
25 | # **Python version check** |
|
25 | # **Python version check** | |
26 | # |
|
26 | # | |
27 | # This check is also made in IPython/__init__, don't forget to update both when |
|
27 | # This check is also made in IPython/__init__, don't forget to update both when | |
28 | # changing Python version requirements. |
|
28 | # changing Python version requirements. | |
29 | if sys.version_info < (3, 4): |
|
29 | if sys.version_info < (3, 4): | |
30 | pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.' |
|
30 | pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.' | |
31 | try: |
|
31 | try: | |
32 | import pip |
|
32 | import pip | |
33 | pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]]) |
|
33 | pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]]) | |
34 | if pip_version < (9, 0, 1) : |
|
34 | if pip_version < (9, 0, 1) : | |
35 | pip_message = 'Your pip version is out of date, please install pip >= 9.0.1. '\ |
|
35 | pip_message = 'Your pip version is out of date, please install pip >= 9.0.1. '\ | |
36 | 'pip {} detected.'.format(pip.__version__) |
|
36 | 'pip {} detected.'.format(pip.__version__) | |
37 | else: |
|
37 | else: | |
38 | # pip is new enough - it must be something else |
|
38 | # pip is new enough - it must be something else | |
39 | pip_message = '' |
|
39 | pip_message = '' | |
40 | except Exception: |
|
40 | except Exception: | |
41 | pass |
|
41 | pass | |
42 |
|
42 | |||
43 |
|
43 | |||
44 | error = """ |
|
44 | error = """ | |
45 | IPython 6.3+ supports Python 3.4 and above. |
|
45 | IPython 6.3+ supports Python 3.4 and above. | |
46 | When using Python 2.7, please install IPython 5.x LTS Long Term Support version. |
|
46 | When using Python 2.7, please install IPython 5.x LTS Long Term Support version. | |
47 | Python 3.3 was supported up to IPython 6.2. |
|
47 | Python 3.3 was supported up to IPython 6.2. | |
48 |
|
48 | |||
49 | See IPython `README.rst` file for more information: |
|
49 | See IPython `README.rst` file for more information: | |
50 |
|
50 | |||
51 | https://github.com/ipython/ipython/blob/master/README.rst |
|
51 | https://github.com/ipython/ipython/blob/master/README.rst | |
52 |
|
52 | |||
53 | Python {py} detected. |
|
53 | Python {py} detected. | |
54 | {pip} |
|
54 | {pip} | |
55 | """.format(py=sys.version_info, pip=pip_message ) |
|
55 | """.format(py=sys.version_info, pip=pip_message ) | |
56 |
|
56 | |||
57 | print(error, file=sys.stderr) |
|
57 | print(error, file=sys.stderr) | |
58 | sys.exit(1) |
|
58 | sys.exit(1) | |
59 |
|
59 | |||
60 | # At least we're on the python version we need, move on. |
|
60 | # At least we're on the python version we need, move on. | |
61 |
|
61 | |||
62 | # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly |
|
62 | # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly | |
63 | # update it when the contents of directories change. |
|
63 | # update it when the contents of directories change. | |
64 | if os.path.exists('MANIFEST'): os.remove('MANIFEST') |
|
64 | if os.path.exists('MANIFEST'): os.remove('MANIFEST') | |
65 |
|
65 | |||
66 | from distutils.core import setup |
|
66 | from distutils.core import setup | |
67 |
|
67 | |||
68 | # Our own imports |
|
68 | # Our own imports | |
69 | from setupbase import target_update |
|
69 | from setupbase import target_update | |
70 |
|
70 | |||
71 | from setupbase import ( |
|
71 | from setupbase import ( | |
72 | setup_args, |
|
72 | setup_args, | |
73 | find_packages, |
|
73 | find_packages, | |
74 | find_package_data, |
|
74 | find_package_data, | |
75 | check_package_data_first, |
|
75 | check_package_data_first, | |
76 | find_entry_points, |
|
76 | find_entry_points, | |
77 | build_scripts_entrypt, |
|
77 | build_scripts_entrypt, | |
78 | find_data_files, |
|
78 | find_data_files, | |
79 | git_prebuild, |
|
79 | git_prebuild, | |
80 | install_symlinked, |
|
80 | install_symlinked, | |
81 | install_lib_symlink, |
|
81 | install_lib_symlink, | |
82 | install_scripts_for_symlink, |
|
82 | install_scripts_for_symlink, | |
83 | unsymlink, |
|
83 | unsymlink, | |
84 | ) |
|
84 | ) | |
85 |
|
85 | |||
86 | isfile = os.path.isfile |
|
86 | isfile = os.path.isfile | |
87 | pjoin = os.path.join |
|
87 | pjoin = os.path.join | |
88 |
|
88 | |||
89 | #------------------------------------------------------------------------------- |
|
89 | #------------------------------------------------------------------------------- | |
90 | # Handle OS specific things |
|
90 | # Handle OS specific things | |
91 | #------------------------------------------------------------------------------- |
|
91 | #------------------------------------------------------------------------------- | |
92 |
|
92 | |||
93 | if os.name in ('nt','dos'): |
|
93 | if os.name in ('nt','dos'): | |
94 | os_name = 'windows' |
|
94 | os_name = 'windows' | |
95 | else: |
|
95 | else: | |
96 | os_name = os.name |
|
96 | os_name = os.name | |
97 |
|
97 | |||
98 | # Under Windows, 'sdist' has not been supported. Now that the docs build with |
|
98 | # Under Windows, 'sdist' has not been supported. Now that the docs build with | |
99 | # Sphinx it might work, but let's not turn it on until someone confirms that it |
|
99 | # Sphinx it might work, but let's not turn it on until someone confirms that it | |
100 | # actually works. |
|
100 | # actually works. | |
101 | if os_name == 'windows' and 'sdist' in sys.argv: |
|
101 | if os_name == 'windows' and 'sdist' in sys.argv: | |
102 | print('The sdist command is not available under Windows. Exiting.') |
|
102 | print('The sdist command is not available under Windows. Exiting.') | |
103 | sys.exit(1) |
|
103 | sys.exit(1) | |
104 |
|
104 | |||
105 |
|
105 | |||
106 | #------------------------------------------------------------------------------- |
|
106 | #------------------------------------------------------------------------------- | |
107 | # Things related to the IPython documentation |
|
107 | # Things related to the IPython documentation | |
108 | #------------------------------------------------------------------------------- |
|
108 | #------------------------------------------------------------------------------- | |
109 |
|
109 | |||
110 | # update the manuals when building a source dist |
|
110 | # update the manuals when building a source dist | |
111 | if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'): |
|
111 | if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'): | |
112 |
|
112 | |||
113 | # List of things to be updated. Each entry is a triplet of args for |
|
113 | # List of things to be updated. Each entry is a triplet of args for | |
114 | # target_update() |
|
114 | # target_update() | |
115 | to_update = [ |
|
115 | to_update = [ | |
116 | ('docs/man/ipython.1.gz', |
|
116 | ('docs/man/ipython.1.gz', | |
117 | ['docs/man/ipython.1'], |
|
117 | ['docs/man/ipython.1'], | |
118 | 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'), |
|
118 | 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'), | |
119 | ] |
|
119 | ] | |
120 |
|
120 | |||
121 |
|
121 | |||
122 | [ target_update(*t) for t in to_update ] |
|
122 | [ target_update(*t) for t in to_update ] | |
123 |
|
123 | |||
124 | #--------------------------------------------------------------------------- |
|
124 | #--------------------------------------------------------------------------- | |
125 | # Find all the packages, package data, and data_files |
|
125 | # Find all the packages, package data, and data_files | |
126 | #--------------------------------------------------------------------------- |
|
126 | #--------------------------------------------------------------------------- | |
127 |
|
127 | |||
128 | packages = find_packages() |
|
128 | packages = find_packages() | |
129 | package_data = find_package_data() |
|
129 | package_data = find_package_data() | |
130 |
|
130 | |||
131 | data_files = find_data_files() |
|
131 | data_files = find_data_files() | |
132 |
|
132 | |||
133 | setup_args['packages'] = packages |
|
133 | setup_args['packages'] = packages | |
134 | setup_args['package_data'] = package_data |
|
134 | setup_args['package_data'] = package_data | |
135 | setup_args['data_files'] = data_files |
|
135 | setup_args['data_files'] = data_files | |
136 |
|
136 | |||
137 | #--------------------------------------------------------------------------- |
|
137 | #--------------------------------------------------------------------------- | |
138 | # custom distutils commands |
|
138 | # custom distutils commands | |
139 | #--------------------------------------------------------------------------- |
|
139 | #--------------------------------------------------------------------------- | |
140 | # imports here, so they are after setuptools import if there was one |
|
140 | # imports here, so they are after setuptools import if there was one | |
141 | from distutils.command.sdist import sdist |
|
141 | from distutils.command.sdist import sdist | |
142 |
|
142 | |||
143 | setup_args['cmdclass'] = { |
|
143 | setup_args['cmdclass'] = { | |
144 | 'build_py': \ |
|
144 | 'build_py': \ | |
145 | check_package_data_first(git_prebuild('IPython')), |
|
145 | check_package_data_first(git_prebuild('IPython')), | |
146 | 'sdist' : git_prebuild('IPython', sdist), |
|
146 | 'sdist' : git_prebuild('IPython', sdist), | |
147 | 'symlink': install_symlinked, |
|
147 | 'symlink': install_symlinked, | |
148 | 'install_lib_symlink': install_lib_symlink, |
|
148 | 'install_lib_symlink': install_lib_symlink, | |
149 | 'install_scripts_sym': install_scripts_for_symlink, |
|
149 | 'install_scripts_sym': install_scripts_for_symlink, | |
150 | 'unsymlink': unsymlink, |
|
150 | 'unsymlink': unsymlink, | |
151 | } |
|
151 | } | |
152 |
|
152 | |||
153 |
|
153 | |||
154 | #--------------------------------------------------------------------------- |
|
154 | #--------------------------------------------------------------------------- | |
155 | # Handle scripts, dependencies, and setuptools specific things |
|
155 | # Handle scripts, dependencies, and setuptools specific things | |
156 | #--------------------------------------------------------------------------- |
|
156 | #--------------------------------------------------------------------------- | |
157 |
|
157 | |||
158 | # For some commands, use setuptools. Note that we do NOT list install here! |
|
158 | # For some commands, use setuptools. Note that we do NOT list install here! | |
159 | # If you want a setuptools-enhanced install, just run 'setupegg.py install' |
|
159 | # If you want a setuptools-enhanced install, just run 'setupegg.py install' | |
160 | needs_setuptools = {'develop', 'release', 'bdist_egg', 'bdist_rpm', |
|
160 | needs_setuptools = {'develop', 'release', 'bdist_egg', 'bdist_rpm', | |
161 | 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel', |
|
161 | 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel', | |
162 | 'egg_info', 'easy_install', 'upload', 'install_egg_info', |
|
162 | 'egg_info', 'easy_install', 'upload', 'install_egg_info', | |
163 | } |
|
163 | } | |
164 |
|
164 | |||
165 | if len(needs_setuptools.intersection(sys.argv)) > 0: |
|
165 | if len(needs_setuptools.intersection(sys.argv)) > 0: | |
166 | import setuptools |
|
166 | import setuptools | |
167 |
|
167 | |||
168 | # This dict is used for passing extra arguments that are setuptools |
|
168 | # This dict is used for passing extra arguments that are setuptools | |
169 | # specific to setup |
|
169 | # specific to setup | |
170 | setuptools_extra_args = {} |
|
170 | setuptools_extra_args = {} | |
171 |
|
171 | |||
172 | # setuptools requirements |
|
172 | # setuptools requirements | |
173 |
|
173 | |||
174 | extras_require = dict( |
|
174 | extras_require = dict( | |
175 | parallel = ['ipyparallel'], |
|
175 | parallel = ['ipyparallel'], | |
176 | qtconsole = ['qtconsole'], |
|
176 | qtconsole = ['qtconsole'], | |
177 | doc = ['Sphinx>=1.3'], |
|
177 | doc = ['Sphinx>=1.3'], | |
178 | test = ['nose>=0.10.1', 'requests', 'testpath', 'pygments', 'nbformat', 'ipykernel', 'numpy'], |
|
178 | test = ['nose>=0.10.1', 'requests', 'testpath', 'pygments', 'nbformat', 'ipykernel', 'numpy'], | |
179 | terminal = [], |
|
179 | terminal = [], | |
180 | kernel = ['ipykernel'], |
|
180 | kernel = ['ipykernel'], | |
181 | nbformat = ['nbformat'], |
|
181 | nbformat = ['nbformat'], | |
182 | notebook = ['notebook', 'ipywidgets'], |
|
182 | notebook = ['notebook', 'ipywidgets'], | |
183 | nbconvert = ['nbconvert'], |
|
183 | nbconvert = ['nbconvert'], | |
184 | ) |
|
184 | ) | |
185 |
|
185 | |||
186 | install_requires = [ |
|
186 | install_requires = [ | |
187 | 'setuptools>=18.5', |
|
187 | 'setuptools>=18.5', | |
188 | 'jedi>=0.10', |
|
188 | 'jedi>=0.10', | |
189 | 'decorator', |
|
189 | 'decorator', | |
190 | 'pickleshare', |
|
190 | 'pickleshare', | |
191 | 'simplegeneric>0.8', |
|
191 | 'simplegeneric>0.8', | |
192 | 'traitlets>=4.2', |
|
192 | 'traitlets>=4.2', | |
193 | 'prompt_toolkit>=1.0.15,<2.0.0', |
|
193 | 'prompt_toolkit>=1.0.15,<2.0.0', | |
194 | 'pygments', |
|
194 | 'pygments', | |
195 | 'backcall', |
|
195 | 'backcall', | |
196 | ] |
|
196 | ] | |
197 |
|
197 | |||
198 | # Platform-specific dependencies: |
|
198 | # Platform-specific dependencies: | |
199 | # This is the correct way to specify these, |
|
199 | # This is the correct way to specify these, | |
200 | # but requires pip >= 6. pip < 6 ignores these. |
|
200 | # but requires pip >= 6. pip < 6 ignores these. | |
201 |
|
201 | |||
202 | extras_require.update({ |
|
202 | extras_require.update({ | |
203 |
':python_version |
|
203 | ':python_version == "3.4"': ['typing'], | |
204 | ':sys_platform != "win32"': ['pexpect'], |
|
204 | ':sys_platform != "win32"': ['pexpect'], | |
205 | ':sys_platform == "darwin"': ['appnope'], |
|
205 | ':sys_platform == "darwin"': ['appnope'], | |
206 | ':sys_platform == "win32"': ['colorama'], |
|
206 | ':sys_platform == "win32"': ['colorama'], | |
207 | ':sys_platform == "win32" and python_version < "3.6"': ['win_unicode_console>=0.5'], |
|
207 | ':sys_platform == "win32" and python_version < "3.6"': ['win_unicode_console>=0.5'], | |
208 | }) |
|
208 | }) | |
209 | # FIXME: re-specify above platform dependencies for pip < 6 |
|
209 | # FIXME: re-specify above platform dependencies for pip < 6 | |
210 | # These would result in non-portable bdists. |
|
210 | # These would result in non-portable bdists. | |
211 | if not any(arg.startswith('bdist') for arg in sys.argv): |
|
211 | if not any(arg.startswith('bdist') for arg in sys.argv): | |
212 | if sys.platform == 'darwin': |
|
212 | if sys.platform == 'darwin': | |
213 | install_requires.extend(['appnope']) |
|
213 | install_requires.extend(['appnope']) | |
214 |
|
214 | |||
215 | if not sys.platform.startswith('win'): |
|
215 | if not sys.platform.startswith('win'): | |
216 | install_requires.append('pexpect') |
|
216 | install_requires.append('pexpect') | |
217 |
|
217 | |||
218 | # workaround pypa/setuptools#147, where setuptools misspells |
|
218 | # workaround pypa/setuptools#147, where setuptools misspells | |
219 | # platform_python_implementation as python_implementation |
|
219 | # platform_python_implementation as python_implementation | |
220 | if 'setuptools' in sys.modules: |
|
220 | if 'setuptools' in sys.modules: | |
221 | for key in list(extras_require): |
|
221 | for key in list(extras_require): | |
222 | if 'platform_python_implementation' in key: |
|
222 | if 'platform_python_implementation' in key: | |
223 | new_key = key.replace('platform_python_implementation', 'python_implementation') |
|
223 | new_key = key.replace('platform_python_implementation', 'python_implementation') | |
224 | extras_require[new_key] = extras_require.pop(key) |
|
224 | extras_require[new_key] = extras_require.pop(key) | |
225 |
|
225 | |||
226 | everything = set() |
|
226 | everything = set() | |
227 | for key, deps in extras_require.items(): |
|
227 | for key, deps in extras_require.items(): | |
228 | if ':' not in key: |
|
228 | if ':' not in key: | |
229 | everything.update(deps) |
|
229 | everything.update(deps) | |
230 | extras_require['all'] = everything |
|
230 | extras_require['all'] = everything | |
231 |
|
231 | |||
232 | if 'setuptools' in sys.modules: |
|
232 | if 'setuptools' in sys.modules: | |
233 | setuptools_extra_args['python_requires'] = '>=3.4' |
|
233 | setuptools_extra_args['python_requires'] = '>=3.4' | |
234 | setuptools_extra_args['zip_safe'] = False |
|
234 | setuptools_extra_args['zip_safe'] = False | |
235 | setuptools_extra_args['entry_points'] = { |
|
235 | setuptools_extra_args['entry_points'] = { | |
236 | 'console_scripts': find_entry_points(), |
|
236 | 'console_scripts': find_entry_points(), | |
237 | 'pygments.lexers': [ |
|
237 | 'pygments.lexers': [ | |
238 | 'ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer', |
|
238 | 'ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer', | |
239 | 'ipython = IPython.lib.lexers:IPythonLexer', |
|
239 | 'ipython = IPython.lib.lexers:IPythonLexer', | |
240 | 'ipython3 = IPython.lib.lexers:IPython3Lexer', |
|
240 | 'ipython3 = IPython.lib.lexers:IPython3Lexer', | |
241 | ], |
|
241 | ], | |
242 | } |
|
242 | } | |
243 | setup_args['extras_require'] = extras_require |
|
243 | setup_args['extras_require'] = extras_require | |
244 | setup_args['install_requires'] = install_requires |
|
244 | setup_args['install_requires'] = install_requires | |
245 |
|
245 | |||
246 | else: |
|
246 | else: | |
247 | # scripts has to be a non-empty list, or install_scripts isn't called |
|
247 | # scripts has to be a non-empty list, or install_scripts isn't called | |
248 | setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()] |
|
248 | setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()] | |
249 |
|
249 | |||
250 | setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt |
|
250 | setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt | |
251 |
|
251 | |||
252 | #--------------------------------------------------------------------------- |
|
252 | #--------------------------------------------------------------------------- | |
253 | # Do the actual setup now |
|
253 | # Do the actual setup now | |
254 | #--------------------------------------------------------------------------- |
|
254 | #--------------------------------------------------------------------------- | |
255 |
|
255 | |||
256 | setup_args.update(setuptools_extra_args) |
|
256 | setup_args.update(setuptools_extra_args) | |
257 |
|
257 | |||
258 |
|
258 | |||
259 |
|
259 | |||
260 | def main(): |
|
260 | def main(): | |
261 | setup(**setup_args) |
|
261 | setup(**setup_args) | |
262 |
|
262 | |||
263 | if __name__ == '__main__': |
|
263 | if __name__ == '__main__': | |
264 | main() |
|
264 | main() |
General Comments 0
You need to be logged in to leave comments.
Login now