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