##// END OF EJS Templates
Merge pull request #13412 from bnavigator/backport-inspect...
Matthias Bussonnier -
r27352:7f253dcf merge
parent child Browse files
Show More
@@ -15,7 +15,7 b' jobs:'
15 15 strategy:
16 16 matrix:
17 17 os: [ubuntu-latest]
18 python-version: ["3.7", "3.8", "3.9"]
18 python-version: ["3.7", "3.8", "3.9", "3.10"]
19 19 # Test all on ubuntu, test ends on macos
20 20 include:
21 21 - os: macos-latest
@@ -39,6 +39,7 b' jobs:'
39 39 - name: Check manifest
40 40 run: check-manifest
41 41 - name: iptest
42 if: matrix.python-version != '3.10'
42 43 run: |
43 44 cd /tmp && iptest --coverage xml && cd -
44 45 cp /tmp/ipy_coverage.xml ./
@@ -182,11 +182,12 b" def getsource(obj, oname='') -> Union[str,None]:"
182 182 except TypeError:
183 183 # The object itself provided no meaningful source, try looking for
184 184 # its class definition instead.
185 if hasattr(obj, '__class__'):
186 try:
187 src = inspect.getsource(obj.__class__)
188 except TypeError:
189 return None
185 try:
186 src = inspect.getsource(obj.__class__)
187 except (OSError, TypeError):
188 return None
189 except OSError:
190 return None
190 191
191 192 return src
192 193
@@ -308,14 +309,14 b' def find_file(obj) -> str:'
308 309 except TypeError:
309 310 # For an instance, the file that matters is where its class was
310 311 # declared.
311 if hasattr(obj, '__class__'):
312 try:
313 fname = inspect.getabsfile(obj.__class__)
314 except TypeError:
315 # Can happen for builtins
316 pass
317 except:
312 try:
313 fname = inspect.getabsfile(obj.__class__)
314 except (OSError, TypeError):
315 # Can happen for builtins
316 pass
317 except OSError:
318 318 pass
319
319 320 return cast_unicode(fname)
320 321
321 322
@@ -338,15 +339,14 b' def find_source_lines(obj):'
338 339 obj = _get_wrapped(obj)
339 340
340 341 try:
342 lineno = inspect.getsourcelines(obj)[1]
343 except TypeError:
344 # For instances, try the class object like getsource() does
341 345 try:
342 lineno = inspect.getsourcelines(obj)[1]
343 except TypeError:
344 # For instances, try the class object like getsource() does
345 if hasattr(obj, '__class__'):
346 lineno = inspect.getsourcelines(obj.__class__)[1]
347 else:
348 lineno = None
349 except:
346 lineno = inspect.getsourcelines(obj.__class__)[1]
347 except (OSError, TypeError):
348 return None
349 except OSError:
350 350 return None
351 351
352 352 return lineno
@@ -12,6 +12,7 b' import unittest'
12 12 from contextlib import contextmanager
13 13
14 14 import nose.tools as nt
15 import pytest
15 16
16 17 from traitlets.config.loader import Config
17 18 from IPython import get_ipython
@@ -29,6 +30,15 b' from IPython.core.completer import ('
29 30 )
30 31 from nose.tools import assert_in, assert_not_in
31 32
33 if sys.version_info >= (3, 10):
34 import jedi
35 from pkg_resources import parse_version
36
37 # Requires https://github.com/davidhalter/jedi/pull/1795
38 jedi_issue = parse_version(jedi.__version__) <= parse_version("0.18.0")
39 else:
40 jedi_issue = False
41
32 42 # -----------------------------------------------------------------------------
33 43 # Test functions
34 44 # -----------------------------------------------------------------------------
@@ -381,6 +391,8 b' class TestCompleter(unittest.TestCase):'
381 391 matches = c.all_completions("TestCl")
382 392 assert matches == ['TestClass'], jedi_status
383 393 matches = c.all_completions("TestClass.")
394 if jedi_status and jedi_issue:
395 continue
384 396 assert len(matches) > 2, jedi_status
385 397 matches = c.all_completions("TestClass.a")
386 398 assert matches == ['TestClass.a', 'TestClass.a1'], jedi_status
@@ -435,6 +447,7 b' class TestCompleter(unittest.TestCase):'
435 447 "encoding" in c.signature
436 448 ), "Signature of function was not found by completer"
437 449
450 @pytest.mark.xfail(jedi_issue, reason="Known failure on jedi<=0.18.0")
438 451 def test_deduplicate_completions(self):
439 452 """
440 453 Test that completions are correctly deduplicated (even if ranges are not the same)
@@ -7,6 +7,7 b''
7 7 #-----------------------------------------------------------------------------
8 8
9 9 import argparse
10 import sys
10 11 from nose.tools import assert_equal
11 12
12 13 from IPython.core.magic_arguments import (argument, argument_group, kwds,
@@ -74,7 +75,12 b' def foo(self, args):'
74 75
75 76
76 77 def test_magic_arguments():
77 assert_equal(magic_foo1.__doc__, '::\n\n %foo1 [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
78 # “optional arguments” was replaced with “options” in argparse help
79 # https://docs.python.org/3/whatsnew/3.10.html#argparse
80 # https://bugs.python.org/issue9694
81 options = "optional arguments" if sys.version_info < (3, 10) else "options"
82
83 assert_equal(magic_foo1.__doc__, f"::\n\n %foo1 [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n")
78 84 assert_equal(getattr(magic_foo1, 'argcmd_name', None), None)
79 85 assert_equal(real_name(magic_foo1), 'foo1')
80 86 assert_equal(magic_foo1(None, ''), argparse.Namespace(foo=None))
@@ -86,32 +92,32 b' def test_magic_arguments():'
86 92 assert_equal(magic_foo2(None, ''), argparse.Namespace())
87 93 assert hasattr(magic_foo2, 'has_arguments')
88 94
89 assert_equal(magic_foo3.__doc__, '::\n\n %foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n')
95 assert_equal(magic_foo3.__doc__, f"::\n\n %foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n")
90 96 assert_equal(getattr(magic_foo3, 'argcmd_name', None), None)
91 97 assert_equal(real_name(magic_foo3), 'foo3')
92 98 assert_equal(magic_foo3(None, ''),
93 99 argparse.Namespace(bar=None, baz=None, foo=None))
94 100 assert hasattr(magic_foo3, 'has_arguments')
95 101
96 assert_equal(magic_foo4.__doc__, '::\n\n %foo4 [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
102 assert_equal(magic_foo4.__doc__, f"::\n\n %foo4 [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n")
97 103 assert_equal(getattr(magic_foo4, 'argcmd_name', None), None)
98 104 assert_equal(real_name(magic_foo4), 'foo4')
99 105 assert_equal(magic_foo4(None, ''), argparse.Namespace())
100 106 assert hasattr(magic_foo4, 'has_arguments')
101 107
102 assert_equal(magic_foo5.__doc__, '::\n\n %frobnicate [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
108 assert_equal(magic_foo5.__doc__, f"::\n\n %frobnicate [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n")
103 109 assert_equal(getattr(magic_foo5, 'argcmd_name', None), 'frobnicate')
104 110 assert_equal(real_name(magic_foo5), 'frobnicate')
105 111 assert_equal(magic_foo5(None, ''), argparse.Namespace(foo=None))
106 112 assert hasattr(magic_foo5, 'has_arguments')
107 113
108 assert_equal(magic_magic_foo.__doc__, '::\n\n %magic_foo [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
114 assert_equal(magic_magic_foo.__doc__, f"::\n\n %magic_foo [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n")
109 115 assert_equal(getattr(magic_magic_foo, 'argcmd_name', None), None)
110 116 assert_equal(real_name(magic_magic_foo), 'magic_foo')
111 117 assert_equal(magic_magic_foo(None, ''), argparse.Namespace(foo=None))
112 118 assert hasattr(magic_magic_foo, 'has_arguments')
113 119
114 assert_equal(foo.__doc__, '::\n\n %foo [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
120 assert_equal(foo.__doc__, f"::\n\n %foo [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n")
115 121 assert_equal(getattr(foo, 'argcmd_name', None), None)
116 122 assert_equal(real_name(foo), 'foo')
117 123 assert_equal(foo(None, ''), argparse.Namespace(foo=None))
@@ -6,10 +6,11 b''
6 6
7 7
8 8 from inspect import signature, Signature, Parameter
9 import inspect
9 10 import os
11 import pytest
10 12 import re
11
12 import nose.tools as nt
13 import sys
13 14
14 15 from .. import oinspect
15 16
@@ -30,6 +31,10 b' def setup_module():'
30 31 inspector = oinspect.Inspector()
31 32
32 33
34 class SourceModuleMainTest:
35 __module__ = "__main__"
36
37
33 38 #-----------------------------------------------------------------------------
34 39 # Local utilities
35 40 #-----------------------------------------------------------------------------
@@ -38,15 +43,28 b' def setup_module():'
38 43 # defined, if any code is inserted above, the following line will need to be
39 44 # updated. Do NOT insert any whitespace between the next line and the function
40 45 # definition below.
41 THIS_LINE_NUMBER = 41 # Put here the actual number of this line
46 THIS_LINE_NUMBER = 46 # Put here the actual number of this line
47
48
49 def test_find_source_lines():
50 assert oinspect.find_source_lines(test_find_source_lines) == THIS_LINE_NUMBER + 3
51 assert oinspect.find_source_lines(type) is None
52 assert oinspect.find_source_lines(SourceModuleMainTest) is None
53 assert oinspect.find_source_lines(SourceModuleMainTest()) is None
54
42 55
43 from unittest import TestCase
56 def test_getsource():
57 assert oinspect.getsource(type) is None
58 assert oinspect.getsource(SourceModuleMainTest) is None
59 assert oinspect.getsource(SourceModuleMainTest()) is None
44 60
45 class Test(TestCase):
46 61
47 def test_find_source_lines(self):
48 self.assertEqual(oinspect.find_source_lines(Test.test_find_source_lines),
49 THIS_LINE_NUMBER+6)
62 def test_inspect_getfile_raises_exception():
63 """Check oinspect.find_file/getsource/find_source_lines expectations"""
64 with pytest.raises(TypeError):
65 inspect.getfile(type)
66 with pytest.raises(OSError if sys.version_info >= (3, 10) else TypeError):
67 inspect.getfile(SourceModuleMainTest)
50 68
51 69
52 70 # A couple of utilities to ensure these tests work the same from a source or a
@@ -56,11 +74,14 b' def pyfile(fname):'
56 74
57 75
58 76 def match_pyfiles(f1, f2):
59 nt.assert_equal(pyfile(f1), pyfile(f2))
77 assert pyfile(f1) == pyfile(f2)
60 78
61 79
62 80 def test_find_file():
63 81 match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__))
82 assert oinspect.find_file(type) is None
83 assert oinspect.find_file(SourceModuleMainTest) is None
84 assert oinspect.find_file(SourceModuleMainTest()) is None
64 85
65 86
66 87 def test_find_file_decorated1():
@@ -74,9 +95,9 b' def test_find_file_decorated1():'
74 95 @noop1
75 96 def f(x):
76 97 "My docstring"
77
98
78 99 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
79 nt.assert_equal(f.__doc__, "My docstring")
100 assert f.__doc__ == "My docstring"
80 101
81 102
82 103 def test_find_file_decorated2():
@@ -90,14 +111,14 b' def test_find_file_decorated2():'
90 111 @noop2
91 112 def f(x):
92 113 "My docstring 2"
93
114
94 115 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
95 nt.assert_equal(f.__doc__, "My docstring 2")
96
116 assert f.__doc__ == "My docstring 2"
117
97 118
98 119 def test_find_file_magic():
99 120 run = ip.find_line_magic('run')
100 nt.assert_not_equal(oinspect.find_file(run), None)
121 assert oinspect.find_file(run) is not None
101 122
102 123
103 124 # A few generic objects we can then inspect in the tests below
@@ -167,41 +188,46 b' class SerialLiar(object):'
167 188
168 189 def test_info():
169 190 "Check that Inspector.info fills out various fields as expected."
170 i = inspector.info(Call, oname='Call')
171 nt.assert_equal(i['type_name'], 'type')
172 expted_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'>
173 nt.assert_equal(i['base_class'], expted_class)
174 nt.assert_regex(i['string_form'], "<class 'IPython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>")
191 i = inspector.info(Call, oname="Call")
192 assert i["type_name"] == "type"
193 expected_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'>
194 assert i["base_class"] == expected_class
195 assert re.search(
196 "<class 'IPython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>",
197 i["string_form"],
198 )
175 199 fname = __file__
176 200 if fname.endswith(".pyc"):
177 201 fname = fname[:-1]
178 202 # case-insensitive comparison needed on some filesystems
179 203 # e.g. Windows:
180 nt.assert_equal(i['file'].lower(), compress_user(fname).lower())
181 nt.assert_equal(i['definition'], None)
182 nt.assert_equal(i['docstring'], Call.__doc__)
183 nt.assert_equal(i['source'], None)
184 nt.assert_true(i['isclass'])
185 nt.assert_equal(i['init_definition'], "Call(x, y=1)")
186 nt.assert_equal(i['init_docstring'], Call.__init__.__doc__)
204 assert i["file"].lower() == compress_user(fname).lower()
205 assert i["definition"] == None
206 assert i["docstring"] == Call.__doc__
207 assert i["source"] == None
208 assert i["isclass"] is True
209 assert i["init_definition"] == "Call(x, y=1)"
210 assert i["init_docstring"] == Call.__init__.__doc__
187 211
188 212 i = inspector.info(Call, detail_level=1)
189 nt.assert_not_equal(i['source'], None)
190 nt.assert_equal(i['docstring'], None)
213 assert i["source"] is not None
214 assert i["docstring"] == None
191 215
192 216 c = Call(1)
193 217 c.__doc__ = "Modified instance docstring"
194 218 i = inspector.info(c)
195 nt.assert_equal(i['type_name'], 'Call')
196 nt.assert_equal(i['docstring'], "Modified instance docstring")
197 nt.assert_equal(i['class_docstring'], Call.__doc__)
198 nt.assert_equal(i['init_docstring'], Call.__init__.__doc__)
199 nt.assert_equal(i['call_docstring'], Call.__call__.__doc__)
219 assert i["type_name"] == "Call"
220 assert i["docstring"] == "Modified instance docstring"
221 assert i["class_docstring"] == Call.__doc__
222 assert i["init_docstring"] == Call.__init__.__doc__
223 assert i["call_docstring"] == Call.__call__.__doc__
224
200 225
201 226 def test_class_signature():
202 info = inspector.info(HasSignature, 'HasSignature')
203 nt.assert_equal(info['init_definition'], "HasSignature(test)")
204 nt.assert_equal(info['init_docstring'], HasSignature.__init__.__doc__)
227 info = inspector.info(HasSignature, "HasSignature")
228 assert info["init_definition"] == "HasSignature(test)"
229 assert info["init_docstring"] == HasSignature.__init__.__doc__
230
205 231
206 232 def test_info_awkward():
207 233 # Just test that this doesn't throw an error.
@@ -216,7 +242,7 b' def test_info_serialliar():'
216 242
217 243 # Nested attribute access should be cut off at 100 levels deep to avoid
218 244 # infinite loops: https://github.com/ipython/ipython/issues/9122
219 nt.assert_less(fib_tracker[0], 9000)
245 assert fib_tracker[0] < 9000
220 246
221 247 def support_function_one(x, y=2, *a, **kw):
222 248 """A simple function."""
@@ -225,14 +251,16 b' def test_calldef_none():'
225 251 # We should ignore __call__ for all of these.
226 252 for obj in [support_function_one, SimpleClass().method, any, str.upper]:
227 253 i = inspector.info(obj)
228 nt.assert_is(i['call_def'], None)
254 assert i["call_def"] is None
255
229 256
230 257 def f_kwarg(pos, *, kwonly):
231 258 pass
232 259
233 260 def test_definition_kwonlyargs():
234 i = inspector.info(f_kwarg, oname='f_kwarg') # analysis:ignore
235 nt.assert_equal(i['definition'], "f_kwarg(pos, *, kwonly)")
261 i = inspector.info(f_kwarg, oname="f_kwarg") # analysis:ignore
262 assert i["definition"] == "f_kwarg(pos, *, kwonly)"
263
236 264
237 265 def test_getdoc():
238 266 class A(object):
@@ -243,34 +271,33 b' def test_getdoc():'
243 271 """standard docstring"""
244 272 def getdoc(self):
245 273 return "custom docstring"
246
274
247 275 class C(object):
248 276 """standard docstring"""
249 277 def getdoc(self):
250 278 return None
251
279
252 280 a = A()
253 281 b = B()
254 282 c = C()
255
256 nt.assert_equal(oinspect.getdoc(a), "standard docstring")
257 nt.assert_equal(oinspect.getdoc(b), "custom docstring")
258 nt.assert_equal(oinspect.getdoc(c), "standard docstring")
283
284 assert oinspect.getdoc(a) == "standard docstring"
285 assert oinspect.getdoc(b) == "custom docstring"
286 assert oinspect.getdoc(c) == "standard docstring"
259 287
260 288
261 289 def test_empty_property_has_no_source():
262 290 i = inspector.info(property(), detail_level=1)
263 nt.assert_is(i['source'], None)
291 assert i["source"] is None
264 292
265 293
266 294 def test_property_sources():
267 import posixpath
268 295 # A simple adder whose source and signature stays
269 296 # the same across Python distributions
270 297 def simple_add(a, b):
271 298 "Adds two numbers"
272 299 return a + b
273
300
274 301 class A(object):
275 302 @property
276 303 def foo(self):
@@ -278,18 +305,18 b' def test_property_sources():'
278 305
279 306 foo = foo.setter(lambda self, v: setattr(self, 'bar', v))
280 307
281 dname = property(posixpath.dirname)
282 adder = property(simple_add)
308 dname = property(oinspect.getdoc)
309 adder = property(simple_add)
283 310
284 311 i = inspector.info(A.foo, detail_level=1)
285 nt.assert_in('def foo(self):', i['source'])
286 nt.assert_in('lambda self, v:', i['source'])
312 assert "def foo(self):" in i["source"]
313 assert "lambda self, v:" in i["source"]
287 314
288 315 i = inspector.info(A.dname, detail_level=1)
289 nt.assert_in('def dirname(p)', i['source'])
290
316 assert "def getdoc(obj)" in i["source"]
317
291 318 i = inspector.info(A.adder, detail_level=1)
292 nt.assert_in('def simple_add(a, b)', i['source'])
319 assert "def simple_add(a, b)" in i["source"]
293 320
294 321
295 322 def test_property_docstring_is_in_info_for_detail_level_0():
@@ -299,15 +326,17 b' def test_property_docstring_is_in_info_for_detail_level_0():'
299 326 """This is `foobar` property."""
300 327 pass
301 328
302 ip.user_ns['a_obj'] = A()
303 nt.assert_equal(
304 'This is `foobar` property.',
305 ip.object_inspect('a_obj.foobar', detail_level=0)['docstring'])
329 ip.user_ns["a_obj"] = A()
330 assert (
331 "This is `foobar` property."
332 == ip.object_inspect("a_obj.foobar", detail_level=0)["docstring"]
333 )
306 334
307 ip.user_ns['a_cls'] = A
308 nt.assert_equal(
309 'This is `foobar` property.',
310 ip.object_inspect('a_cls.foobar', detail_level=0)['docstring'])
335 ip.user_ns["a_cls"] = A
336 assert (
337 "This is `foobar` property."
338 == ip.object_inspect("a_cls.foobar", detail_level=0)["docstring"]
339 )
311 340
312 341
313 342 def test_pdef():
@@ -359,11 +388,11 b' def test_pinfo_docstring_if_detail_and_no_source():'
359 388 def bar(self):
360 389 """ This is a docstring for Foo.bar """
361 390 pass
362 '''
363
391 '''
392
364 393 ip.run_cell(obj_def)
365 394 ip.run_cell('foo = Foo()')
366
395
367 396 with AssertNotPrints("Source:"):
368 397 with AssertPrints('Docstring:'):
369 398 ip._inspect('pinfo', 'foo', detail_level=0)
@@ -388,14 +417,14 b' def test_pinfo_magic():'
388 417 def test_init_colors():
389 418 # ensure colors are not present in signature info
390 419 info = inspector.info(HasSignature)
391 init_def = info['init_definition']
392 nt.assert_not_in('[0m', init_def)
420 init_def = info["init_definition"]
421 assert "[0m" not in init_def
393 422
394 423
395 424 def test_builtin_init():
396 425 info = inspector.info(list)
397 426 init_def = info['init_definition']
398 nt.assert_is_not_none(init_def)
427 assert init_def is not None
399 428
400 429
401 430 def test_render_signature_short():
@@ -404,7 +433,7 b' def test_render_signature_short():'
404 433 signature(short_fun),
405 434 short_fun.__name__,
406 435 )
407 nt.assert_equal(sig, 'short_fun(a=1)')
436 assert sig == "short_fun(a=1)"
408 437
409 438
410 439 def test_render_signature_long():
@@ -420,7 +449,7 b' def test_render_signature_long():'
420 449 signature(long_function),
421 450 long_function.__name__,
422 451 )
423 nt.assert_in(sig, [
452 assert sig in [
424 453 # Python >=3.9
425 454 '''\
426 455 long_function(
@@ -444,4 +473,4 b' long_function('
444 473 let_us_make_sure_this_is_looong:Union[str, NoneType]=None,
445 474 ) -> bool\
446 475 ''',
447 ])
476 ] No newline at end of file
@@ -9,6 +9,7 b' from collections import Counter, defaultdict, deque, OrderedDict'
9 9 import os
10 10 import types
11 11 import string
12 import sys
12 13 import unittest
13 14
14 15 import nose.tools as nt
@@ -118,12 +119,15 b' def test_sets():'
118 119 yield nt.assert_equal, got_output, expected_output
119 120
120 121
121 @skip_without('xxlimited')
122 @skip_without("xxlimited" if sys.version_info < (3, 10) else "xxlimited_35")
122 123 def test_pprint_heap_allocated_type():
123 124 """
124 125 Test that pprint works for heap allocated types.
125 126 """
126 import xxlimited
127 if sys.version_info < (3, 10):
128 import xxlimited
129 else:
130 import xxlimited_35 as xxlimited
127 131 output = pretty.pretty(xxlimited.Null)
128 132 nt.assert_equal(output, 'xxlimited.Null')
129 133
General Comments 0
You need to be logged in to leave comments. Login now