##// END OF EJS Templates
Statically type OInfo. (#13973)...
Matthias Bussonnier -
r28166:29b451fc merge
parent child Browse files
Show More
@@ -32,7 +32,7 b' from io import open as io_open'
32 from logging import error
32 from logging import error
33 from pathlib import Path
33 from pathlib import Path
34 from typing import Callable
34 from typing import Callable
35 from typing import List as ListType
35 from typing import List as ListType, Dict as DictType, Any as AnyType
36 from typing import Optional, Tuple
36 from typing import Optional, Tuple
37 from warnings import warn
37 from warnings import warn
38
38
@@ -90,6 +90,8 b' from IPython.utils.process import getoutput, system'
90 from IPython.utils.strdispatch import StrDispatch
90 from IPython.utils.strdispatch import StrDispatch
91 from IPython.utils.syspathcontext import prepended_to_syspath
91 from IPython.utils.syspathcontext import prepended_to_syspath
92 from IPython.utils.text import DollarFormatter, LSString, SList, format_screen
92 from IPython.utils.text import DollarFormatter, LSString, SList, format_screen
93 from IPython.core.oinspect import OInfo
94
93
95
94 sphinxify: Optional[Callable]
96 sphinxify: Optional[Callable]
95
97
@@ -1560,15 +1562,28 b' class InteractiveShell(SingletonConfigurable):'
1560 #-------------------------------------------------------------------------
1562 #-------------------------------------------------------------------------
1561 # Things related to object introspection
1563 # Things related to object introspection
1562 #-------------------------------------------------------------------------
1564 #-------------------------------------------------------------------------
1565 @staticmethod
1566 def _find_parts(oname: str) -> ListType[str]:
1567 """
1568 Given an object name, return a list of parts of this object name.
1569
1570 Basically split on docs when using attribute access,
1571 and extract the value when using square bracket.
1572
1573
1574 For example foo.bar[3].baz[x] -> foo, bar, 3, baz, x
1575
1576
1577 Returns
1578 -------
1579 parts_ok: bool
1580 wether we were properly able to parse parts.
1581 parts: list of str
1582 extracted parts
1563
1583
1564 def _ofind(self, oname, namespaces=None):
1565 """Find an object in the available namespaces.
1566
1584
1567 self._ofind(oname) -> dict with keys: found,obj,ospace,ismagic
1568
1585
1569 Has special code to detect magic functions.
1570 """
1586 """
1571 oname = oname.strip()
1572 raw_parts = oname.split(".")
1587 raw_parts = oname.split(".")
1573 parts = []
1588 parts = []
1574 parts_ok = True
1589 parts_ok = True
@@ -1590,12 +1605,31 b' class InteractiveShell(SingletonConfigurable):'
1590 parts_ok = False
1605 parts_ok = False
1591 parts.append(p)
1606 parts.append(p)
1592
1607
1608 return parts_ok, parts
1609
1610 def _ofind(self, oname: str, namespaces: DictType[str, AnyType] = None):
1611 """Find an object in the available namespaces.
1612
1613 self._ofind(oname) -> dict with keys: found,obj,ospace,ismagic
1614
1615 Has special code to detect magic functions.
1616 """
1617 oname = oname.strip()
1618 parts_ok, parts = self._find_parts(oname)
1619
1593 if (
1620 if (
1594 not oname.startswith(ESC_MAGIC)
1621 not oname.startswith(ESC_MAGIC)
1595 and not oname.startswith(ESC_MAGIC2)
1622 and not oname.startswith(ESC_MAGIC2)
1596 and not parts_ok
1623 and not parts_ok
1597 ):
1624 ):
1598 return {"found": False}
1625 return OInfo(
1626 ismagic=False,
1627 isalias=False,
1628 found=False,
1629 obj=None,
1630 namespace="",
1631 parent=None,
1632 )
1599
1633
1600 if namespaces is None:
1634 if namespaces is None:
1601 # Namespaces to search in:
1635 # Namespaces to search in:
@@ -1675,14 +1709,16 b' class InteractiveShell(SingletonConfigurable):'
1675 found = True
1709 found = True
1676 ospace = 'Interactive'
1710 ospace = 'Interactive'
1677
1711
1678 return {
1712 return OInfo(
1679 'obj':obj,
1713 **{
1680 'found':found,
1714 "obj": obj,
1681 'parent':parent,
1715 "found": found,
1682 'ismagic':ismagic,
1716 "parent": parent,
1683 'isalias':isalias,
1717 "ismagic": ismagic,
1684 'namespace':ospace
1718 "isalias": isalias,
1685 }
1719 "namespace": ospace,
1720 }
1721 )
1686
1722
1687 @staticmethod
1723 @staticmethod
1688 def _getattr_property(obj, attrname):
1724 def _getattr_property(obj, attrname):
@@ -1726,9 +1762,9 b' class InteractiveShell(SingletonConfigurable):'
1726 # Nothing helped, fall back.
1762 # Nothing helped, fall back.
1727 return getattr(obj, attrname)
1763 return getattr(obj, attrname)
1728
1764
1729 def _object_find(self, oname, namespaces=None):
1765 def _object_find(self, oname, namespaces=None) -> OInfo:
1730 """Find an object and return a struct with info about it."""
1766 """Find an object and return a struct with info about it."""
1731 return Struct(self._ofind(oname, namespaces))
1767 return self._ofind(oname, namespaces)
1732
1768
1733 def _inspect(self, meth, oname, namespaces=None, **kw):
1769 def _inspect(self, meth, oname, namespaces=None, **kw):
1734 """Generic interface to the inspector system.
1770 """Generic interface to the inspector system.
@@ -46,6 +46,19 b' from pygments import highlight'
46 from pygments.lexers import PythonLexer
46 from pygments.lexers import PythonLexer
47 from pygments.formatters import HtmlFormatter
47 from pygments.formatters import HtmlFormatter
48
48
49 from typing import Any
50 from dataclasses import dataclass
51
52
53 @dataclass
54 class OInfo:
55 ismagic: bool
56 isalias: bool
57 found: bool
58 namespace: str
59 parent: Any
60 obj: Any
61
49 def pylight(code):
62 def pylight(code):
50 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
63 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
51
64
@@ -499,7 +499,7 b' class AutocallChecker(PrefilterChecker):'
499 return None
499 return None
500
500
501 oinfo = line_info.ofind(self.shell) # This can mutate state via getattr
501 oinfo = line_info.ofind(self.shell) # This can mutate state via getattr
502 if not oinfo['found']:
502 if not oinfo.found:
503 return None
503 return None
504
504
505 ignored_funs = ['b', 'f', 'r', 'u', 'br', 'rb', 'fr', 'rf']
505 ignored_funs = ['b', 'f', 'r', 'u', 'br', 'rb', 'fr', 'rf']
@@ -508,10 +508,12 b' class AutocallChecker(PrefilterChecker):'
508 if ifun.lower() in ignored_funs and (line.startswith(ifun + "'") or line.startswith(ifun + '"')):
508 if ifun.lower() in ignored_funs and (line.startswith(ifun + "'") or line.startswith(ifun + '"')):
509 return None
509 return None
510
510
511 if callable(oinfo['obj']) \
511 if (
512 and (not self.exclude_regexp.match(line_info.the_rest)) \
512 callable(oinfo.obj)
513 and self.function_name_regexp.match(line_info.ifun):
513 and (not self.exclude_regexp.match(line_info.the_rest))
514 return self.prefilter_manager.get_handler_by_name('auto')
514 and self.function_name_regexp.match(line_info.ifun)
515 ):
516 return self.prefilter_manager.get_handler_by_name("auto")
515 else:
517 else:
516 return None
518 return None
517
519
@@ -601,7 +603,7 b' class AutoHandler(PrefilterHandler):'
601 the_rest = line_info.the_rest
603 the_rest = line_info.the_rest
602 esc = line_info.esc
604 esc = line_info.esc
603 continue_prompt = line_info.continue_prompt
605 continue_prompt = line_info.continue_prompt
604 obj = line_info.ofind(self.shell)['obj']
606 obj = line_info.ofind(self.shell).obj
605
607
606 # This should only be active for single-line input!
608 # This should only be active for single-line input!
607 if continue_prompt:
609 if continue_prompt:
@@ -25,6 +25,7 b' import sys'
25
25
26 from IPython.utils import py3compat
26 from IPython.utils import py3compat
27 from IPython.utils.encoding import get_stream_enc
27 from IPython.utils.encoding import get_stream_enc
28 from IPython.core.oinspect import OInfo
28
29
29 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
30 # Main function
31 # Main function
@@ -118,7 +119,7 b' class LineInfo(object):'
118 else:
119 else:
119 self.pre_whitespace = self.pre
120 self.pre_whitespace = self.pre
120
121
121 def ofind(self, ip):
122 def ofind(self, ip) -> OInfo:
122 """Do a full, attribute-walking lookup of the ifun in the various
123 """Do a full, attribute-walking lookup of the ifun in the various
123 namespaces for the given IPython InteractiveShell instance.
124 namespaces for the given IPython InteractiveShell instance.
124
125
@@ -177,7 +177,7 b' def test_module_without_init():'
177 try:
177 try:
178 os.makedirs(os.path.join(tmpdir, fake_module_name))
178 os.makedirs(os.path.join(tmpdir, fake_module_name))
179 s = try_import(mod=fake_module_name)
179 s = try_import(mod=fake_module_name)
180 assert s == []
180 assert s == [], f"for module {fake_module_name}"
181 finally:
181 finally:
182 sys.path.remove(tmpdir)
182 sys.path.remove(tmpdir)
183
183
@@ -25,6 +25,7 b' from os.path import join'
25 from IPython.core.error import InputRejected
25 from IPython.core.error import InputRejected
26 from IPython.core.inputtransformer import InputTransformer
26 from IPython.core.inputtransformer import InputTransformer
27 from IPython.core import interactiveshell
27 from IPython.core import interactiveshell
28 from IPython.core.oinspect import OInfo
28 from IPython.testing.decorators import (
29 from IPython.testing.decorators import (
29 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
30 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
30 )
31 )
@@ -360,7 +361,7 b' class InteractiveShellTestCase(unittest.TestCase):'
360
361
361 # Get info on line magic
362 # Get info on line magic
362 lfind = ip._ofind("lmagic")
363 lfind = ip._ofind("lmagic")
363 info = dict(
364 info = OInfo(
364 found=True,
365 found=True,
365 isalias=False,
366 isalias=False,
366 ismagic=True,
367 ismagic=True,
@@ -379,7 +380,7 b' class InteractiveShellTestCase(unittest.TestCase):'
379
380
380 # Get info on cell magic
381 # Get info on cell magic
381 find = ip._ofind("cmagic")
382 find = ip._ofind("cmagic")
382 info = dict(
383 info = OInfo(
383 found=True,
384 found=True,
384 isalias=False,
385 isalias=False,
385 ismagic=True,
386 ismagic=True,
@@ -397,9 +398,15 b' class InteractiveShellTestCase(unittest.TestCase):'
397
398
398 a = A()
399 a = A()
399
400
400 found = ip._ofind('a.foo', [('locals', locals())])
401 found = ip._ofind("a.foo", [("locals", locals())])
401 info = dict(found=True, isalias=False, ismagic=False,
402 info = OInfo(
402 namespace='locals', obj=A.foo, parent=a)
403 found=True,
404 isalias=False,
405 ismagic=False,
406 namespace="locals",
407 obj=A.foo,
408 parent=a,
409 )
403 self.assertEqual(found, info)
410 self.assertEqual(found, info)
404
411
405 def test_ofind_multiple_attribute_lookups(self):
412 def test_ofind_multiple_attribute_lookups(self):
@@ -412,9 +419,15 b' class InteractiveShellTestCase(unittest.TestCase):'
412 a.a = A()
419 a.a = A()
413 a.a.a = A()
420 a.a.a = A()
414
421
415 found = ip._ofind('a.a.a.foo', [('locals', locals())])
422 found = ip._ofind("a.a.a.foo", [("locals", locals())])
416 info = dict(found=True, isalias=False, ismagic=False,
423 info = OInfo(
417 namespace='locals', obj=A.foo, parent=a.a.a)
424 found=True,
425 isalias=False,
426 ismagic=False,
427 namespace="locals",
428 obj=A.foo,
429 parent=a.a.a,
430 )
418 self.assertEqual(found, info)
431 self.assertEqual(found, info)
419
432
420 def test_ofind_slotted_attributes(self):
433 def test_ofind_slotted_attributes(self):
@@ -424,14 +437,26 b' class InteractiveShellTestCase(unittest.TestCase):'
424 self.foo = 'bar'
437 self.foo = 'bar'
425
438
426 a = A()
439 a = A()
427 found = ip._ofind('a.foo', [('locals', locals())])
440 found = ip._ofind("a.foo", [("locals", locals())])
428 info = dict(found=True, isalias=False, ismagic=False,
441 info = OInfo(
429 namespace='locals', obj=a.foo, parent=a)
442 found=True,
443 isalias=False,
444 ismagic=False,
445 namespace="locals",
446 obj=a.foo,
447 parent=a,
448 )
430 self.assertEqual(found, info)
449 self.assertEqual(found, info)
431
450
432 found = ip._ofind('a.bar', [('locals', locals())])
451 found = ip._ofind("a.bar", [("locals", locals())])
433 info = dict(found=False, isalias=False, ismagic=False,
452 info = OInfo(
434 namespace=None, obj=None, parent=a)
453 found=False,
454 isalias=False,
455 ismagic=False,
456 namespace=None,
457 obj=None,
458 parent=a,
459 )
435 self.assertEqual(found, info)
460 self.assertEqual(found, info)
436
461
437 def test_ofind_prefers_property_to_instance_level_attribute(self):
462 def test_ofind_prefers_property_to_instance_level_attribute(self):
@@ -443,7 +468,7 b' class InteractiveShellTestCase(unittest.TestCase):'
443 a.__dict__["foo"] = "baz"
468 a.__dict__["foo"] = "baz"
444 self.assertEqual(a.foo, "bar")
469 self.assertEqual(a.foo, "bar")
445 found = ip._ofind("a.foo", [("locals", locals())])
470 found = ip._ofind("a.foo", [("locals", locals())])
446 self.assertIs(found["obj"], A.foo)
471 self.assertIs(found.obj, A.foo)
447
472
448 def test_custom_syntaxerror_exception(self):
473 def test_custom_syntaxerror_exception(self):
449 called = []
474 called = []
General Comments 0
You need to be logged in to leave comments. Login now