##// 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 32 from logging import error
33 33 from pathlib import Path
34 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 36 from typing import Optional, Tuple
37 37 from warnings import warn
38 38
@@ -90,6 +90,8 b' from IPython.utils.process import getoutput, system'
90 90 from IPython.utils.strdispatch import StrDispatch
91 91 from IPython.utils.syspathcontext import prepended_to_syspath
92 92 from IPython.utils.text import DollarFormatter, LSString, SList, format_screen
93 from IPython.core.oinspect import OInfo
94
93 95
94 96 sphinxify: Optional[Callable]
95 97
@@ -1560,15 +1562,28 b' class InteractiveShell(SingletonConfigurable):'
1560 1562 #-------------------------------------------------------------------------
1561 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 1587 raw_parts = oname.split(".")
1573 1588 parts = []
1574 1589 parts_ok = True
@@ -1590,12 +1605,31 b' class InteractiveShell(SingletonConfigurable):'
1590 1605 parts_ok = False
1591 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 1620 if (
1594 1621 not oname.startswith(ESC_MAGIC)
1595 1622 and not oname.startswith(ESC_MAGIC2)
1596 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 1634 if namespaces is None:
1601 1635 # Namespaces to search in:
@@ -1675,14 +1709,16 b' class InteractiveShell(SingletonConfigurable):'
1675 1709 found = True
1676 1710 ospace = 'Interactive'
1677 1711
1678 return {
1679 'obj':obj,
1680 'found':found,
1681 'parent':parent,
1682 'ismagic':ismagic,
1683 'isalias':isalias,
1684 'namespace':ospace
1685 }
1712 return OInfo(
1713 **{
1714 "obj": obj,
1715 "found": found,
1716 "parent": parent,
1717 "ismagic": ismagic,
1718 "isalias": isalias,
1719 "namespace": ospace,
1720 }
1721 )
1686 1722
1687 1723 @staticmethod
1688 1724 def _getattr_property(obj, attrname):
@@ -1726,9 +1762,9 b' class InteractiveShell(SingletonConfigurable):'
1726 1762 # Nothing helped, fall back.
1727 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 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 1769 def _inspect(self, meth, oname, namespaces=None, **kw):
1734 1770 """Generic interface to the inspector system.
@@ -46,6 +46,19 b' from pygments import highlight'
46 46 from pygments.lexers import PythonLexer
47 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 62 def pylight(code):
50 63 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
51 64
@@ -499,7 +499,7 b' class AutocallChecker(PrefilterChecker):'
499 499 return None
500 500
501 501 oinfo = line_info.ofind(self.shell) # This can mutate state via getattr
502 if not oinfo['found']:
502 if not oinfo.found:
503 503 return None
504 504
505 505 ignored_funs = ['b', 'f', 'r', 'u', 'br', 'rb', 'fr', 'rf']
@@ -508,10 +508,12 b' class AutocallChecker(PrefilterChecker):'
508 508 if ifun.lower() in ignored_funs and (line.startswith(ifun + "'") or line.startswith(ifun + '"')):
509 509 return None
510 510
511 if callable(oinfo['obj']) \
512 and (not self.exclude_regexp.match(line_info.the_rest)) \
513 and self.function_name_regexp.match(line_info.ifun):
514 return self.prefilter_manager.get_handler_by_name('auto')
511 if (
512 callable(oinfo.obj)
513 and (not self.exclude_regexp.match(line_info.the_rest))
514 and self.function_name_regexp.match(line_info.ifun)
515 ):
516 return self.prefilter_manager.get_handler_by_name("auto")
515 517 else:
516 518 return None
517 519
@@ -601,7 +603,7 b' class AutoHandler(PrefilterHandler):'
601 603 the_rest = line_info.the_rest
602 604 esc = line_info.esc
603 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 608 # This should only be active for single-line input!
607 609 if continue_prompt:
@@ -25,6 +25,7 b' import sys'
25 25
26 26 from IPython.utils import py3compat
27 27 from IPython.utils.encoding import get_stream_enc
28 from IPython.core.oinspect import OInfo
28 29
29 30 #-----------------------------------------------------------------------------
30 31 # Main function
@@ -118,7 +119,7 b' class LineInfo(object):'
118 119 else:
119 120 self.pre_whitespace = self.pre
120 121
121 def ofind(self, ip):
122 def ofind(self, ip) -> OInfo:
122 123 """Do a full, attribute-walking lookup of the ifun in the various
123 124 namespaces for the given IPython InteractiveShell instance.
124 125
@@ -177,7 +177,7 b' def test_module_without_init():'
177 177 try:
178 178 os.makedirs(os.path.join(tmpdir, fake_module_name))
179 179 s = try_import(mod=fake_module_name)
180 assert s == []
180 assert s == [], f"for module {fake_module_name}"
181 181 finally:
182 182 sys.path.remove(tmpdir)
183 183
@@ -25,6 +25,7 b' from os.path import join'
25 25 from IPython.core.error import InputRejected
26 26 from IPython.core.inputtransformer import InputTransformer
27 27 from IPython.core import interactiveshell
28 from IPython.core.oinspect import OInfo
28 29 from IPython.testing.decorators import (
29 30 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
30 31 )
@@ -360,7 +361,7 b' class InteractiveShellTestCase(unittest.TestCase):'
360 361
361 362 # Get info on line magic
362 363 lfind = ip._ofind("lmagic")
363 info = dict(
364 info = OInfo(
364 365 found=True,
365 366 isalias=False,
366 367 ismagic=True,
@@ -379,7 +380,7 b' class InteractiveShellTestCase(unittest.TestCase):'
379 380
380 381 # Get info on cell magic
381 382 find = ip._ofind("cmagic")
382 info = dict(
383 info = OInfo(
383 384 found=True,
384 385 isalias=False,
385 386 ismagic=True,
@@ -397,9 +398,15 b' class InteractiveShellTestCase(unittest.TestCase):'
397 398
398 399 a = A()
399 400
400 found = ip._ofind('a.foo', [('locals', locals())])
401 info = dict(found=True, isalias=False, ismagic=False,
402 namespace='locals', obj=A.foo, parent=a)
401 found = ip._ofind("a.foo", [("locals", locals())])
402 info = OInfo(
403 found=True,
404 isalias=False,
405 ismagic=False,
406 namespace="locals",
407 obj=A.foo,
408 parent=a,
409 )
403 410 self.assertEqual(found, info)
404 411
405 412 def test_ofind_multiple_attribute_lookups(self):
@@ -412,9 +419,15 b' class InteractiveShellTestCase(unittest.TestCase):'
412 419 a.a = A()
413 420 a.a.a = A()
414 421
415 found = ip._ofind('a.a.a.foo', [('locals', locals())])
416 info = dict(found=True, isalias=False, ismagic=False,
417 namespace='locals', obj=A.foo, parent=a.a.a)
422 found = ip._ofind("a.a.a.foo", [("locals", locals())])
423 info = OInfo(
424 found=True,
425 isalias=False,
426 ismagic=False,
427 namespace="locals",
428 obj=A.foo,
429 parent=a.a.a,
430 )
418 431 self.assertEqual(found, info)
419 432
420 433 def test_ofind_slotted_attributes(self):
@@ -424,14 +437,26 b' class InteractiveShellTestCase(unittest.TestCase):'
424 437 self.foo = 'bar'
425 438
426 439 a = A()
427 found = ip._ofind('a.foo', [('locals', locals())])
428 info = dict(found=True, isalias=False, ismagic=False,
429 namespace='locals', obj=a.foo, parent=a)
440 found = ip._ofind("a.foo", [("locals", locals())])
441 info = OInfo(
442 found=True,
443 isalias=False,
444 ismagic=False,
445 namespace="locals",
446 obj=a.foo,
447 parent=a,
448 )
430 449 self.assertEqual(found, info)
431 450
432 found = ip._ofind('a.bar', [('locals', locals())])
433 info = dict(found=False, isalias=False, ismagic=False,
434 namespace=None, obj=None, parent=a)
451 found = ip._ofind("a.bar", [("locals", locals())])
452 info = OInfo(
453 found=False,
454 isalias=False,
455 ismagic=False,
456 namespace=None,
457 obj=None,
458 parent=a,
459 )
435 460 self.assertEqual(found, info)
436 461
437 462 def test_ofind_prefers_property_to_instance_level_attribute(self):
@@ -443,7 +468,7 b' class InteractiveShellTestCase(unittest.TestCase):'
443 468 a.__dict__["foo"] = "baz"
444 469 self.assertEqual(a.foo, "bar")
445 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 473 def test_custom_syntaxerror_exception(self):
449 474 called = []
General Comments 0
You need to be logged in to leave comments. Login now