##// END OF EJS Templates
Improve type hinting and documentation
krassowski -
Show More
@@ -0,0 +1,7 b''
1 # encoding: utf-8
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5 import os
6
7 GENERATING_DOCUMENTATION = os.environ.get("IN_SPHINX_RUN", None) == "True"
@@ -109,21 +109,48 b' The matchers API is provisional and subject to change without notice.'
109
109
110 The built-in matchers include:
110 The built-in matchers include:
111
111
112 - ``IPCompleter.dict_key_matcher``: dictionary key completions,
112 - :any:`IPCompleter.dict_key_matcher`: dictionary key completions,
113 - ``IPCompleter.magic_matcher``: completions for magics,
113 - :any:`IPCompleter.magic_matcher`: completions for magics,
114 - ``IPCompleter.unicode_name_matcher``, ``IPCompleter.fwd_unicode_matcher`` and ``IPCompleter.latex_matcher``: see `Forward latex/unicode completion`_,
114 - :any:`IPCompleter.unicode_name_matcher`,
115 - ``back_unicode_name_matcher`` and ``back_latex_name_matcher``: see `Backward latex completion`_,
115 :any:`IPCompleter.fwd_unicode_matcher`
116 - ``IPCompleter.file_matcher``: paths to files and directories,
116 and :any:`IPCompleter.latex_name_matcher`: see `Forward latex/unicode completion`_,
117 - ``IPCompleter.python_func_kw_matcher`` - function keywords,
117 - :any:`back_unicode_name_matcher` and :any:`back_latex_name_matcher`: see `Backward latex completion`_,
118 - ``IPCompleter.python_matches`` - globals and attributes (v1 API),
118 - :any:`IPCompleter.file_matcher`: paths to files and directories,
119 - :any:`IPCompleter.python_func_kw_matcher` - function keywords,
120 - :any:`IPCompleter.python_matches` - globals and attributes (v1 API),
119 - ``IPCompleter.jedi_matcher`` - static analysis with Jedi,
121 - ``IPCompleter.jedi_matcher`` - static analysis with Jedi,
120 - ``IPCompleter.custom_completer_matcher`` - pluggable completer with a default implementation in any:`core.InteractiveShell`
122 - :any:`IPCompleter.custom_completer_matcher` - pluggable completer with a default
121 which uses uses IPython hooks system (`complete_command`) with string dispatch (including regular expressions).
123 implementation in :any:`InteractiveShell` which uses IPython hooks system
122 Differently to other matchers, ``custom_completer_matcher`` will not suppress Jedi results to match
124 (`complete_command`) with string dispatch (including regular expressions).
123 behaviour in earlier IPython versions.
125 Differently to other matchers, ``custom_completer_matcher`` will not suppress
126 Jedi results to match behaviour in earlier IPython versions.
124
127
125 Custom matchers can be added by appending to ``IPCompleter.custom_matchers`` list.
128 Custom matchers can be added by appending to ``IPCompleter.custom_matchers`` list.
126
129
130 Matcher API
131 -----------
132
133 Simplifying some details, the ``Matcher`` interface can described as
134
135 .. highlight::
136
137 MatcherAPIv1 = Callable[[str], list[str]]
138 MatcherAPIv2 = Callable[[CompletionContext], SimpleMatcherResult]
139
140 Matcher = MatcherAPIv1 | MatcherAPIv2
141
142 The ``MatcherAPIv1`` reflects the matcher API as available prior to IPython 8.6.0
143 and remains supported as a simplest way for generating completions. This is also
144 currently the only API supported by the IPython hooks system `complete_command`.
145
146 To distinguish between matcher versions ``matcher_api_version`` attribute is used.
147 More precisely, the API allows to omit ``matcher_api_version`` for v1 Matchers,
148 and requires a literal ``2`` for v2 Matchers.
149
150 Once the API stabilises future versions may relax the requirement for specifying
151 ``matcher_api_version`` by switching to :any:`functools.singledispatch`, therefore
152 please do not rely on the presence of ``matcher_api_version`` for any purposes.
153
127 Suppression of competing matchers
154 Suppression of competing matchers
128 ---------------------------------
155 ---------------------------------
129
156
@@ -137,6 +164,9 b' the matcher with higher priority will be returned.'
137 Sometimes it is desirable to suppress most but not all other matchers;
164 Sometimes it is desirable to suppress most but not all other matchers;
138 this can be achieved by adding a list of identifiers of matchers which
165 this can be achieved by adding a list of identifiers of matchers which
139 should not be suppressed to ``MatcherResult`` under ``do_not_suppress`` key.
166 should not be suppressed to ``MatcherResult`` under ``do_not_suppress`` key.
167
168 The suppression behaviour can is user-configurable via
169 :any:`IPCompleter.suppress_competing_matchers`.
140 """
170 """
141
171
142
172
@@ -146,7 +176,7 b' should not be suppressed to ``MatcherResult`` under ``do_not_suppress`` key.'
146 # Some of this code originated from rlcompleter in the Python standard library
176 # Some of this code originated from rlcompleter in the Python standard library
147 # Copyright (C) 2001 Python Software Foundation, www.python.org
177 # Copyright (C) 2001 Python Software Foundation, www.python.org
148
178
149
179 from __future__ import annotations
150 import builtins as builtin_mod
180 import builtins as builtin_mod
151 import glob
181 import glob
152 import inspect
182 import inspect
@@ -176,9 +206,9 b' from typing import ('
176 NamedTuple,
206 NamedTuple,
177 Pattern,
207 Pattern,
178 Optional,
208 Optional,
179 Callable,
180 TYPE_CHECKING,
209 TYPE_CHECKING,
181 Set,
210 Set,
211 Literal,
182 )
212 )
183
213
184 from IPython.core.error import TryNext
214 from IPython.core.error import TryNext
@@ -187,7 +217,9 b' from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol'
187 from IPython.core.oinspect import InspectColors
217 from IPython.core.oinspect import InspectColors
188 from IPython.testing.skipdoctest import skip_doctest
218 from IPython.testing.skipdoctest import skip_doctest
189 from IPython.utils import generics
219 from IPython.utils import generics
220 from IPython.utils.decorators import sphinx_options
190 from IPython.utils.dir2 import dir2, get_real_method
221 from IPython.utils.dir2 import dir2, get_real_method
222 from IPython.utils.docs import GENERATING_DOCUMENTATION
191 from IPython.utils.path import ensure_dir_exists
223 from IPython.utils.path import ensure_dir_exists
192 from IPython.utils.process import arg_split
224 from IPython.utils.process import arg_split
193 from traitlets import (
225 from traitlets import (
@@ -218,16 +250,23 b' try:'
218 except ImportError:
250 except ImportError:
219 JEDI_INSTALLED = False
251 JEDI_INSTALLED = False
220
252
221 if TYPE_CHECKING:
253
254 if TYPE_CHECKING or GENERATING_DOCUMENTATION:
222 from typing import cast
255 from typing import cast
223 from typing_extensions import TypedDict, NotRequired
256 from typing_extensions import TypedDict, NotRequired, Protocol, TypeAlias
224 else:
257 else:
225
258
226 def cast(obj, _type):
259 def cast(obj, type_):
260 """Workaround for `TypeError: MatcherAPIv2() takes no arguments`"""
227 return obj
261 return obj
228
262
229 TypedDict = Dict
263 # do not require on runtime
230 NotRequired = Tuple
264 NotRequired = Tuple # requires Python >=3.11
265 TypedDict = Dict # by extension of `NotRequired` requires 3.11 too
266 Protocol = object # requires Python >=3.8
267 TypeAlias = Any # requires Python >=3.10
268 if GENERATING_DOCUMENTATION:
269 from typing import TypedDict
231
270
232 # -----------------------------------------------------------------------------
271 # -----------------------------------------------------------------------------
233 # Globals
272 # Globals
@@ -522,31 +561,36 b' class SimpleCompletion:'
522 return f"<SimpleCompletion text={self.text!r} type={self.type!r}>"
561 return f"<SimpleCompletion text={self.text!r} type={self.type!r}>"
523
562
524
563
525 class MatcherResultBase(TypedDict):
564 class _MatcherResultBase(TypedDict):
526 """Definition of dictionary to be returned by new-style Matcher (API v2)."""
565 """Definition of dictionary to be returned by new-style Matcher (API v2)."""
527
566
528 #: suffix of the provided ``CompletionContext.token``, if not given defaults to full token.
567 #: Suffix of the provided ``CompletionContext.token``, if not given defaults to full token.
529 matched_fragment: NotRequired[str]
568 matched_fragment: NotRequired[str]
530
569
531 #: whether to suppress results from all other matchers (True), some
570 #: Whether to suppress results from all other matchers (True), some
532 #: matchers (set of identifiers) or none (False); default is False.
571 #: matchers (set of identifiers) or none (False); default is False.
533 suppress: NotRequired[Union[bool, Set[str]]]
572 suppress: NotRequired[Union[bool, Set[str]]]
534
573
535 #: identifiers of matchers which should NOT be suppressed
574 #: Identifiers of matchers which should NOT be suppressed when this matcher
575 #: requests to suppress all other matchers; defaults to an empty set.
536 do_not_suppress: NotRequired[Set[str]]
576 do_not_suppress: NotRequired[Set[str]]
537
577
538 #: are completions already ordered and should be left as-is? default is False.
578 #: Are completions already ordered and should be left as-is? default is False.
539 ordered: NotRequired[bool]
579 ordered: NotRequired[bool]
540
580
541
581
542 class SimpleMatcherResult(MatcherResultBase):
582 @sphinx_options(show_inherited_members=True, exclude_inherited_from=["dict"])
583 class SimpleMatcherResult(_MatcherResultBase, TypedDict):
543 """Result of new-style completion matcher."""
584 """Result of new-style completion matcher."""
544
585
545 #: list of candidate completions
586 # note: TypedDict is added again to the inheritance chain
587 # in order to get __orig_bases__ for documentation
588
589 #: List of candidate completions
546 completions: Sequence[SimpleCompletion]
590 completions: Sequence[SimpleCompletion]
547
591
548
592
549 class _JediMatcherResult(MatcherResultBase):
593 class _JediMatcherResult(_MatcherResultBase):
550 """Matching result returned by Jedi (will be processed differently)"""
594 """Matching result returned by Jedi (will be processed differently)"""
551
595
552 #: list of candidate completions
596 #: list of candidate completions
@@ -592,11 +636,38 b' class CompletionContext(NamedTuple):'
592 return self.full_text.split("\n")[self.cursor_line]
636 return self.full_text.split("\n")[self.cursor_line]
593
637
594
638
639 #: Matcher results for API v2.
595 MatcherResult = Union[SimpleMatcherResult, _JediMatcherResult]
640 MatcherResult = Union[SimpleMatcherResult, _JediMatcherResult]
596
641
597 MatcherAPIv1 = Callable[[str], List[str]]
642
598 MatcherAPIv2 = Callable[[CompletionContext], MatcherResult]
643 class _MatcherAPIv1Base(Protocol):
599 Matcher = Union[MatcherAPIv1, MatcherAPIv2]
644 def __call__(self, text: str) -> list[str]:
645 """Call signature."""
646
647
648 class _MatcherAPIv1Total(_MatcherAPIv1Base, Protocol):
649 #: API version
650 matcher_api_version: Optional[Literal[1]]
651
652 def __call__(self, text: str) -> list[str]:
653 """Call signature."""
654
655
656 #: Protocol describing Matcher API v1.
657 MatcherAPIv1: TypeAlias = Union[_MatcherAPIv1Base, _MatcherAPIv1Total]
658
659
660 class MatcherAPIv2(Protocol):
661 """Protocol describing Matcher API v2."""
662
663 #: API version
664 matcher_api_version: Literal[2] = 2
665
666 def __call__(self, context: CompletionContext) -> MatcherResult:
667 """Call signature."""
668
669
670 Matcher: TypeAlias = Union[MatcherAPIv1, MatcherAPIv2]
600
671
601
672
602 def completion_matcher(
673 def completion_matcher(
@@ -1160,7 +1231,7 b' def _safe_isinstance(obj, module, class_name):'
1160 def back_unicode_name_matcher(context):
1231 def back_unicode_name_matcher(context):
1161 """Match Unicode characters back to Unicode name
1232 """Match Unicode characters back to Unicode name
1162
1233
1163 Same as ``back_unicode_name_matches``, but adopted to new Matcher API.
1234 Same as :any:`back_unicode_name_matches`, but adopted to new Matcher API.
1164 """
1235 """
1165 fragment, matches = back_unicode_name_matches(context.token)
1236 fragment, matches = back_unicode_name_matches(context.token)
1166 return _convert_matcher_v1_result_to_v2(
1237 return _convert_matcher_v1_result_to_v2(
@@ -1178,6 +1249,9 b' def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:'
1178
1249
1179 This will not either back-complete standard sequences like \\n, \\b ...
1250 This will not either back-complete standard sequences like \\n, \\b ...
1180
1251
1252 .. deprecated:: 8.6
1253 You can use :meth:`back_unicode_name_matcher` instead.
1254
1181 Returns
1255 Returns
1182 =======
1256 =======
1183
1257
@@ -1187,7 +1261,6 b' def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:'
1187 empty string,
1261 empty string,
1188 - a sequence (of 1), name for the match Unicode character, preceded by
1262 - a sequence (of 1), name for the match Unicode character, preceded by
1189 backslash, or empty if no match.
1263 backslash, or empty if no match.
1190
1191 """
1264 """
1192 if len(text)<2:
1265 if len(text)<2:
1193 return '', ()
1266 return '', ()
@@ -1212,7 +1285,7 b' def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:'
1212 def back_latex_name_matcher(context):
1285 def back_latex_name_matcher(context):
1213 """Match latex characters back to unicode name
1286 """Match latex characters back to unicode name
1214
1287
1215 Same as ``back_latex_name_matches``, but adopted to new Matcher API.
1288 Same as :any:`back_latex_name_matches`, but adopted to new Matcher API.
1216 """
1289 """
1217 fragment, matches = back_latex_name_matches(context.token)
1290 fragment, matches = back_latex_name_matches(context.token)
1218 return _convert_matcher_v1_result_to_v2(
1291 return _convert_matcher_v1_result_to_v2(
@@ -1225,6 +1298,8 b' def back_latex_name_matches(text: str) -> Tuple[str, Sequence[str]]:'
1225
1298
1226 This does ``\\β„΅`` -> ``\\aleph``
1299 This does ``\\β„΅`` -> ``\\aleph``
1227
1300
1301 .. deprecated:: 8.6
1302 You can use :meth:`back_latex_name_matcher` instead.
1228 """
1303 """
1229 if len(text)<2:
1304 if len(text)<2:
1230 return '', ()
1305 return '', ()
@@ -1567,7 +1642,7 b' class IPCompleter(Completer):'
1567
1642
1568 @context_matcher()
1643 @context_matcher()
1569 def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1644 def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1570 """Same as ``file_matches``, but adopted to new Matcher API."""
1645 """Same as :any:`file_matches`, but adopted to new Matcher API."""
1571 matches = self.file_matches(context.token)
1646 matches = self.file_matches(context.token)
1572 # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter,
1647 # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter,
1573 # starts with `/home/`, `C:\`, etc)
1648 # starts with `/home/`, `C:\`, etc)
@@ -1587,7 +1662,8 b' class IPCompleter(Completer):'
1587 current (as of Python 2.3) Python readline it's possible to do
1662 current (as of Python 2.3) Python readline it's possible to do
1588 better.
1663 better.
1589
1664
1590 DEPRECATED: Deprecated since 8.6. Use ``file_matcher`` instead.
1665 .. deprecated:: 8.6
1666 You can use :meth:`file_matcher` instead.
1591 """
1667 """
1592
1668
1593 # chars that require escaping with backslash - i.e. chars
1669 # chars that require escaping with backslash - i.e. chars
@@ -1660,6 +1736,7 b' class IPCompleter(Completer):'
1660
1736
1661 @context_matcher()
1737 @context_matcher()
1662 def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1738 def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1739 """Match magics."""
1663 text = context.token
1740 text = context.token
1664 matches = self.magic_matches(text)
1741 matches = self.magic_matches(text)
1665 result = _convert_matcher_v1_result_to_v2(matches, type="magic")
1742 result = _convert_matcher_v1_result_to_v2(matches, type="magic")
@@ -1670,7 +1747,8 b' class IPCompleter(Completer):'
1670 def magic_matches(self, text: str):
1747 def magic_matches(self, text: str):
1671 """Match magics.
1748 """Match magics.
1672
1749
1673 DEPRECATED: Deprecated since 8.6. Use ``magic_matcher`` instead.
1750 .. deprecated:: 8.6
1751 You can use :meth:`magic_matcher` instead.
1674 """
1752 """
1675 # Get all shell magics now rather than statically, so magics loaded at
1753 # Get all shell magics now rather than statically, so magics loaded at
1676 # runtime show up too.
1754 # runtime show up too.
@@ -1722,7 +1800,8 b' class IPCompleter(Completer):'
1722 def magic_config_matches(self, text: str) -> List[str]:
1800 def magic_config_matches(self, text: str) -> List[str]:
1723 """Match class names and attributes for %config magic.
1801 """Match class names and attributes for %config magic.
1724
1802
1725 DEPRECATED: Deprecated since 8.6. Use ``magic_config_matcher`` instead.
1803 .. deprecated:: 8.6
1804 You can use :meth:`magic_config_matcher` instead.
1726 """
1805 """
1727 texts = text.strip().split()
1806 texts = text.strip().split()
1728
1807
@@ -1767,7 +1846,8 b' class IPCompleter(Completer):'
1767 def magic_color_matches(self, text: str) -> List[str]:
1846 def magic_color_matches(self, text: str) -> List[str]:
1768 """Match color schemes for %colors magic.
1847 """Match color schemes for %colors magic.
1769
1848
1770 DEPRECATED: Deprecated since 8.6. Use ``magic_color_matcher`` instead.
1849 .. deprecated:: 8.6
1850 You can use :meth:`magic_color_matcher` instead.
1771 """
1851 """
1772 texts = text.split()
1852 texts = text.split()
1773 if text.endswith(' '):
1853 if text.endswith(' '):
@@ -1815,7 +1895,8 b' class IPCompleter(Completer):'
1815 If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion`
1895 If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion`
1816 object containing a string with the Jedi debug information attached.
1896 object containing a string with the Jedi debug information attached.
1817
1897
1818 DEPRECATED: Deprecated since 8.6. Use ``_jedi_matcher`` instead.
1898 .. deprecated:: 8.6
1899 You can use :meth:`_jedi_matcher` instead.
1819 """
1900 """
1820 namespaces = [self.namespace]
1901 namespaces = [self.namespace]
1821 if self.global_namespace is not None:
1902 if self.global_namespace is not None:
@@ -1961,7 +2042,8 b' class IPCompleter(Completer):'
1961 def python_func_kw_matches(self, text):
2042 def python_func_kw_matches(self, text):
1962 """Match named parameters (kwargs) of the last open function.
2043 """Match named parameters (kwargs) of the last open function.
1963
2044
1964 DEPRECATED: Deprecated since 8.6. Use ``magic_config_matcher`` instead.
2045 .. deprecated:: 8.6
2046 You can use :meth:`python_func_kw_matcher` instead.
1965 """
2047 """
1966
2048
1967 if "." in text: # a parameter cannot be dotted
2049 if "." in text: # a parameter cannot be dotted
@@ -2068,7 +2150,8 b' class IPCompleter(Completer):'
2068 def dict_key_matches(self, text: str) -> List[str]:
2150 def dict_key_matches(self, text: str) -> List[str]:
2069 """Match string keys in a dictionary, after e.g. ``foo[``.
2151 """Match string keys in a dictionary, after e.g. ``foo[``.
2070
2152
2071 DEPRECATED: Deprecated since 8.6. Use `dict_key_matcher` instead.
2153 .. deprecated:: 8.6
2154 You can use :meth:`dict_key_matcher` instead.
2072 """
2155 """
2073
2156
2074 if self.__dict_key_regexps is not None:
2157 if self.__dict_key_regexps is not None:
@@ -2173,6 +2256,7 b' class IPCompleter(Completer):'
2173
2256
2174 @context_matcher()
2257 @context_matcher()
2175 def unicode_name_matcher(self, context):
2258 def unicode_name_matcher(self, context):
2259 """Same as :any:`unicode_name_matches`, but adopted to new Matcher API."""
2176 fragment, matches = self.unicode_name_matches(context.token)
2260 fragment, matches = self.unicode_name_matches(context.token)
2177 return _convert_matcher_v1_result_to_v2(
2261 return _convert_matcher_v1_result_to_v2(
2178 matches, type="unicode", fragment=fragment, suppress_if_matches=True
2262 matches, type="unicode", fragment=fragment, suppress_if_matches=True
@@ -2216,7 +2300,8 b' class IPCompleter(Completer):'
2216
2300
2217 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``Ξ±``
2301 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``Ξ±``
2218
2302
2219 DEPRECATED: Deprecated since 8.6. Use `latex_matcher` instead.
2303 .. deprecated:: 8.6
2304 You can use :meth:`latex_name_matcher` instead.
2220 """
2305 """
2221 slashpos = text.rfind('\\')
2306 slashpos = text.rfind('\\')
2222 if slashpos > -1:
2307 if slashpos > -1:
@@ -2235,9 +2320,13 b' class IPCompleter(Completer):'
2235
2320
2236 @context_matcher()
2321 @context_matcher()
2237 def custom_completer_matcher(self, context):
2322 def custom_completer_matcher(self, context):
2323 """Dispatch custom completer.
2324
2325 If a match is found, suppresses all other matchers except for Jedi.
2326 """
2238 matches = self.dispatch_custom_completer(context.token) or []
2327 matches = self.dispatch_custom_completer(context.token) or []
2239 result = _convert_matcher_v1_result_to_v2(
2328 result = _convert_matcher_v1_result_to_v2(
2240 matches, type="<unknown>", suppress_if_matches=True
2329 matches, type=_UNKNOWN_TYPE, suppress_if_matches=True
2241 )
2330 )
2242 result["ordered"] = True
2331 result["ordered"] = True
2243 result["do_not_suppress"] = {_get_matcher_id(self._jedi_matcher)}
2332 result["do_not_suppress"] = {_get_matcher_id(self._jedi_matcher)}
@@ -2245,7 +2334,8 b' class IPCompleter(Completer):'
2245
2334
2246 def dispatch_custom_completer(self, text):
2335 def dispatch_custom_completer(self, text):
2247 """
2336 """
2248 DEPRECATED: Deprecated since 8.6. Use `custom_completer_matcher` instead.
2337 .. deprecated:: 8.6
2338 You can use :meth:`custom_completer_matcher` instead.
2249 """
2339 """
2250 if not self.custom_completers:
2340 if not self.custom_completers:
2251 return
2341 return
@@ -2768,7 +2858,7 b' class IPCompleter(Completer):'
2768
2858
2769 @context_matcher()
2859 @context_matcher()
2770 def fwd_unicode_matcher(self, context):
2860 def fwd_unicode_matcher(self, context):
2771 """Same as ``fwd_unicode_match``, but adopted to new Matcher API."""
2861 """Same as :any:`fwd_unicode_match`, but adopted to new Matcher API."""
2772 fragment, matches = self.latex_matches(context.token)
2862 fragment, matches = self.latex_matches(context.token)
2773 return _convert_matcher_v1_result_to_v2(
2863 return _convert_matcher_v1_result_to_v2(
2774 matches, type="unicode", fragment=fragment, suppress_if_matches=True
2864 matches, type="unicode", fragment=fragment, suppress_if_matches=True
@@ -2779,15 +2869,16 b' class IPCompleter(Completer):'
2779 Forward match a string starting with a backslash with a list of
2869 Forward match a string starting with a backslash with a list of
2780 potential Unicode completions.
2870 potential Unicode completions.
2781
2871
2782 Will compute list list of Unicode character names on first call and cache it.
2872 Will compute list of Unicode character names on first call and cache it.
2873
2874 .. deprecated:: 8.6
2875 You can use :meth:`fwd_unicode_matcher` instead.
2783
2876
2784 Returns
2877 Returns
2785 -------
2878 -------
2786 At tuple with:
2879 At tuple with:
2787 - matched text (empty if no matches)
2880 - matched text (empty if no matches)
2788 - list of potential completions, empty tuple otherwise)
2881 - list of potential completions, empty tuple otherwise)
2789
2790 DEPRECATED: Deprecated since 8.6. Use `fwd_unicode_matcher` instead.
2791 """
2882 """
2792 # TODO: self.unicode_names is here a list we traverse each time with ~100k elements.
2883 # TODO: self.unicode_names is here a list we traverse each time with ~100k elements.
2793 # We could do a faster match using a Trie.
2884 # We could do a faster match using a Trie.
@@ -2,7 +2,7 b''
2 """Decorators that don't go anywhere else.
2 """Decorators that don't go anywhere else.
3
3
4 This module contains misc. decorators that don't really go with another module
4 This module contains misc. decorators that don't really go with another module
5 in :mod:`IPython.utils`. Beore putting something here please see if it should
5 in :mod:`IPython.utils`. Before putting something here please see if it should
6 go into another topical module in :mod:`IPython.utils`.
6 go into another topical module in :mod:`IPython.utils`.
7 """
7 """
8
8
@@ -16,6 +16,10 b' go into another topical module in :mod:`IPython.utils`.'
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 from typing import Sequence
20
21 from IPython.utils.docs import GENERATING_DOCUMENTATION
22
19
23
20 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
21 # Code
25 # Code
@@ -48,6 +52,7 b' def flag_calls(func):'
48 wrapper.__doc__ = func.__doc__
52 wrapper.__doc__ = func.__doc__
49 return wrapper
53 return wrapper
50
54
55
51 def undoc(func):
56 def undoc(func):
52 """Mark a function or class as undocumented.
57 """Mark a function or class as undocumented.
53
58
@@ -56,3 +61,23 b' def undoc(func):'
56 """
61 """
57 return func
62 return func
58
63
64
65 def sphinx_options(
66 show_inheritance: bool = True,
67 show_inherited_members: bool = False,
68 exclude_inherited_from: Sequence[str] = tuple(),
69 ):
70 """Set sphinx options"""
71
72 def wrapper(func):
73 if not GENERATING_DOCUMENTATION:
74 return func
75
76 func._sphinx_options = dict(
77 show_inheritance=show_inheritance,
78 show_inherited_members=show_inherited_members,
79 exclude_inherited_from=exclude_inherited_from,
80 )
81 return func
82
83 return wrapper
@@ -41,6 +41,14 b' else:'
41 html_theme = "sphinx_rtd_theme"
41 html_theme = "sphinx_rtd_theme"
42 html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
42 html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
43
43
44 # Allow Python scripts to change behaviour during sphinx run
45 os.environ["IN_SPHINX_RUN"] = "True"
46
47 autodoc_type_aliases = {
48 "Matcher": " IPython.core.completer.Matcher",
49 "MatcherAPIv1": " IPython.core.completer.MatcherAPIv1",
50 }
51
44 # If your extensions are in another directory, add it here. If the directory
52 # If your extensions are in another directory, add it here. If the directory
45 # is relative to the documentation root, use os.path.abspath to make it
53 # is relative to the documentation root, use os.path.abspath to make it
46 # absolute, like shown here.
54 # absolute, like shown here.
@@ -24,14 +24,9 b' import inspect'
24 import os
24 import os
25 import re
25 import re
26 from importlib import import_module
26 from importlib import import_module
27 from types import SimpleNamespace as Obj
27
28
28
29
29 class Obj(object):
30 '''Namespace to hold arbitrary information.'''
31 def __init__(self, **kwargs):
32 for k, v in kwargs.items():
33 setattr(self, k, v)
34
35 class FuncClsScanner(ast.NodeVisitor):
30 class FuncClsScanner(ast.NodeVisitor):
36 """Scan a module for top-level functions and classes.
31 """Scan a module for top-level functions and classes.
37
32
@@ -42,7 +37,7 b' class FuncClsScanner(ast.NodeVisitor):'
42 self.classes = []
37 self.classes = []
43 self.classes_seen = set()
38 self.classes_seen = set()
44 self.functions = []
39 self.functions = []
45
40
46 @staticmethod
41 @staticmethod
47 def has_undoc_decorator(node):
42 def has_undoc_decorator(node):
48 return any(isinstance(d, ast.Name) and d.id == 'undoc' \
43 return any(isinstance(d, ast.Name) and d.id == 'undoc' \
@@ -62,11 +57,15 b' class FuncClsScanner(ast.NodeVisitor):'
62 self.functions.append(node.name)
57 self.functions.append(node.name)
63
58
64 def visit_ClassDef(self, node):
59 def visit_ClassDef(self, node):
65 if not (node.name.startswith('_') or self.has_undoc_decorator(node)) \
60 if (
66 and node.name not in self.classes_seen:
61 not (node.name.startswith("_") or self.has_undoc_decorator(node))
67 cls = Obj(name=node.name)
62 and node.name not in self.classes_seen
68 cls.has_init = any(isinstance(n, ast.FunctionDef) and \
63 ):
69 n.name=='__init__' for n in node.body)
64 cls = Obj(name=node.name, sphinx_options={})
65 cls.has_init = any(
66 isinstance(n, ast.FunctionDef) and n.name == "__init__"
67 for n in node.body
68 )
70 self.classes.append(cls)
69 self.classes.append(cls)
71 self.classes_seen.add(node.name)
70 self.classes_seen.add(node.name)
72
71
@@ -221,7 +220,11 b' class ApiDocWriter(object):'
221 funcs, classes = [], []
220 funcs, classes = [], []
222 for name, obj in ns.items():
221 for name, obj in ns.items():
223 if inspect.isclass(obj):
222 if inspect.isclass(obj):
224 cls = Obj(name=name, has_init='__init__' in obj.__dict__)
223 cls = Obj(
224 name=name,
225 has_init="__init__" in obj.__dict__,
226 sphinx_options=getattr(obj, "_sphinx_options", {}),
227 )
225 classes.append(cls)
228 classes.append(cls)
226 elif inspect.isfunction(obj):
229 elif inspect.isfunction(obj):
227 funcs.append(name)
230 funcs.append(name)
@@ -279,10 +282,18 b' class ApiDocWriter(object):'
279 self.rst_section_levels[2] * len(subhead) + '\n'
282 self.rst_section_levels[2] * len(subhead) + '\n'
280
283
281 for c in classes:
284 for c in classes:
282 ad += '\n.. autoclass:: ' + c.name + '\n'
285 opts = c.sphinx_options
286 ad += "\n.. autoclass:: " + c.name + "\n"
283 # must NOT exclude from index to keep cross-refs working
287 # must NOT exclude from index to keep cross-refs working
284 ad += ' :members:\n' \
288 ad += " :members:\n"
285 ' :show-inheritance:\n'
289 if opts.get("show_inheritance", True):
290 ad += " :show-inheritance:\n"
291 if opts.get("show_inherited_members", False):
292 exclusions_list = opts.get("exclude_inherited_from", [])
293 exclusions = (
294 (" " + " ".join(exclusions_list)) if exclusions_list else ""
295 )
296 ad += f" :inherited-members:{exclusions}\n"
286 if c.has_init:
297 if c.has_init:
287 ad += '\n .. automethod:: __init__\n'
298 ad += '\n .. automethod:: __init__\n'
288
299
General Comments 0
You need to be logged in to leave comments. Login now