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 |
|
|
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 |
|
|
|
1681 |
|
|
|
1682 | 'ismagic':ismagic, | |
|
1683 |
|
|
|
1684 | 'namespace':ospace | |
|
1712 | return OInfo( | |
|
1713 | **{ | |
|
1714 | "obj": obj, | |
|
1715 | "found": found, | |
|
1716 | "parent": parent, | |
|
1717 | "ismagic": ismagic, | |
|
1718 | "isalias": isalias, | |
|
1719 | "namespace": ospace, | |
|
1685 | 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 |
|
|
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 |
|
|
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 |
|
|
|
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) |
|
|
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 = |
|
|
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 = |
|
|
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( |
|
|
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( |
|
|
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( |
|
|
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( |
|
|
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 |
|
|
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