##// END OF EJS Templates
Cleanup and type annotate completer.py...
Matthias Bussonnier -
Show More
@@ -0,0 +1,4 b''
1 [mypy]
2 python_version = 3.6
3 ignore_missing_imports = True
4 follow_imports = silent
@@ -40,6 +40,7 b' install:'
40 40 - pip install trio curio --upgrade --upgrade-strategy eager
41 41 - pip install pytest 'matplotlib !=3.2.0' mypy
42 42 - pip install codecov check-manifest --upgrade
43 - pip install mypy
43 44
44 45 script:
45 46 - check-manifest
@@ -50,7 +51,8 b' script:'
50 51 fi
51 52 - cd /tmp && iptest --coverage xml && cd -
52 53 - pytest IPython
53 - mypy --ignore-missing-imports -m IPython.terminal.ptutils
54 - mypy IPython/terminal/ptutils.py
55 - mypy IPython/core/c*.py
54 56 # On the latest Python (on Linux) only, make sure that the docs build.
55 57 - |
56 58 if [[ "$TRAVIS_PYTHON_VERSION" == "3.7" ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
@@ -126,7 +126,7 b' import warnings'
126 126 from contextlib import contextmanager
127 127 from importlib import import_module
128 128 from types import SimpleNamespace
129 from typing import Iterable, Iterator, List, Tuple
129 from typing import Iterable, Iterator, List, Tuple, Union, Any, Sequence, Dict, NamedTuple, Pattern, Optional
130 130
131 131 from IPython.core.error import TryNext
132 132 from IPython.core.inputtransformer2 import ESC_MAGIC
@@ -745,7 +745,7 b' def get__all__entries(obj):'
745 745 return [w for w in words if isinstance(w, str)]
746 746
747 747
748 def match_dict_keys(keys: List[str], prefix: str, delims: str):
748 def match_dict_keys(keys: List[Union[str, bytes]], prefix: str, delims: str) -> Tuple[str, int, List[str]]:
749 749 """Used by dict_key_matches, matching the prefix to a list of keys
750 750
751 751 Parameters
@@ -766,22 +766,25 b' def match_dict_keys(keys: List[str], prefix: str, delims: str):'
766 766 ``matches`` a list of replacement/completion
767 767
768 768 """
769 keys = [k for k in keys if isinstance(k, (str, bytes))]
769 770 if not prefix:
770 return None, 0, [repr(k) for k in keys
771 return '', 0, [repr(k) for k in keys
771 772 if isinstance(k, (str, bytes))]
772 773 quote_match = re.search('["\']', prefix)
774 assert quote_match is not None # silence mypy
773 775 quote = quote_match.group()
774 776 try:
775 777 prefix_str = eval(prefix + quote, {})
776 778 except Exception:
777 return None, 0, []
779 return '', 0, []
778 780
779 781 pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$'
780 782 token_match = re.search(pattern, prefix, re.UNICODE)
783 assert token_match is not None # silence mypy
781 784 token_start = token_match.start()
782 785 token_prefix = token_match.group()
783 786
784 matched = []
787 matched:List[str] = []
785 788 for key in keys:
786 789 try:
787 790 if not key.startswith(prefix_str):
@@ -794,14 +797,6 b' def match_dict_keys(keys: List[str], prefix: str, delims: str):'
794 797 rem = key[len(prefix_str):]
795 798 # force repr wrapped in '
796 799 rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"')
797 if rem_repr.startswith('u') and prefix[0] not in 'uU':
798 # Found key is unicode, but prefix is Py2 string.
799 # Therefore attempt to interpret key as string.
800 try:
801 rem_repr = repr(rem.encode('ascii') + '"')
802 except UnicodeEncodeError:
803 continue
804
805 800 rem_repr = rem_repr[1 + rem_repr.index("'"):-2]
806 801 if quote == '"':
807 802 # The entered prefix is quoted with ",
@@ -887,9 +882,8 b' def _safe_isinstance(obj, module, class_name):'
887 882 return (module in sys.modules and
888 883 isinstance(obj, getattr(import_module(module), class_name)))
889 884
890
891 def back_unicode_name_matches(text):
892 u"""Match unicode characters back to unicode name
885 def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]:
886 """Match unicode characters back to unicode name
893 887
894 888 This does ``☃`` -> ``\\snowman``
895 889
@@ -898,25 +892,29 b' def back_unicode_name_matches(text):'
898 892
899 893 This will not either back-complete standard sequences like \\n, \\b ...
900 894
901 Used on Python 3 only.
895 Returns
896 =======
897
898 Return a tuple of
899
902 900 """
903 901 if len(text)<2:
904 return u'', ()
902 return '', ()
905 903 maybe_slash = text[-2]
906 904 if maybe_slash != '\\':
907 return u'', ()
905 return '', ()
908 906
909 907 char = text[-1]
910 908 # no expand on quote for completion in strings.
911 909 # nor backcomplete standard ascii keys
912 910 if char in string.ascii_letters or char in ['"',"'"]:
913 return u'', ()
911 return '', ()
914 912 try :
915 913 unic = unicodedata.name(char)
916 914 return '\\'+char,['\\'+unic]
917 915 except KeyError:
918 916 pass
919 return u'', ()
917 return '', ()
920 918
921 919 def back_latex_name_matches(text:str):
922 920 """Match latex characters back to unicode name
@@ -1002,9 +1000,19 b' def _make_signature(completion)-> str:'
1002 1000 return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures()
1003 1001 for p in signature.defined_names()) if f])
1004 1002
1003
1004 class _CompleteResult(NamedTuple):
1005 matched_text : str
1006 matches: Sequence[str]
1007 matches_origin: Sequence[str]
1008 jedi_matches: Any
1009
1010
1005 1011 class IPCompleter(Completer):
1006 1012 """Extension of the completer class with IPython-specific features"""
1007 1013
1014 __dict_key_regexps: Optional[Dict[bool,Pattern]] = None
1015
1008 1016 @observe('greedy')
1009 1017 def _greedy_changed(self, change):
1010 1018 """update the splitter and readline delims when greedy is changed"""
@@ -1143,7 +1151,7 b' class IPCompleter(Completer):'
1143 1151 self._unicode_names = None
1144 1152
1145 1153 @property
1146 def matchers(self):
1154 def matchers(self) -> List[Any]:
1147 1155 """All active matcher routines for completion"""
1148 1156 if self.dict_keys_only:
1149 1157 return [self.dict_key_matches]
@@ -1165,7 +1173,7 b' class IPCompleter(Completer):'
1165 1173 self.dict_key_matches,
1166 1174 ]
1167 1175
1168 def all_completions(self, text) -> List[str]:
1176 def all_completions(self, text:str) -> List[str]:
1169 1177 """
1170 1178 Wrapper around the completion methods for the benefit of emacs.
1171 1179 """
@@ -1176,14 +1184,14 b' class IPCompleter(Completer):'
1176 1184
1177 1185 return self.complete(text)[1]
1178 1186
1179 def _clean_glob(self, text):
1187 def _clean_glob(self, text:str):
1180 1188 return self.glob("%s*" % text)
1181 1189
1182 def _clean_glob_win32(self,text):
1190 def _clean_glob_win32(self,text:str):
1183 1191 return [f.replace("\\","/")
1184 1192 for f in self.glob("%s*" % text)]
1185 1193
1186 def file_matches(self, text):
1194 def file_matches(self, text:str)->List[str]:
1187 1195 """Match filenames, expanding ~USER type strings.
1188 1196
1189 1197 Most of the seemingly convoluted logic in this completer is an
@@ -1265,7 +1273,7 b' class IPCompleter(Completer):'
1265 1273 # Mark directories in input list by appending '/' to their names.
1266 1274 return [x+'/' if os.path.isdir(x) else x for x in matches]
1267 1275
1268 def magic_matches(self, text):
1276 def magic_matches(self, text:str):
1269 1277 """Match magics"""
1270 1278 # Get all shell magics now rather than statically, so magics loaded at
1271 1279 # runtime show up too.
@@ -1356,7 +1364,7 b' class IPCompleter(Completer):'
1356 1364 if color.startswith(prefix) ]
1357 1365 return []
1358 1366
1359 def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str):
1367 def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str) -> Iterable[Any]:
1360 1368 """
1361 1369
1362 1370 Return a list of :any:`jedi.api.Completions` object from a ``text`` and
@@ -1430,7 +1438,7 b' class IPCompleter(Completer):'
1430 1438 else:
1431 1439 return []
1432 1440
1433 def python_matches(self, text):
1441 def python_matches(self, text:str)->list[str]:
1434 1442 """Match attributes or global python names"""
1435 1443 if "." in text:
1436 1444 try:
@@ -1512,7 +1520,7 b' class IPCompleter(Completer):'
1512 1520
1513 1521 return list(set(ret))
1514 1522
1515 def python_func_kw_matches(self,text):
1523 def python_func_kw_matches(self, text):
1516 1524 """Match named parameters (kwargs) of the last open function"""
1517 1525
1518 1526 if "." in text: # a parameter cannot be dotted
@@ -1588,30 +1596,33 b' class IPCompleter(Completer):'
1588 1596
1589 1597 return argMatches
1590 1598
1591 def dict_key_matches(self, text):
1599 @staticmethod
1600 def _get_keys(obj: Any) -> List[Any]:
1601 # Objects can define their own completions by defining an
1602 # _ipy_key_completions_() method.
1603 method = get_real_method(obj, '_ipython_key_completions_')
1604 if method is not None:
1605 return method()
1606
1607 # Special case some common in-memory dict-like types
1608 if isinstance(obj, dict) or\
1609 _safe_isinstance(obj, 'pandas', 'DataFrame'):
1610 try:
1611 return list(obj.keys())
1612 except Exception:
1613 return []
1614 elif _safe_isinstance(obj, 'numpy', 'ndarray') or\
1615 _safe_isinstance(obj, 'numpy', 'void'):
1616 return obj.dtype.names or []
1617 return []
1618
1619 def dict_key_matches(self, text:str) -> List[str]:
1592 1620 "Match string keys in a dictionary, after e.g. 'foo[' "
1593 def get_keys(obj):
1594 # Objects can define their own completions by defining an
1595 # _ipy_key_completions_() method.
1596 method = get_real_method(obj, '_ipython_key_completions_')
1597 if method is not None:
1598 return method()
1599
1600 # Special case some common in-memory dict-like types
1601 if isinstance(obj, dict) or\
1602 _safe_isinstance(obj, 'pandas', 'DataFrame'):
1603 try:
1604 return list(obj.keys())
1605 except Exception:
1606 return []
1607 elif _safe_isinstance(obj, 'numpy', 'ndarray') or\
1608 _safe_isinstance(obj, 'numpy', 'void'):
1609 return obj.dtype.names or []
1610 return []
1611 1621
1612 try:
1622
1623 if self.__dict_key_regexps is not None:
1613 1624 regexps = self.__dict_key_regexps
1614 except AttributeError:
1625 else:
1615 1626 dict_key_re_fmt = r'''(?x)
1616 1627 ( # match dict-referring expression wrt greedy setting
1617 1628 %s
@@ -1651,7 +1662,7 b' class IPCompleter(Completer):'
1651 1662 except Exception:
1652 1663 return []
1653 1664
1654 keys = get_keys(obj)
1665 keys = self._get_keys(obj)
1655 1666 if not keys:
1656 1667 return keys
1657 1668 closing_quote, token_offset, matches = match_dict_keys(keys, prefix, self.splitter.delims)
@@ -1696,16 +1707,15 b' class IPCompleter(Completer):'
1696 1707
1697 1708 return [leading + k + suf for k in matches]
1698 1709
1699 def unicode_name_matches(self, text):
1700 u"""Match Latex-like syntax for unicode characters base
1710 @staticmethod
1711 def unicode_name_matches(text:str) -> Tuple[str, List[str]] :
1712 """Match Latex-like syntax for unicode characters base
1701 1713 on the name of the character.
1702 1714
1703 1715 This does ``\\GREEK SMALL LETTER ETA`` -> ``η``
1704 1716
1705 1717 Works only on valid python 3 identifier, or on combining characters that
1706 1718 will combine to form a valid identifier.
1707
1708 Used on Python 3 only.
1709 1719 """
1710 1720 slashpos = text.rfind('\\')
1711 1721 if slashpos > -1:
@@ -1717,7 +1727,7 b' class IPCompleter(Completer):'
1717 1727 return '\\'+s,[unic]
1718 1728 except KeyError:
1719 1729 pass
1720 return u'', []
1730 return '', []
1721 1731
1722 1732
1723 1733 def latex_matches(self, text):
@@ -1839,6 +1849,7 b' class IPCompleter(Completer):'
1839 1849 category=ProvisionalCompleterWarning, stacklevel=2)
1840 1850
1841 1851 seen = set()
1852 profiler:Optional[cProfile.Profile]
1842 1853 try:
1843 1854 if self.profile_completions:
1844 1855 import cProfile
@@ -1864,7 +1875,7 b' class IPCompleter(Completer):'
1864 1875 print("Writing profiler output to", output_path)
1865 1876 profiler.dump_stats(output_path)
1866 1877
1867 def _completions(self, full_text: str, offset: int, *, _timeout)->Iterator[Completion]:
1878 def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]:
1868 1879 """
1869 1880 Core completion module.Same signature as :any:`completions`, with the
1870 1881 extra `timeout` parameter (in seconds).
@@ -1949,7 +1960,7 b' class IPCompleter(Completer):'
1949 1960 yield Completion(start=start_offset, end=offset, text=m, _origin=t, signature='', type='<unknown>')
1950 1961
1951 1962
1952 def complete(self, text=None, line_buffer=None, cursor_pos=None):
1963 def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]:
1953 1964 """Find completions for the given text and line context.
1954 1965
1955 1966 Note that both the text and the line_buffer are optional, but at least
@@ -1973,9 +1984,9 b' class IPCompleter(Completer):'
1973 1984
1974 1985 Returns
1975 1986 -------
1987 Tuple of two items:
1976 1988 text : str
1977 1989 Text that was actually used in the completion.
1978
1979 1990 matches : list
1980 1991 A list of completion matches.
1981 1992
@@ -1995,7 +2006,7 b' class IPCompleter(Completer):'
1995 2006 return self._complete(line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0)[:2]
1996 2007
1997 2008 def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None,
1998 full_text=None) -> Tuple[str, List[str], List[str], Iterable[_FakeJediCompletion]]:
2009 full_text=None) -> _CompleteResult:
1999 2010 """
2000 2011
2001 2012 Like complete but can also returns raw jedi completions as well as the
@@ -2008,8 +2019,19 b' class IPCompleter(Completer):'
2008 2019 caller) as the offset in the ``text`` or ``line_buffer``, or as the
2009 2020 ``column`` when passing multiline strings this could/should be renamed
2010 2021 but would add extra noise.
2022
2023 Return
2024 ======
2025
2026 A tuple of N elements which are (likely):
2027
2028 matched_text: ? the text that the complete matched
2029 matches: list of completions ?
2030 matches_origin: ? list same lenght as matches, and where each completion came from
2031 jedi_matches: list of Jedi matches, have it's own structure.
2011 2032 """
2012 2033
2034
2013 2035 # if the cursor position isn't given, the only sane assumption we can
2014 2036 # make is that it's at the end of the line (the common case)
2015 2037 if cursor_pos is None:
@@ -2027,17 +2049,16 b' class IPCompleter(Completer):'
2027 2049 if self.backslash_combining_completions:
2028 2050 # allow deactivation of these on windows.
2029 2051 base_text = text if not line_buffer else line_buffer[:cursor_pos]
2030 latex_text, latex_matches = self.latex_matches(base_text)
2031 if latex_matches:
2032 return latex_text, latex_matches, ['latex_matches']*len(latex_matches), ()
2033 name_text = ''
2034 name_matches = []
2035 # need to add self.fwd_unicode_match() function here when done
2036 for meth in (self.unicode_name_matches, back_latex_name_matches, back_unicode_name_matches, self.fwd_unicode_match):
2052
2053 for meth in (self.latex_matches,
2054 self.unicode_name_matches,
2055 back_latex_name_matches,
2056 back_unicode_name_matches,
2057 self.fwd_unicode_match):
2037 2058 name_text, name_matches = meth(base_text)
2038 2059 if name_text:
2039 return name_text, name_matches[:MATCHES_LIMIT], \
2040 [meth.__qualname__]*min(len(name_matches), MATCHES_LIMIT), ()
2060 return _CompleteResult(name_text, name_matches[:MATCHES_LIMIT], \
2061 [meth.__qualname__]*min(len(name_matches), MATCHES_LIMIT), ())
2041 2062
2042 2063
2043 2064 # If no line buffer is given, assume the input text is all there was
@@ -2052,7 +2073,7 b' class IPCompleter(Completer):'
2052 2073 matches = list(matcher(line_buffer))[:MATCHES_LIMIT]
2053 2074 if matches:
2054 2075 origins = [matcher.__qualname__] * len(matches)
2055 return text, matches, origins, ()
2076 return _CompleteResult(text, matches, origins, ())
2056 2077
2057 2078 # Start with a clean slate of completions
2058 2079 matches = []
@@ -2061,7 +2082,7 b' class IPCompleter(Completer):'
2061 2082 # different types of objects. The rlcomplete() method could then
2062 2083 # simply collapse the dict into a list for readline, but we'd have
2063 2084 # richer completion semantics in other environments.
2064 completions = ()
2085 completions:Iterable[Any] = []
2065 2086 if self.use_jedi:
2066 2087 if not full_text:
2067 2088 full_text = line_buffer
@@ -2105,9 +2126,38 b' class IPCompleter(Completer):'
2105 2126
2106 2127 self.matches = _matches
2107 2128
2108 return text, _matches, origins, completions
2129 return _CompleteResult(text, _matches, origins, completions)
2109 2130
2110 def fwd_unicode_match(self, text:str) -> Tuple[str, list]:
2131 def fwd_unicode_match(self, text:str) -> Tuple[str, Iterable[str]]:
2132 """
2133
2134 Forward match a string starting with a backslash with a list of
2135 potential Unicode completions.
2136
2137 Will compute list list of Unicode character names on first call and cache it.
2138
2139 Return
2140 ======
2141
2142 At tuple with:
2143 - matched text (empty if no matches)
2144 - list of potential completions, empty tuple otherwise)
2145 """
2146 # TODO: self.unicode_names is here a list we traverse each time with ~100k elements.
2147 # We could do a faster match using a Trie.
2148
2149 # Using pygtrie the follwing seem to work:
2150
2151 # s = PrefixSet()
2152
2153 # for c in range(0,0x10FFFF + 1):
2154 # try:
2155 # s.add(unicodedata.name(chr(c)))
2156 # except ValueError:
2157 # pass
2158 # [''.join(k) for k in s.iter(prefix)]
2159
2160 # But need to be timed and adds an extra dependency.
2111 2161
2112 2162 slashpos = text.rfind('\\')
2113 2163 # if text starts with slash
@@ -2126,7 +2176,7 b' class IPCompleter(Completer):'
2126 2176
2127 2177 # if text does not start with slash
2128 2178 else:
2129 return u'', ()
2179 return '', ()
2130 2180
2131 2181 @property
2132 2182 def unicode_names(self) -> List[str]:
@@ -212,9 +212,8 b' class TestCompleter(unittest.TestCase):'
212 212 keys = random.sample(latex_symbols.keys(), 10)
213 213 for k in keys:
214 214 text, matches = ip.complete(k)
215 nt.assert_equal(len(matches), 1)
216 215 nt.assert_equal(text, k)
217 nt.assert_equal(matches[0], latex_symbols[k])
216 nt.assert_equal(matches, [latex_symbols[k]])
218 217 # Test a more complex line
219 218 text, matches = ip.complete("print(\\alpha")
220 219 nt.assert_equal(text, "\\alpha")
@@ -250,8 +249,8 b' class TestCompleter(unittest.TestCase):'
250 249 ip = get_ipython()
251 250
252 251 name, matches = ip.complete("\\ROMAN NUMERAL FIVE")
253 nt.assert_equal(len(matches), 1)
254 nt.assert_equal(matches[0], "")
252 nt.assert_equal(matches, ["Ⅴ"] ) # This is not a V
253 nt.assert_equal(matches, ["\u2164"] ) # same as above but explicit.
255 254
256 255 @nt.nottest # now we have a completion for \jmath
257 256 @decorators.knownfailureif(
@@ -5,6 +5,7 b' include setupbase.py'
5 5 include setupegg.py
6 6 include MANIFEST.in
7 7 include pytest.ini
8 include mypy.ini
8 9 include .mailmap
9 10
10 11 recursive-exclude tools *
General Comments 0
You need to be logged in to leave comments. Login now