##// END OF EJS Templates
Merge branch 'main' into shaperilio/autoreload-verbosity
Emilio Graff -
r27880:51eb040f merge
parent child Browse files
Show More
@@ -0,0 +1,3 b''
1 import os
2
3 GENERATING_DOCUMENTATION = os.environ.get("IN_SPHINX_RUN", None) == "True"
@@ -28,7 +28,7 b' jobs:'
28 28 - name: Install dependencies
29 29 run: |
30 30 python -m pip install --upgrade pip
31 pip install darker black==21.12b0
31 pip install darker==1.5.1 black==22.10.0
32 32 - name: Lint with darker
33 33 run: |
34 34 darker -r 60625f241f298b5039cb2debc365db38aa7bb522 --check --diff . || (
@@ -24,7 +24,6 b' __pycache__'
24 24 .cache
25 25 .coverage
26 26 *.swp
27 .vscode
28 27 .pytest_cache
29 28 .python-version
30 29 venv*/
@@ -73,25 +73,6 b' class CachingCompiler(codeop.Compile):'
73 73 def __init__(self):
74 74 codeop.Compile.__init__(self)
75 75
76 # This is ugly, but it must be done this way to allow multiple
77 # simultaneous ipython instances to coexist. Since Python itself
78 # directly accesses the data structures in the linecache module, and
79 # the cache therein is global, we must work with that data structure.
80 # We must hold a reference to the original checkcache routine and call
81 # that in our own check_cache() below, but the special IPython cache
82 # must also be shared by all IPython instances. If we were to hold
83 # separate caches (one in each CachingCompiler instance), any call made
84 # by Python itself to linecache.checkcache() would obliterate the
85 # cached data from the other IPython instances.
86 if not hasattr(linecache, '_ipython_cache'):
87 linecache._ipython_cache = {}
88 if not hasattr(linecache, '_checkcache_ori'):
89 linecache._checkcache_ori = linecache.checkcache
90 # Now, we must monkeypatch the linecache directly so that parts of the
91 # stdlib that call it outside our control go through our codepath
92 # (otherwise we'd lose our tracebacks).
93 linecache.checkcache = check_linecache_ipython
94
95 76 # Caching a dictionary { filename: execution_count } for nicely
96 77 # rendered tracebacks. The filename corresponds to the filename
97 78 # argument used for the builtins.compile function.
@@ -161,14 +142,24 b' class CachingCompiler(codeop.Compile):'
161 142 # Save the execution count
162 143 self._filename_map[name] = number
163 144
145 # Since Python 2.5, setting mtime to `None` means the lines will
146 # never be removed by `linecache.checkcache`. This means all the
147 # monkeypatching has *never* been necessary, since this code was
148 # only added in 2010, at which point IPython had already stopped
149 # supporting Python 2.4.
150 #
151 # Note that `linecache.clearcache` and `linecache.updatecache` may
152 # still remove our code from the cache, but those show explicit
153 # intent, and we should not try to interfere. Normally the former
154 # is never called except when out of memory, and the latter is only
155 # called for lines *not* in the cache.
164 156 entry = (
165 157 len(transformed_code),
166 time.time(),
158 None,
167 159 [line + "\n" for line in transformed_code.splitlines()],
168 160 name,
169 161 )
170 162 linecache.cache[name] = entry
171 linecache._ipython_cache[name] = entry
172 163 return name
173 164
174 165 @contextmanager
@@ -187,10 +178,22 b' class CachingCompiler(codeop.Compile):'
187 178
188 179
189 180 def check_linecache_ipython(*args):
190 """Call linecache.checkcache() safely protecting our cached values.
181 """Deprecated since IPython 8.6. Call linecache.checkcache() directly.
182
183 It was already not necessary to call this function directly. If no
184 CachingCompiler had been created, this function would fail badly. If
185 an instance had been created, this function would've been monkeypatched
186 into place.
187
188 As of IPython 8.6, the monkeypatching has gone away entirely. But there
189 were still internal callers of this function, so maybe external callers
190 also existed?
191 191 """
192 # First call the original checkcache as intended
193 linecache._checkcache_ori(*args)
194 # Then, update back the cache with our data, so that tracebacks related
195 # to our compiled codes can be produced.
196 linecache.cache.update(linecache._ipython_cache)
192 import warnings
193
194 warnings.warn(
195 "Deprecated Since IPython 8.6, Just call linecache.checkcache() directly.",
196 DeprecationWarning,
197 stacklevel=2,
198 )
199 linecache.checkcache()
This diff has been collapsed as it changes many lines, (919 lines changed) Show them Hide them
@@ -100,6 +100,73 b' option.'
100 100
101 101 Be sure to update :any:`jedi` to the latest stable version or to try the
102 102 current development version to get better completions.
103
104 Matchers
105 ========
106
107 All completions routines are implemented using unified *Matchers* API.
108 The matchers API is provisional and subject to change without notice.
109
110 The built-in matchers include:
111
112 - :any:`IPCompleter.dict_key_matcher`: dictionary key completions,
113 - :any:`IPCompleter.magic_matcher`: completions for magics,
114 - :any:`IPCompleter.unicode_name_matcher`,
115 :any:`IPCompleter.fwd_unicode_matcher`
116 and :any:`IPCompleter.latex_name_matcher`: see `Forward latex/unicode completion`_,
117 - :any:`back_unicode_name_matcher` and :any:`back_latex_name_matcher`: see `Backward latex completion`_,
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),
121 - ``IPCompleter.jedi_matcher`` - static analysis with Jedi,
122 - :any:`IPCompleter.custom_completer_matcher` - pluggable completer with a default
123 implementation in :any:`InteractiveShell` which uses IPython hooks system
124 (`complete_command`) with string dispatch (including regular expressions).
125 Differently to other matchers, ``custom_completer_matcher`` will not suppress
126 Jedi results to match behaviour in earlier IPython versions.
127
128 Custom matchers can be added by appending to ``IPCompleter.custom_matchers`` list.
129
130 Matcher API
131 -----------
132
133 Simplifying some details, the ``Matcher`` interface can described as
134
135 .. code-block::
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
154 Suppression of competing matchers
155 ---------------------------------
156
157 By default results from all matchers are combined, in the order determined by
158 their priority. Matchers can request to suppress results from subsequent
159 matchers by setting ``suppress`` to ``True`` in the ``MatcherResult``.
160
161 When multiple matchers simultaneously request surpression, the results from of
162 the matcher with higher priority will be returned.
163
164 Sometimes it is desirable to suppress most but not all other matchers;
165 this can be achieved by adding a list of identifiers of matchers which
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`.
103 170 """
104 171
105 172
@@ -109,7 +176,7 b' current development version to get better completions.'
109 176 # Some of this code originated from rlcompleter in the Python standard library
110 177 # Copyright (C) 2001 Python Software Foundation, www.python.org
111 178
112
179 from __future__ import annotations
113 180 import builtins as builtin_mod
114 181 import glob
115 182 import inspect
@@ -124,9 +191,26 b' import unicodedata'
124 191 import uuid
125 192 import warnings
126 193 from contextlib import contextmanager
194 from dataclasses import dataclass
195 from functools import cached_property, partial
127 196 from importlib import import_module
128 197 from types import SimpleNamespace
129 from typing import Iterable, Iterator, List, Tuple, Union, Any, Sequence, Dict, NamedTuple, Pattern, Optional
198 from typing import (
199 Iterable,
200 Iterator,
201 List,
202 Tuple,
203 Union,
204 Any,
205 Sequence,
206 Dict,
207 NamedTuple,
208 Pattern,
209 Optional,
210 TYPE_CHECKING,
211 Set,
212 Literal,
213 )
130 214
131 215 from IPython.core.error import TryNext
132 216 from IPython.core.inputtransformer2 import ESC_MAGIC
@@ -134,10 +218,22 b' from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol'
134 218 from IPython.core.oinspect import InspectColors
135 219 from IPython.testing.skipdoctest import skip_doctest
136 220 from IPython.utils import generics
221 from IPython.utils.decorators import sphinx_options
137 222 from IPython.utils.dir2 import dir2, get_real_method
223 from IPython.utils.docs import GENERATING_DOCUMENTATION
138 224 from IPython.utils.path import ensure_dir_exists
139 225 from IPython.utils.process import arg_split
140 from traitlets import Bool, Enum, Int, List as ListTrait, Unicode, default, observe
226 from traitlets import (
227 Bool,
228 Enum,
229 Int,
230 List as ListTrait,
231 Unicode,
232 Dict as DictTrait,
233 Union as UnionTrait,
234 default,
235 observe,
236 )
141 237 from traitlets.config.configurable import Configurable
142 238
143 239 import __main__
@@ -145,6 +241,7 b' import __main__'
145 241 # skip module docstests
146 242 __skip_doctest__ = True
147 243
244
148 245 try:
149 246 import jedi
150 247 jedi.settings.case_insensitive_completion = False
@@ -153,6 +250,25 b' try:'
153 250 JEDI_INSTALLED = True
154 251 except ImportError:
155 252 JEDI_INSTALLED = False
253
254
255 if TYPE_CHECKING or GENERATING_DOCUMENTATION:
256 from typing import cast
257 from typing_extensions import TypedDict, NotRequired, Protocol, TypeAlias
258 else:
259
260 def cast(obj, type_):
261 """Workaround for `TypeError: MatcherAPIv2() takes no arguments`"""
262 return obj
263
264 # do not require on runtime
265 NotRequired = Tuple # requires Python >=3.11
266 TypedDict = Dict # by extension of `NotRequired` requires 3.11 too
267 Protocol = object # requires Python >=3.8
268 TypeAlias = Any # requires Python >=3.10
269 if GENERATING_DOCUMENTATION:
270 from typing import TypedDict
271
156 272 #-----------------------------------------------------------------------------
157 273 # Globals
158 274 #-----------------------------------------------------------------------------
@@ -166,7 +282,7 b' except ImportError:'
166 282 _UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)]
167 283
168 284 # Public API
169 __all__ = ['Completer','IPCompleter']
285 __all__ = ["Completer", "IPCompleter"]
170 286
171 287 if sys.platform == 'win32':
172 288 PROTECTABLES = ' '
@@ -177,6 +293,8 b' else:'
177 293 # may have trouble processing.
178 294 MATCHES_LIMIT = 500
179 295
296 # Completion type reported when no type can be inferred.
297 _UNKNOWN_TYPE = "<unknown>"
180 298
181 299 class ProvisionalCompleterWarning(FutureWarning):
182 300 """
@@ -355,9 +473,12 b' class _FakeJediCompletion:'
355 473 return '<Fake completion object jedi has crashed>'
356 474
357 475
476 _JediCompletionLike = Union[jedi.api.Completion, _FakeJediCompletion]
477
478
358 479 class Completion:
359 480 """
360 Completion object used and return by IPython completers.
481 Completion object used and returned by IPython completers.
361 482
362 483 .. warning::
363 484
@@ -417,6 +538,188 b' class Completion:'
417 538 return hash((self.start, self.end, self.text))
418 539
419 540
541 class SimpleCompletion:
542 """Completion item to be included in the dictionary returned by new-style Matcher (API v2).
543
544 .. warning::
545
546 Provisional
547
548 This class is used to describe the currently supported attributes of
549 simple completion items, and any additional implementation details
550 should not be relied on. Additional attributes may be included in
551 future versions, and meaning of text disambiguated from the current
552 dual meaning of "text to insert" and "text to used as a label".
553 """
554
555 __slots__ = ["text", "type"]
556
557 def __init__(self, text: str, *, type: str = None):
558 self.text = text
559 self.type = type
560
561 def __repr__(self):
562 return f"<SimpleCompletion text={self.text!r} type={self.type!r}>"
563
564
565 class _MatcherResultBase(TypedDict):
566 """Definition of dictionary to be returned by new-style Matcher (API v2)."""
567
568 #: Suffix of the provided ``CompletionContext.token``, if not given defaults to full token.
569 matched_fragment: NotRequired[str]
570
571 #: Whether to suppress results from all other matchers (True), some
572 #: matchers (set of identifiers) or none (False); default is False.
573 suppress: NotRequired[Union[bool, Set[str]]]
574
575 #: Identifiers of matchers which should NOT be suppressed when this matcher
576 #: requests to suppress all other matchers; defaults to an empty set.
577 do_not_suppress: NotRequired[Set[str]]
578
579 #: Are completions already ordered and should be left as-is? default is False.
580 ordered: NotRequired[bool]
581
582
583 @sphinx_options(show_inherited_members=True, exclude_inherited_from=["dict"])
584 class SimpleMatcherResult(_MatcherResultBase, TypedDict):
585 """Result of new-style completion matcher."""
586
587 # note: TypedDict is added again to the inheritance chain
588 # in order to get __orig_bases__ for documentation
589
590 #: List of candidate completions
591 completions: Sequence[SimpleCompletion]
592
593
594 class _JediMatcherResult(_MatcherResultBase):
595 """Matching result returned by Jedi (will be processed differently)"""
596
597 #: list of candidate completions
598 completions: Iterable[_JediCompletionLike]
599
600
601 @dataclass
602 class CompletionContext:
603 """Completion context provided as an argument to matchers in the Matcher API v2."""
604
605 # rationale: many legacy matchers relied on completer state (`self.text_until_cursor`)
606 # which was not explicitly visible as an argument of the matcher, making any refactor
607 # prone to errors; by explicitly passing `cursor_position` we can decouple the matchers
608 # from the completer, and make substituting them in sub-classes easier.
609
610 #: Relevant fragment of code directly preceding the cursor.
611 #: The extraction of token is implemented via splitter heuristic
612 #: (following readline behaviour for legacy reasons), which is user configurable
613 #: (by switching the greedy mode).
614 token: str
615
616 #: The full available content of the editor or buffer
617 full_text: str
618
619 #: Cursor position in the line (the same for ``full_text`` and ``text``).
620 cursor_position: int
621
622 #: Cursor line in ``full_text``.
623 cursor_line: int
624
625 #: The maximum number of completions that will be used downstream.
626 #: Matchers can use this information to abort early.
627 #: The built-in Jedi matcher is currently excepted from this limit.
628 # If not given, return all possible completions.
629 limit: Optional[int]
630
631 @cached_property
632 def text_until_cursor(self) -> str:
633 return self.line_with_cursor[: self.cursor_position]
634
635 @cached_property
636 def line_with_cursor(self) -> str:
637 return self.full_text.split("\n")[self.cursor_line]
638
639
640 #: Matcher results for API v2.
641 MatcherResult = Union[SimpleMatcherResult, _JediMatcherResult]
642
643
644 class _MatcherAPIv1Base(Protocol):
645 def __call__(self, text: str) -> list[str]:
646 """Call signature."""
647
648
649 class _MatcherAPIv1Total(_MatcherAPIv1Base, Protocol):
650 #: API version
651 matcher_api_version: Optional[Literal[1]]
652
653 def __call__(self, text: str) -> list[str]:
654 """Call signature."""
655
656
657 #: Protocol describing Matcher API v1.
658 MatcherAPIv1: TypeAlias = Union[_MatcherAPIv1Base, _MatcherAPIv1Total]
659
660
661 class MatcherAPIv2(Protocol):
662 """Protocol describing Matcher API v2."""
663
664 #: API version
665 matcher_api_version: Literal[2] = 2
666
667 def __call__(self, context: CompletionContext) -> MatcherResult:
668 """Call signature."""
669
670
671 Matcher: TypeAlias = Union[MatcherAPIv1, MatcherAPIv2]
672
673
674 def completion_matcher(
675 *, priority: float = None, identifier: str = None, api_version: int = 1
676 ):
677 """Adds attributes describing the matcher.
678
679 Parameters
680 ----------
681 priority : Optional[float]
682 The priority of the matcher, determines the order of execution of matchers.
683 Higher priority means that the matcher will be executed first. Defaults to 0.
684 identifier : Optional[str]
685 identifier of the matcher allowing users to modify the behaviour via traitlets,
686 and also used to for debugging (will be passed as ``origin`` with the completions).
687 Defaults to matcher function ``__qualname__``.
688 api_version: Optional[int]
689 version of the Matcher API used by this matcher.
690 Currently supported values are 1 and 2.
691 Defaults to 1.
692 """
693
694 def wrapper(func: Matcher):
695 func.matcher_priority = priority or 0
696 func.matcher_identifier = identifier or func.__qualname__
697 func.matcher_api_version = api_version
698 if TYPE_CHECKING:
699 if api_version == 1:
700 func = cast(func, MatcherAPIv1)
701 elif api_version == 2:
702 func = cast(func, MatcherAPIv2)
703 return func
704
705 return wrapper
706
707
708 def _get_matcher_priority(matcher: Matcher):
709 return getattr(matcher, "matcher_priority", 0)
710
711
712 def _get_matcher_id(matcher: Matcher):
713 return getattr(matcher, "matcher_identifier", matcher.__qualname__)
714
715
716 def _get_matcher_api_version(matcher):
717 return getattr(matcher, "matcher_api_version", 1)
718
719
720 context_matcher = partial(completion_matcher, api_version=2)
721
722
420 723 _IC = Iterable[Completion]
421 724
422 725
@@ -924,6 +1227,19 b' def _safe_isinstance(obj, module, class_name):'
924 1227 return (module in sys.modules and
925 1228 isinstance(obj, getattr(import_module(module), class_name)))
926 1229
1230
1231 @context_matcher()
1232 def back_unicode_name_matcher(context: CompletionContext):
1233 """Match Unicode characters back to Unicode name
1234
1235 Same as :any:`back_unicode_name_matches`, but adopted to new Matcher API.
1236 """
1237 fragment, matches = back_unicode_name_matches(context.text_until_cursor)
1238 return _convert_matcher_v1_result_to_v2(
1239 matches, type="unicode", fragment=fragment, suppress_if_matches=True
1240 )
1241
1242
927 1243 def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]:
928 1244 """Match Unicode characters back to Unicode name
929 1245
@@ -934,6 +1250,9 b' def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]:'
934 1250
935 1251 This will not either back-complete standard sequences like \\n, \\b ...
936 1252
1253 .. deprecated:: 8.6
1254 You can use :meth:`back_unicode_name_matcher` instead.
1255
937 1256 Returns
938 1257 =======
939 1258
@@ -943,7 +1262,6 b' def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]:'
943 1262 empty string,
944 1263 - a sequence (of 1), name for the match Unicode character, preceded by
945 1264 backslash, or empty if no match.
946
947 1265 """
948 1266 if len(text)<2:
949 1267 return '', ()
@@ -963,11 +1281,26 b' def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]:'
963 1281 pass
964 1282 return '', ()
965 1283
1284
1285 @context_matcher()
1286 def back_latex_name_matcher(context: CompletionContext):
1287 """Match latex characters back to unicode name
1288
1289 Same as :any:`back_latex_name_matches`, but adopted to new Matcher API.
1290 """
1291 fragment, matches = back_latex_name_matches(context.text_until_cursor)
1292 return _convert_matcher_v1_result_to_v2(
1293 matches, type="latex", fragment=fragment, suppress_if_matches=True
1294 )
1295
1296
966 1297 def back_latex_name_matches(text:str) -> Tuple[str, Sequence[str]] :
967 1298 """Match latex characters back to unicode name
968 1299
969 1300 This does ``\\β„΅`` -> ``\\aleph``
970 1301
1302 .. deprecated:: 8.6
1303 You can use :meth:`back_latex_name_matcher` instead.
971 1304 """
972 1305 if len(text)<2:
973 1306 return '', ()
@@ -1042,11 +1375,23 b' def _make_signature(completion)-> str:'
1042 1375 for p in signature.defined_names()) if f])
1043 1376
1044 1377
1045 class _CompleteResult(NamedTuple):
1046 matched_text : str
1047 matches: Sequence[str]
1048 matches_origin: Sequence[str]
1049 jedi_matches: Any
1378 _CompleteResult = Dict[str, MatcherResult]
1379
1380
1381 def _convert_matcher_v1_result_to_v2(
1382 matches: Sequence[str],
1383 type: str,
1384 fragment: str = None,
1385 suppress_if_matches: bool = False,
1386 ) -> SimpleMatcherResult:
1387 """Utility to help with transition"""
1388 result = {
1389 "completions": [SimpleCompletion(text=match, type=type) for match in matches],
1390 "suppress": (True if matches else False) if suppress_if_matches else False,
1391 }
1392 if fragment is not None:
1393 result["matched_fragment"] = fragment
1394 return result
1050 1395
1051 1396
1052 1397 class IPCompleter(Completer):
@@ -1062,17 +1407,59 b' class IPCompleter(Completer):'
1062 1407 else:
1063 1408 self.splitter.delims = DELIMS
1064 1409
1065 dict_keys_only = Bool(False,
1066 help="""Whether to show dict key matches only""")
1410 dict_keys_only = Bool(
1411 False,
1412 help="""
1413 Whether to show dict key matches only.
1414
1415 (disables all matchers except for `IPCompleter.dict_key_matcher`).
1416 """,
1417 )
1418
1419 suppress_competing_matchers = UnionTrait(
1420 [Bool(allow_none=True), DictTrait(Bool(None, allow_none=True))],
1421 default_value=None,
1422 help="""
1423 Whether to suppress completions from other *Matchers*.
1424
1425 When set to ``None`` (default) the matchers will attempt to auto-detect
1426 whether suppression of other matchers is desirable. For example, at
1427 the beginning of a line followed by `%` we expect a magic completion
1428 to be the only applicable option, and after ``my_dict['`` we usually
1429 expect a completion with an existing dictionary key.
1430
1431 If you want to disable this heuristic and see completions from all matchers,
1432 set ``IPCompleter.suppress_competing_matchers = False``.
1433 To disable the heuristic for specific matchers provide a dictionary mapping:
1434 ``IPCompleter.suppress_competing_matchers = {'IPCompleter.dict_key_matcher': False}``.
1435
1436 Set ``IPCompleter.suppress_competing_matchers = True`` to limit
1437 completions to the set of matchers with the highest priority;
1438 this is equivalent to ``IPCompleter.merge_completions`` and
1439 can be beneficial for performance, but will sometimes omit relevant
1440 candidates from matchers further down the priority list.
1441 """,
1442 ).tag(config=True)
1067 1443
1068 merge_completions = Bool(True,
1444 merge_completions = Bool(
1445 True,
1069 1446 help="""Whether to merge completion results into a single list
1070 1447
1071 1448 If False, only the completion results from the first non-empty
1072 1449 completer will be returned.
1073 """
1450
1451 As of version 8.6.0, setting the value to ``False`` is an alias for:
1452 ``IPCompleter.suppress_competing_matchers = True.``.
1453 """,
1454 ).tag(config=True)
1455
1456 disable_matchers = ListTrait(
1457 Unicode(), help="""List of matchers to disable."""
1074 1458 ).tag(config=True)
1075 omit__names = Enum((0,1,2), default_value=2,
1459
1460 omit__names = Enum(
1461 (0, 1, 2),
1462 default_value=2,
1076 1463 help="""Instruct the completer to omit private method names
1077 1464
1078 1465 Specifically, when completing on ``object.<tab>``.
@@ -1148,7 +1535,7 b' class IPCompleter(Completer):'
1148 1535 namespace=namespace,
1149 1536 global_namespace=global_namespace,
1150 1537 config=config,
1151 **kwargs
1538 **kwargs,
1152 1539 )
1153 1540
1154 1541 # List where completion matches will be stored
@@ -1177,8 +1564,8 b' class IPCompleter(Completer):'
1177 1564 #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)')
1178 1565
1179 1566 self.magic_arg_matchers = [
1180 self.magic_config_matches,
1181 self.magic_color_matches,
1567 self.magic_config_matcher,
1568 self.magic_color_matcher,
1182 1569 ]
1183 1570
1184 1571 # This is set externally by InteractiveShell
@@ -1190,27 +1577,50 b' class IPCompleter(Completer):'
1190 1577 # attribute through the `@unicode_names` property.
1191 1578 self._unicode_names = None
1192 1579
1580 self._backslash_combining_matchers = [
1581 self.latex_name_matcher,
1582 self.unicode_name_matcher,
1583 back_latex_name_matcher,
1584 back_unicode_name_matcher,
1585 self.fwd_unicode_matcher,
1586 ]
1587
1588 if not self.backslash_combining_completions:
1589 for matcher in self._backslash_combining_matchers:
1590 self.disable_matchers.append(matcher.matcher_identifier)
1591
1592 if not self.merge_completions:
1593 self.suppress_competing_matchers = True
1594
1193 1595 @property
1194 def matchers(self) -> List[Any]:
1596 def matchers(self) -> List[Matcher]:
1195 1597 """All active matcher routines for completion"""
1196 1598 if self.dict_keys_only:
1197 return [self.dict_key_matches]
1599 return [self.dict_key_matcher]
1198 1600
1199 1601 if self.use_jedi:
1200 1602 return [
1201 1603 *self.custom_matchers,
1202 self.dict_key_matches,
1203 self.file_matches,
1204 self.magic_matches,
1604 *self._backslash_combining_matchers,
1605 *self.magic_arg_matchers,
1606 self.custom_completer_matcher,
1607 self.magic_matcher,
1608 self._jedi_matcher,
1609 self.dict_key_matcher,
1610 self.file_matcher,
1205 1611 ]
1206 1612 else:
1207 1613 return [
1208 1614 *self.custom_matchers,
1209 self.dict_key_matches,
1615 *self._backslash_combining_matchers,
1616 *self.magic_arg_matchers,
1617 self.custom_completer_matcher,
1618 self.dict_key_matcher,
1619 # TODO: convert python_matches to v2 API
1620 self.magic_matcher,
1210 1621 self.python_matches,
1211 self.file_matches,
1212 self.magic_matches,
1213 self.python_func_kw_matches,
1622 self.file_matcher,
1623 self.python_func_kw_matcher,
1214 1624 ]
1215 1625
1216 1626 def all_completions(self, text:str) -> List[str]:
@@ -1231,6 +1641,14 b' class IPCompleter(Completer):'
1231 1641 return [f.replace("\\","/")
1232 1642 for f in self.glob("%s*" % text)]
1233 1643
1644 @context_matcher()
1645 def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1646 """Same as :any:`file_matches`, but adopted to new Matcher API."""
1647 matches = self.file_matches(context.token)
1648 # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter,
1649 # starts with `/home/`, `C:\`, etc)
1650 return _convert_matcher_v1_result_to_v2(matches, type="path")
1651
1234 1652 def file_matches(self, text:str)->List[str]:
1235 1653 """Match filenames, expanding ~USER type strings.
1236 1654
@@ -1243,7 +1661,11 b' class IPCompleter(Completer):'
1243 1661 only the parts after what's already been typed (instead of the
1244 1662 full completions, as is normally done). I don't think with the
1245 1663 current (as of Python 2.3) Python readline it's possible to do
1246 better."""
1664 better.
1665
1666 .. deprecated:: 8.6
1667 You can use :meth:`file_matcher` instead.
1668 """
1247 1669
1248 1670 # chars that require escaping with backslash - i.e. chars
1249 1671 # that readline treats incorrectly as delimiters, but we
@@ -1313,8 +1735,22 b' class IPCompleter(Completer):'
1313 1735 # Mark directories in input list by appending '/' to their names.
1314 1736 return [x+'/' if os.path.isdir(x) else x for x in matches]
1315 1737
1738 @context_matcher()
1739 def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1740 """Match magics."""
1741 text = context.token
1742 matches = self.magic_matches(text)
1743 result = _convert_matcher_v1_result_to_v2(matches, type="magic")
1744 is_magic_prefix = len(text) > 0 and text[0] == "%"
1745 result["suppress"] = is_magic_prefix and bool(result["completions"])
1746 return result
1747
1316 1748 def magic_matches(self, text:str):
1317 """Match magics"""
1749 """Match magics.
1750
1751 .. deprecated:: 8.6
1752 You can use :meth:`magic_matcher` instead.
1753 """
1318 1754 # Get all shell magics now rather than statically, so magics loaded at
1319 1755 # runtime show up too.
1320 1756 lsm = self.shell.magics_manager.lsmagic()
@@ -1355,8 +1791,19 b' class IPCompleter(Completer):'
1355 1791
1356 1792 return comp
1357 1793
1794 @context_matcher()
1795 def magic_config_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1796 """Match class names and attributes for %config magic."""
1797 # NOTE: uses `line_buffer` equivalent for compatibility
1798 matches = self.magic_config_matches(context.line_with_cursor)
1799 return _convert_matcher_v1_result_to_v2(matches, type="param")
1800
1358 1801 def magic_config_matches(self, text:str) -> List[str]:
1359 """ Match class names and attributes for %config magic """
1802 """Match class names and attributes for %config magic.
1803
1804 .. deprecated:: 8.6
1805 You can use :meth:`magic_config_matcher` instead.
1806 """
1360 1807 texts = text.strip().split()
1361 1808
1362 1809 if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'):
@@ -1390,8 +1837,19 b' class IPCompleter(Completer):'
1390 1837 if attr.startswith(texts[1]) ]
1391 1838 return []
1392 1839
1840 @context_matcher()
1841 def magic_color_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1842 """Match color schemes for %colors magic."""
1843 # NOTE: uses `line_buffer` equivalent for compatibility
1844 matches = self.magic_color_matches(context.line_with_cursor)
1845 return _convert_matcher_v1_result_to_v2(matches, type="param")
1846
1393 1847 def magic_color_matches(self, text:str) -> List[str] :
1394 """ Match color schemes for %colors magic"""
1848 """Match color schemes for %colors magic.
1849
1850 .. deprecated:: 8.6
1851 You can use :meth:`magic_color_matcher` instead.
1852 """
1395 1853 texts = text.split()
1396 1854 if text.endswith(' '):
1397 1855 # .split() strips off the trailing whitespace. Add '' back
@@ -1404,9 +1862,24 b' class IPCompleter(Completer):'
1404 1862 if color.startswith(prefix) ]
1405 1863 return []
1406 1864
1407 def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str) -> Iterable[Any]:
1865 @context_matcher(identifier="IPCompleter.jedi_matcher")
1866 def _jedi_matcher(self, context: CompletionContext) -> _JediMatcherResult:
1867 matches = self._jedi_matches(
1868 cursor_column=context.cursor_position,
1869 cursor_line=context.cursor_line,
1870 text=context.full_text,
1871 )
1872 return {
1873 "completions": matches,
1874 # static analysis should not suppress other matchers
1875 "suppress": False,
1876 }
1877
1878 def _jedi_matches(
1879 self, cursor_column: int, cursor_line: int, text: str
1880 ) -> Iterable[_JediCompletionLike]:
1408 1881 """
1409 Return a list of :any:`jedi.api.Completions` object from a ``text`` and
1882 Return a list of :any:`jedi.api.Completion`s object from a ``text`` and
1410 1883 cursor position.
1411 1884
1412 1885 Parameters
@@ -1422,6 +1895,9 b' class IPCompleter(Completer):'
1422 1895 -----
1423 1896 If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion`
1424 1897 object containing a string with the Jedi debug information attached.
1898
1899 .. deprecated:: 8.6
1900 You can use :meth:`_jedi_matcher` instead.
1425 1901 """
1426 1902 namespaces = [self.namespace]
1427 1903 if self.global_namespace is not None:
@@ -1558,8 +2034,18 b' class IPCompleter(Completer):'
1558 2034
1559 2035 return list(set(ret))
1560 2036
2037 @context_matcher()
2038 def python_func_kw_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2039 """Match named parameters (kwargs) of the last open function."""
2040 matches = self.python_func_kw_matches(context.token)
2041 return _convert_matcher_v1_result_to_v2(matches, type="param")
2042
1561 2043 def python_func_kw_matches(self, text):
1562 """Match named parameters (kwargs) of the last open function"""
2044 """Match named parameters (kwargs) of the last open function.
2045
2046 .. deprecated:: 8.6
2047 You can use :meth:`python_func_kw_matcher` instead.
2048 """
1563 2049
1564 2050 if "." in text: # a parameter cannot be dotted
1565 2051 return []
@@ -1654,9 +2140,20 b' class IPCompleter(Completer):'
1654 2140 return obj.dtype.names or []
1655 2141 return []
1656 2142
2143 @context_matcher()
2144 def dict_key_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2145 """Match string keys in a dictionary, after e.g. ``foo[``."""
2146 matches = self.dict_key_matches(context.token)
2147 return _convert_matcher_v1_result_to_v2(
2148 matches, type="dict key", suppress_if_matches=True
2149 )
2150
1657 2151 def dict_key_matches(self, text:str) -> List[str]:
1658 "Match string keys in a dictionary, after e.g. 'foo[' "
2152 """Match string keys in a dictionary, after e.g. ``foo[``.
1659 2153
2154 .. deprecated:: 8.6
2155 You can use :meth:`dict_key_matcher` instead.
2156 """
1660 2157
1661 2158 if self.__dict_key_regexps is not None:
1662 2159 regexps = self.__dict_key_regexps
@@ -1758,6 +2255,14 b' class IPCompleter(Completer):'
1758 2255
1759 2256 return [leading + k + suf for k in matches]
1760 2257
2258 @context_matcher()
2259 def unicode_name_matcher(self, context: CompletionContext):
2260 """Same as :any:`unicode_name_matches`, but adopted to new Matcher API."""
2261 fragment, matches = self.unicode_name_matches(context.text_until_cursor)
2262 return _convert_matcher_v1_result_to_v2(
2263 matches, type="unicode", fragment=fragment, suppress_if_matches=True
2264 )
2265
1761 2266 @staticmethod
1762 2267 def unicode_name_matches(text:str) -> Tuple[str, List[str]] :
1763 2268 """Match Latex-like syntax for unicode characters base
@@ -1780,11 +2285,24 b' class IPCompleter(Completer):'
1780 2285 pass
1781 2286 return '', []
1782 2287
2288 @context_matcher()
2289 def latex_name_matcher(self, context: CompletionContext):
2290 """Match Latex syntax for unicode characters.
2291
2292 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``Ξ±``
2293 """
2294 fragment, matches = self.latex_matches(context.text_until_cursor)
2295 return _convert_matcher_v1_result_to_v2(
2296 matches, type="latex", fragment=fragment, suppress_if_matches=True
2297 )
1783 2298
1784 2299 def latex_matches(self, text:str) -> Tuple[str, Sequence[str]]:
1785 2300 """Match Latex syntax for unicode characters.
1786 2301
1787 2302 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``Ξ±``
2303
2304 .. deprecated:: 8.6
2305 You can use :meth:`latex_name_matcher` instead.
1788 2306 """
1789 2307 slashpos = text.rfind('\\')
1790 2308 if slashpos > -1:
@@ -1801,7 +2319,25 b' class IPCompleter(Completer):'
1801 2319 return s, matches
1802 2320 return '', ()
1803 2321
2322 @context_matcher()
2323 def custom_completer_matcher(self, context):
2324 """Dispatch custom completer.
2325
2326 If a match is found, suppresses all other matchers except for Jedi.
2327 """
2328 matches = self.dispatch_custom_completer(context.token) or []
2329 result = _convert_matcher_v1_result_to_v2(
2330 matches, type=_UNKNOWN_TYPE, suppress_if_matches=True
2331 )
2332 result["ordered"] = True
2333 result["do_not_suppress"] = {_get_matcher_id(self._jedi_matcher)}
2334 return result
2335
1804 2336 def dispatch_custom_completer(self, text):
2337 """
2338 .. deprecated:: 8.6
2339 You can use :meth:`custom_completer_matcher` instead.
2340 """
1805 2341 if not self.custom_completers:
1806 2342 return
1807 2343
@@ -1955,12 +2491,25 b' class IPCompleter(Completer):'
1955 2491 """
1956 2492 deadline = time.monotonic() + _timeout
1957 2493
1958
1959 2494 before = full_text[:offset]
1960 2495 cursor_line, cursor_column = position_to_cursor(full_text, offset)
1961 2496
1962 matched_text, matches, matches_origin, jedi_matches = self._complete(
1963 full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column)
2497 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2498
2499 results = self._complete(
2500 full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column
2501 )
2502 non_jedi_results: Dict[str, SimpleMatcherResult] = {
2503 identifier: result
2504 for identifier, result in results.items()
2505 if identifier != jedi_matcher_id
2506 }
2507
2508 jedi_matches = (
2509 cast(results[jedi_matcher_id], _JediMatcherResult)["completions"]
2510 if jedi_matcher_id in results
2511 else ()
2512 )
1964 2513
1965 2514 iter_jm = iter(jedi_matches)
1966 2515 if _timeout:
@@ -1988,28 +2537,57 b' class IPCompleter(Completer):'
1988 2537
1989 2538 for jm in iter_jm:
1990 2539 delta = len(jm.name_with_symbols) - len(jm.complete)
1991 yield Completion(start=offset - delta,
2540 yield Completion(
2541 start=offset - delta,
1992 2542 end=offset,
1993 2543 text=jm.name_with_symbols,
1994 type='<unknown>', # don't compute type for speed
1995 _origin='jedi',
1996 signature='')
1997
1998
1999 start_offset = before.rfind(matched_text)
2544 type=_UNKNOWN_TYPE, # don't compute type for speed
2545 _origin="jedi",
2546 signature="",
2547 )
2000 2548
2001 2549 # TODO:
2002 2550 # Suppress this, right now just for debug.
2003 if jedi_matches and matches and self.debug:
2004 yield Completion(start=start_offset, end=offset, text='--jedi/ipython--',
2005 _origin='debug', type='none', signature='')
2551 if jedi_matches and non_jedi_results and self.debug:
2552 some_start_offset = before.rfind(
2553 next(iter(non_jedi_results.values()))["matched_fragment"]
2554 )
2555 yield Completion(
2556 start=some_start_offset,
2557 end=offset,
2558 text="--jedi/ipython--",
2559 _origin="debug",
2560 type="none",
2561 signature="",
2562 )
2563
2564 ordered = []
2565 sortable = []
2566
2567 for origin, result in non_jedi_results.items():
2568 matched_text = result["matched_fragment"]
2569 start_offset = before.rfind(matched_text)
2570 is_ordered = result.get("ordered", False)
2571 container = ordered if is_ordered else sortable
2006 2572
2007 2573 # I'm unsure if this is always true, so let's assert and see if it
2008 2574 # crash
2009 2575 assert before.endswith(matched_text)
2010 for m, t in zip(matches, matches_origin):
2011 yield Completion(start=start_offset, end=offset, text=m, _origin=t, signature='', type='<unknown>')
2012 2576
2577 for simple_completion in result["completions"]:
2578 completion = Completion(
2579 start=start_offset,
2580 end=offset,
2581 text=simple_completion.text,
2582 _origin=origin,
2583 signature="",
2584 type=simple_completion.type or _UNKNOWN_TYPE,
2585 )
2586 container.append(completion)
2587
2588 yield from list(self._deduplicate(ordered + self._sort(sortable)))[
2589 :MATCHES_LIMIT
2590 ]
2013 2591
2014 2592 def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]:
2015 2593 """Find completions for the given text and line context.
@@ -2050,7 +2628,56 b' class IPCompleter(Completer):'
2050 2628 PendingDeprecationWarning)
2051 2629 # potential todo, FOLD the 3rd throw away argument of _complete
2052 2630 # into the first 2 one.
2053 return self._complete(line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0)[:2]
2631 # TODO: Q: does the above refer to jedi completions (i.e. 0-indexed?)
2632 # TODO: should we deprecate now, or does it stay?
2633
2634 results = self._complete(
2635 line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0
2636 )
2637
2638 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2639
2640 return self._arrange_and_extract(
2641 results,
2642 # TODO: can we confirm that excluding Jedi here was a deliberate choice in previous version?
2643 skip_matchers={jedi_matcher_id},
2644 # this API does not support different start/end positions (fragments of token).
2645 abort_if_offset_changes=True,
2646 )
2647
2648 def _arrange_and_extract(
2649 self,
2650 results: Dict[str, MatcherResult],
2651 skip_matchers: Set[str],
2652 abort_if_offset_changes: bool,
2653 ):
2654
2655 sortable = []
2656 ordered = []
2657 most_recent_fragment = None
2658 for identifier, result in results.items():
2659 if identifier in skip_matchers:
2660 continue
2661 if not result["completions"]:
2662 continue
2663 if not most_recent_fragment:
2664 most_recent_fragment = result["matched_fragment"]
2665 if (
2666 abort_if_offset_changes
2667 and result["matched_fragment"] != most_recent_fragment
2668 ):
2669 break
2670 if result.get("ordered", False):
2671 ordered.extend(result["completions"])
2672 else:
2673 sortable.extend(result["completions"])
2674
2675 if not most_recent_fragment:
2676 most_recent_fragment = "" # to satisfy typechecker (and just in case)
2677
2678 return most_recent_fragment, [
2679 m.text for m in self._deduplicate(ordered + self._sort(sortable))
2680 ]
2054 2681
2055 2682 def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None,
2056 2683 full_text=None) -> _CompleteResult:
@@ -2085,14 +2712,10 b' class IPCompleter(Completer):'
2085 2712
2086 2713 Returns
2087 2714 -------
2088 A tuple of N elements which are (likely):
2089 matched_text: ? the text that the complete matched
2090 matches: list of completions ?
2091 matches_origin: ? list same length as matches, and where each completion came from
2092 jedi_matches: list of Jedi matches, have it's own structure.
2715 An ordered dictionary where keys are identifiers of completion
2716 matchers and values are ``MatcherResult``s.
2093 2717 """
2094 2718
2095
2096 2719 # if the cursor position isn't given, the only sane assumption we can
2097 2720 # make is that it's at the end of the line (the common case)
2098 2721 if cursor_pos is None:
@@ -2105,97 +2728,155 b' class IPCompleter(Completer):'
2105 2728 if (not line_buffer) and full_text:
2106 2729 line_buffer = full_text.split('\n')[cursor_line]
2107 2730 if not text: # issue #11508: check line_buffer before calling split_line
2108 text = self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else ''
2109
2110 if self.backslash_combining_completions:
2111 # allow deactivation of these on windows.
2112 base_text = text if not line_buffer else line_buffer[:cursor_pos]
2113
2114 for meth in (self.latex_matches,
2115 self.unicode_name_matches,
2116 back_latex_name_matches,
2117 back_unicode_name_matches,
2118 self.fwd_unicode_match):
2119 name_text, name_matches = meth(base_text)
2120 if name_text:
2121 return _CompleteResult(name_text, name_matches[:MATCHES_LIMIT], \
2122 [meth.__qualname__]*min(len(name_matches), MATCHES_LIMIT), ())
2123
2731 text = (
2732 self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else ""
2733 )
2124 2734
2125 2735 # If no line buffer is given, assume the input text is all there was
2126 2736 if line_buffer is None:
2127 2737 line_buffer = text
2128 2738
2739 # deprecated - do not use `line_buffer` in new code.
2129 2740 self.line_buffer = line_buffer
2130 2741 self.text_until_cursor = self.line_buffer[:cursor_pos]
2131 2742
2132 # Do magic arg matches
2133 for matcher in self.magic_arg_matchers:
2134 matches = list(matcher(line_buffer))[:MATCHES_LIMIT]
2135 if matches:
2136 origins = [matcher.__qualname__] * len(matches)
2137 return _CompleteResult(text, matches, origins, ())
2743 if not full_text:
2744 full_text = line_buffer
2745
2746 context = CompletionContext(
2747 full_text=full_text,
2748 cursor_position=cursor_pos,
2749 cursor_line=cursor_line,
2750 token=text,
2751 limit=MATCHES_LIMIT,
2752 )
2138 2753
2139 2754 # Start with a clean slate of completions
2140 matches = []
2755 results = {}
2141 2756
2142 # FIXME: we should extend our api to return a dict with completions for
2143 # different types of objects. The rlcomplete() method could then
2144 # simply collapse the dict into a list for readline, but we'd have
2145 # richer completion semantics in other environments.
2146 is_magic_prefix = len(text) > 0 and text[0] == "%"
2147 completions: Iterable[Any] = []
2148 if self.use_jedi and not is_magic_prefix:
2149 if not full_text:
2150 full_text = line_buffer
2151 completions = self._jedi_matches(
2152 cursor_pos, cursor_line, full_text)
2757 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2758
2759 suppressed_matchers = set()
2760
2761 matchers = {
2762 _get_matcher_id(matcher): matcher
2763 for matcher in sorted(
2764 self.matchers, key=_get_matcher_priority, reverse=True
2765 )
2766 }
2767
2768 for matcher_id, matcher in matchers.items():
2769 api_version = _get_matcher_api_version(matcher)
2770 matcher_id = _get_matcher_id(matcher)
2771
2772 if matcher_id in self.disable_matchers:
2773 continue
2774
2775 if matcher_id in results:
2776 warnings.warn(f"Duplicate matcher ID: {matcher_id}.")
2777
2778 if matcher_id in suppressed_matchers:
2779 continue
2153 2780
2154 if self.merge_completions:
2155 matches = []
2156 for matcher in self.matchers:
2157 2781 try:
2158 matches.extend([(m, matcher.__qualname__)
2159 for m in matcher(text)])
2782 if api_version == 1:
2783 result = _convert_matcher_v1_result_to_v2(
2784 matcher(text), type=_UNKNOWN_TYPE
2785 )
2786 elif api_version == 2:
2787 result = cast(matcher, MatcherAPIv2)(context)
2788 else:
2789 raise ValueError(f"Unsupported API version {api_version}")
2160 2790 except:
2161 2791 # Show the ugly traceback if the matcher causes an
2162 2792 # exception, but do NOT crash the kernel!
2163 2793 sys.excepthook(*sys.exc_info())
2164 else:
2165 for matcher in self.matchers:
2166 matches = [(m, matcher.__qualname__)
2167 for m in matcher(text)]
2168 if matches:
2169 break
2794 continue
2170 2795
2171 seen = set()
2172 filtered_matches = set()
2173 for m in matches:
2174 t, c = m
2175 if t not in seen:
2176 filtered_matches.add(m)
2177 seen.add(t)
2796 # set default value for matched fragment if suffix was not selected.
2797 result["matched_fragment"] = result.get("matched_fragment", context.token)
2178 2798
2179 _filtered_matches = sorted(filtered_matches, key=lambda x: completions_sorting_key(x[0]))
2799 if not suppressed_matchers:
2800 suppression_recommended = result.get("suppress", False)
2180 2801
2181 custom_res = [(m, 'custom') for m in self.dispatch_custom_completer(text) or []]
2802 suppression_config = (
2803 self.suppress_competing_matchers.get(matcher_id, None)
2804 if isinstance(self.suppress_competing_matchers, dict)
2805 else self.suppress_competing_matchers
2806 )
2807 should_suppress = (
2808 (suppression_config is True)
2809 or (suppression_recommended and (suppression_config is not False))
2810 ) and len(result["completions"])
2182 2811
2183 _filtered_matches = custom_res or _filtered_matches
2812 if should_suppress:
2813 suppression_exceptions = result.get("do_not_suppress", set())
2814 try:
2815 to_suppress = set(suppression_recommended)
2816 except TypeError:
2817 to_suppress = set(matchers)
2818 suppressed_matchers = to_suppress - suppression_exceptions
2819
2820 new_results = {}
2821 for previous_matcher_id, previous_result in results.items():
2822 if previous_matcher_id not in suppressed_matchers:
2823 new_results[previous_matcher_id] = previous_result
2824 results = new_results
2825
2826 results[matcher_id] = result
2827
2828 _, matches = self._arrange_and_extract(
2829 results,
2830 # TODO Jedi completions non included in legacy stateful API; was this deliberate or omission?
2831 # if it was omission, we can remove the filtering step, otherwise remove this comment.
2832 skip_matchers={jedi_matcher_id},
2833 abort_if_offset_changes=False,
2834 )
2184 2835
2185 _filtered_matches = _filtered_matches[:MATCHES_LIMIT]
2186 _matches = [m[0] for m in _filtered_matches]
2187 origins = [m[1] for m in _filtered_matches]
2836 # populate legacy stateful API
2837 self.matches = matches
2188 2838
2189 self.matches = _matches
2839 return results
2190 2840
2191 return _CompleteResult(text, _matches, origins, completions)
2841 @staticmethod
2842 def _deduplicate(
2843 matches: Sequence[SimpleCompletion],
2844 ) -> Iterable[SimpleCompletion]:
2845 filtered_matches = {}
2846 for match in matches:
2847 text = match.text
2848 if (
2849 text not in filtered_matches
2850 or filtered_matches[text].type == _UNKNOWN_TYPE
2851 ):
2852 filtered_matches[text] = match
2853
2854 return filtered_matches.values()
2855
2856 @staticmethod
2857 def _sort(matches: Sequence[SimpleCompletion]):
2858 return sorted(matches, key=lambda x: completions_sorting_key(x.text))
2859
2860 @context_matcher()
2861 def fwd_unicode_matcher(self, context: CompletionContext):
2862 """Same as :any:`fwd_unicode_match`, but adopted to new Matcher API."""
2863 # TODO: use `context.limit` to terminate early once we matched the maximum
2864 # number that will be used downstream; can be added as an optional to
2865 # `fwd_unicode_match(text: str, limit: int = None)` or we could re-implement here.
2866 fragment, matches = self.fwd_unicode_match(context.text_until_cursor)
2867 return _convert_matcher_v1_result_to_v2(
2868 matches, type="unicode", fragment=fragment, suppress_if_matches=True
2869 )
2192 2870
2193 2871 def fwd_unicode_match(self, text:str) -> Tuple[str, Sequence[str]]:
2194 2872 """
2195 2873 Forward match a string starting with a backslash with a list of
2196 2874 potential Unicode completions.
2197 2875
2198 Will compute list list of Unicode character names on first call and cache it.
2876 Will compute list of Unicode character names on first call and cache it.
2877
2878 .. deprecated:: 8.6
2879 You can use :meth:`fwd_unicode_matcher` instead.
2199 2880
2200 2881 Returns
2201 2882 -------
@@ -389,7 +389,19 b' class DisplayObject(object):'
389 389
390 390
391 391 class TextDisplayObject(DisplayObject):
392 """Validate that display data is text"""
392 """Create a text display object given raw data.
393
394 Parameters
395 ----------
396 data : str or unicode
397 The raw data or a URL or file to load the data from.
398 url : unicode
399 A URL to download the data from.
400 filename : unicode
401 Path to a local file to load the data from.
402 metadata : dict
403 Dict of metadata associated to be the object when displayed
404 """
393 405 def _check_data(self):
394 406 if self.data is not None and not isinstance(self.data, str):
395 407 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
@@ -613,8 +625,9 b' class JSON(DisplayObject):'
613 625 def _repr_json_(self):
614 626 return self._data_and_metadata()
615 627
628
616 629 _css_t = """var link = document.createElement("link");
617 link.ref = "stylesheet";
630 link.rel = "stylesheet";
618 631 link.type = "text/css";
619 632 link.href = "%s";
620 633 document.head.appendChild(link);
@@ -88,13 +88,7 b' class ExtensionManager(Configurable):'
88 88
89 89 with self.shell.builtin_trap:
90 90 if module_str not in sys.modules:
91 with prepended_to_syspath(self.ipython_extension_dir):
92 91 mod = import_module(module_str)
93 if mod.__file__.startswith(self.ipython_extension_dir):
94 print(("Loading extensions from {dir} is deprecated. "
95 "We recommend managing extensions like any "
96 "other Python packages, in site-packages.").format(
97 dir=compress_user(self.ipython_extension_dir)))
98 92 mod = sys.modules[module_str]
99 93 if self._call_load_ipython_extension(mod):
100 94 self.loaded.add(module_str)
@@ -155,13 +149,3 b' class ExtensionManager(Configurable):'
155 149 if hasattr(mod, 'unload_ipython_extension'):
156 150 mod.unload_ipython_extension(self.shell)
157 151 return True
158
159 @undoc
160 def install_extension(self, url, filename=None):
161 """
162 Deprecated.
163 """
164 # Ensure the extension directory exists
165 raise DeprecationWarning(
166 '`install_extension` and the `install_ext` magic have been deprecated since IPython 4.0'
167 'Use pip or other package managers to manage ipython extensions.')
@@ -429,13 +429,17 b' class EscapedCommand(TokenTransformBase):'
429 429
430 430 return lines_before + [new_line] + lines_after
431 431
432 _help_end_re = re.compile(r"""(%{0,2}
432
433 _help_end_re = re.compile(
434 r"""(%{0,2}
433 435 (?!\d)[\w*]+ # Variable name
434 (\.(?!\d)[\w*]+)* # .etc.etc
436 (\.(?!\d)[\w*]+|\[-?[0-9]+\])* # .etc.etc or [0], we only support literal integers.
435 437 )
436 438 (\?\??)$ # ? or ??
437 439 """,
438 re.VERBOSE)
440 re.VERBOSE,
441 )
442
439 443
440 444 class HelpEnd(TokenTransformBase):
441 445 """Transformer for help syntax: obj? and obj??"""
@@ -464,7 +468,8 b' class HelpEnd(TokenTransformBase):'
464 468 def transform(self, lines):
465 469 """Transform a help command found by the ``find()`` classmethod.
466 470 """
467 piece = ''.join(lines[self.start_line:self.q_line+1])
471
472 piece = "".join(lines[self.start_line : self.q_line + 1])
468 473 indent, content = piece[:self.start_col], piece[self.start_col:]
469 474 lines_before = lines[:self.start_line]
470 475 lines_after = lines[self.q_line + 1:]
@@ -543,8 +548,13 b' def has_sunken_brackets(tokens: List[tokenize.TokenInfo]):'
543 548
544 549 def show_linewise_tokens(s: str):
545 550 """For investigation and debugging"""
546 if not s.endswith('\n'):
547 s += '\n'
551 warnings.warn(
552 "show_linewise_tokens is deprecated since IPython 8.6",
553 DeprecationWarning,
554 stacklevel=2,
555 )
556 if not s.endswith("\n"):
557 s += "\n"
548 558 lines = s.splitlines(keepends=True)
549 559 for line in make_tokens_by_line(lines):
550 560 print("Line -------")
@@ -61,7 +61,7 b' from IPython.core import magic, oinspect, page, prefilter, ultratb'
61 61 from IPython.core.alias import Alias, AliasManager
62 62 from IPython.core.autocall import ExitAutocall
63 63 from IPython.core.builtin_trap import BuiltinTrap
64 from IPython.core.compilerop import CachingCompiler, check_linecache_ipython
64 from IPython.core.compilerop import CachingCompiler
65 65 from IPython.core.debugger import InterruptiblePdb
66 66 from IPython.core.display_trap import DisplayTrap
67 67 from IPython.core.displayhook import DisplayHook
@@ -147,6 +147,19 b" dedent_re = re.compile(r'^\\s+raise|^\\s+return|^\\s+pass')"
147 147 # Utilities
148 148 #-----------------------------------------------------------------------------
149 149
150
151 def is_integer_string(s: str):
152 """
153 Variant of "str.isnumeric()" that allow negative values and other ints.
154 """
155 try:
156 int(s)
157 return True
158 except ValueError:
159 return False
160 raise ValueError("Unexpected error")
161
162
150 163 @undoc
151 164 def softspace(file, newvalue):
152 165 """Copied from code.py, to remove the dependency"""
@@ -213,7 +226,9 b' class ExecutionInfo(object):'
213 226 raw_cell = (
214 227 (self.raw_cell[:50] + "..") if len(self.raw_cell) > 50 else self.raw_cell
215 228 )
216 return '<%s object at %x, raw_cell="%s" store_history=%s silent=%s shell_futures=%s cell_id=%s>' % (
229 return (
230 '<%s object at %x, raw_cell="%s" store_history=%s silent=%s shell_futures=%s cell_id=%s>'
231 % (
217 232 name,
218 233 id(self),
219 234 raw_cell,
@@ -222,6 +237,7 b' class ExecutionInfo(object):'
222 237 self.shell_futures,
223 238 self.cell_id,
224 239 )
240 )
225 241
226 242
227 243 class ExecutionResult(object):
@@ -254,6 +270,16 b' class ExecutionResult(object):'
254 270 return '<%s object at %x, execution_count=%s error_before_exec=%s error_in_exec=%s info=%s result=%s>' %\
255 271 (name, id(self), self.execution_count, self.error_before_exec, self.error_in_exec, repr(self.info), repr(self.result))
256 272
273 @functools.wraps(io_open)
274 def _modified_open(file, *args, **kwargs):
275 if file in {0, 1, 2}:
276 raise ValueError(
277 f"IPython won't let you open fd={file} by default "
278 "as it is likely to crash IPython. If you know what you are doing, "
279 "you can use builtins' open."
280 )
281
282 return io_open(file, *args, **kwargs)
257 283
258 284 class InteractiveShell(SingletonConfigurable):
259 285 """An enhanced, interactive shell for Python."""
@@ -1307,6 +1333,7 b' class InteractiveShell(SingletonConfigurable):'
1307 1333
1308 1334 ns['exit'] = self.exiter
1309 1335 ns['quit'] = self.exiter
1336 ns["open"] = _modified_open
1310 1337
1311 1338 # Sync what we've added so far to user_ns_hidden so these aren't seen
1312 1339 # by %who
@@ -1537,10 +1564,33 b' class InteractiveShell(SingletonConfigurable):'
1537 1564 Has special code to detect magic functions.
1538 1565 """
1539 1566 oname = oname.strip()
1540 if not oname.startswith(ESC_MAGIC) and \
1541 not oname.startswith(ESC_MAGIC2) and \
1542 not all(a.isidentifier() for a in oname.split(".")):
1543 return {'found': False}
1567 raw_parts = oname.split(".")
1568 parts = []
1569 parts_ok = True
1570 for p in raw_parts:
1571 if p.endswith("]"):
1572 var, *indices = p.split("[")
1573 if not var.isidentifier():
1574 parts_ok = False
1575 break
1576 parts.append(var)
1577 for ind in indices:
1578 if ind[-1] != "]" and not is_integer_string(ind[:-1]):
1579 parts_ok = False
1580 break
1581 parts.append(ind[:-1])
1582 continue
1583
1584 if not p.isidentifier():
1585 parts_ok = False
1586 parts.append(p)
1587
1588 if (
1589 not oname.startswith(ESC_MAGIC)
1590 and not oname.startswith(ESC_MAGIC2)
1591 and not parts_ok
1592 ):
1593 return {"found": False}
1544 1594
1545 1595 if namespaces is None:
1546 1596 # Namespaces to search in:
@@ -1562,7 +1612,7 b' class InteractiveShell(SingletonConfigurable):'
1562 1612 # Look for the given name by splitting it in parts. If the head is
1563 1613 # found, then we look for all the remaining parts as members, and only
1564 1614 # declare success if we can find them all.
1565 oname_parts = oname.split('.')
1615 oname_parts = parts
1566 1616 oname_head, oname_rest = oname_parts[0],oname_parts[1:]
1567 1617 for nsname,ns in namespaces:
1568 1618 try:
@@ -1579,6 +1629,9 b' class InteractiveShell(SingletonConfigurable):'
1579 1629 if idx == len(oname_rest) - 1:
1580 1630 obj = self._getattr_property(obj, part)
1581 1631 else:
1632 if is_integer_string(part):
1633 obj = obj[int(part)]
1634 else:
1582 1635 obj = getattr(obj, part)
1583 1636 except:
1584 1637 # Blanket except b/c some badly implemented objects
@@ -1643,6 +1696,9 b' class InteractiveShell(SingletonConfigurable):'
1643 1696 #
1644 1697 # The universal alternative is to traverse the mro manually
1645 1698 # searching for attrname in class dicts.
1699 if is_integer_string(attrname):
1700 return obj[int(attrname)]
1701 else:
1646 1702 attr = getattr(type(obj), attrname)
1647 1703 except AttributeError:
1648 1704 pass
@@ -1765,7 +1821,6 b' class InteractiveShell(SingletonConfigurable):'
1765 1821 self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain',
1766 1822 color_scheme='NoColor',
1767 1823 tb_offset = 1,
1768 check_cache=check_linecache_ipython,
1769 1824 debugger_cls=self.debugger_cls, parent=self)
1770 1825
1771 1826 # The instance will store a pointer to the system-wide exception hook,
@@ -297,7 +297,10 b' Currently the magic system has the following functions:""",'
297 297 oname = args and args or '_'
298 298 info = self.shell._ofind(oname)
299 299 if info['found']:
300 txt = (raw and str or pformat)( info['obj'] )
300 if raw:
301 txt = str(info["obj"])
302 else:
303 txt = pformat(info["obj"])
301 304 page.page(txt)
302 305 else:
303 306 print('Object `%s` not found' % oname)
@@ -80,6 +80,9 b' class ConfigMagics(Magics):'
80 80 Enable debug for the Completer. Mostly print extra information for
81 81 experimental jedi integration.
82 82 Current: False
83 IPCompleter.disable_matchers=<list-item-1>...
84 List of matchers to disable.
85 Current: []
83 86 IPCompleter.greedy=<Bool>
84 87 Activate greedy completion
85 88 PENDING DEPRECATION. this is now mostly taken care of with Jedi.
@@ -102,6 +105,8 b' class ConfigMagics(Magics):'
102 105 Whether to merge completion results into a single list
103 106 If False, only the completion results from the first non-empty
104 107 completer will be returned.
108 As of version 8.6.0, setting the value to ``False`` is an alias for:
109 ``IPCompleter.suppress_competing_matchers = True.``.
105 110 Current: True
106 111 IPCompleter.omit__names=<Enum>
107 112 Instruct the completer to omit private method names
@@ -117,6 +122,24 b' class ConfigMagics(Magics):'
117 122 IPCompleter.profiler_output_dir=<Unicode>
118 123 Template for path at which to output profile data for completions.
119 124 Current: '.completion_profiles'
125 IPCompleter.suppress_competing_matchers=<Union>
126 Whether to suppress completions from other *Matchers*.
127 When set to ``None`` (default) the matchers will attempt to auto-detect
128 whether suppression of other matchers is desirable. For example, at the
129 beginning of a line followed by `%` we expect a magic completion to be the
130 only applicable option, and after ``my_dict['`` we usually expect a
131 completion with an existing dictionary key.
132 If you want to disable this heuristic and see completions from all matchers,
133 set ``IPCompleter.suppress_competing_matchers = False``. To disable the
134 heuristic for specific matchers provide a dictionary mapping:
135 ``IPCompleter.suppress_competing_matchers = {'IPCompleter.dict_key_matcher':
136 False}``.
137 Set ``IPCompleter.suppress_competing_matchers = True`` to limit completions
138 to the set of matchers with the highest priority; this is equivalent to
139 ``IPCompleter.merge_completions`` and can be beneficial for performance, but
140 will sometimes omit relevant candidates from matchers further down the
141 priority list.
142 Current: None
120 143 IPCompleter.use_jedi=<Bool>
121 144 Experimental: Use Jedi to generate autocompletions. Default to True if jedi
122 145 is installed.
@@ -492,7 +492,7 b' class NamespaceMagics(Magics):'
492 492 --aggressive
493 493 Try to aggressively remove modules from sys.modules ; this
494 494 may allow you to reimport Python modules that have been updated and
495 pick up changes, but can have unattended consequences.
495 pick up changes, but can have unintended consequences.
496 496
497 497 in
498 498 reset input history
@@ -26,6 +26,7 b' backends = {'
26 26 "qt": "Qt5Agg",
27 27 "osx": "MacOSX",
28 28 "nbagg": "nbAgg",
29 "webagg": "WebAgg",
29 30 "notebook": "nbAgg",
30 31 "agg": "agg",
31 32 "svg": "svg",
@@ -16,7 +16,7 b''
16 16 # release. 'dev' as a _version_extra string means this is a development
17 17 # version
18 18 _version_major = 8
19 _version_minor = 6
19 _version_minor = 7
20 20 _version_patch = 0
21 21 _version_extra = ".dev"
22 22 # _version_extra = "rc1"
@@ -36,7 +36,7 b' version_info = (_version_major, _version_minor, _version_patch, _version_extra)'
36 36 kernel_protocol_version_info = (5, 0)
37 37 kernel_protocol_version = "%i.%i" % kernel_protocol_version_info
38 38
39 license = 'BSD'
39 license = "BSD-3-Clause"
40 40
41 41 authors = {'Fernando' : ('Fernando Perez','fperez.net@gmail.com'),
42 42 'Janko' : ('Janko Hauser','jhauser@zscout.de'),
@@ -1,4 +1,4 b''
1 1 # coding: iso-8859-5
2 2 # (Unlikely to be the default encoding for most testers.)
3 3 # οΏ½οΏ½οΏ½οΏ½οΏ½οΏ½οΏ½οΏ½οΏ½οΏ½οΏ½οΏ½οΏ½οΏ½οΏ½οΏ½οΏ½οΏ½οΏ½ <- Cyrillic characters
4 u = 'οΏ½οΏ½οΏ½οΏ½'
4 u = "οΏ½οΏ½οΏ½οΏ½"
@@ -24,6 +24,9 b' from IPython.core.completer import ('
24 24 provisionalcompleter,
25 25 match_dict_keys,
26 26 _deduplicate_completions,
27 completion_matcher,
28 SimpleCompletion,
29 CompletionContext,
27 30 )
28 31
29 32 # -----------------------------------------------------------------------------
@@ -109,6 +112,16 b' def greedy_completion():'
109 112 ip.Completer.greedy = greedy_original
110 113
111 114
115 @contextmanager
116 def custom_matchers(matchers):
117 ip = get_ipython()
118 try:
119 ip.Completer.custom_matchers.extend(matchers)
120 yield
121 finally:
122 ip.Completer.custom_matchers.clear()
123
124
112 125 def test_protect_filename():
113 126 if sys.platform == "win32":
114 127 pairs = [
@@ -298,7 +311,7 b' class TestCompleter(unittest.TestCase):'
298 311 ip = get_ipython()
299 312
300 313 name, matches = ip.complete("\\β…€")
301 self.assertEqual(matches, ("\\ROMAN NUMERAL FIVE",))
314 self.assertEqual(matches, ["\\ROMAN NUMERAL FIVE"])
302 315
303 316 def test_forward_unicode_completion(self):
304 317 ip = get_ipython()
@@ -379,6 +392,12 b' class TestCompleter(unittest.TestCase):'
379 392
380 393 def test_quoted_file_completions(self):
381 394 ip = get_ipython()
395
396 def _(text):
397 return ip.Completer._complete(
398 cursor_line=0, cursor_pos=len(text), full_text=text
399 )["IPCompleter.file_matcher"]["completions"]
400
382 401 with TemporaryWorkingDirectory():
383 402 name = "foo'bar"
384 403 open(name, "w", encoding="utf-8").close()
@@ -387,25 +406,16 b' class TestCompleter(unittest.TestCase):'
387 406 escaped = name if sys.platform == "win32" else "foo\\'bar"
388 407
389 408 # Single quote matches embedded single quote
390 text = "open('foo"
391 c = ip.Completer._complete(
392 cursor_line=0, cursor_pos=len(text), full_text=text
393 )[1]
394 self.assertEqual(c, [escaped])
409 c = _("open('foo")[0]
410 self.assertEqual(c.text, escaped)
395 411
396 412 # Double quote requires no escape
397 text = 'open("foo'
398 c = ip.Completer._complete(
399 cursor_line=0, cursor_pos=len(text), full_text=text
400 )[1]
401 self.assertEqual(c, [name])
413 c = _('open("foo')[0]
414 self.assertEqual(c.text, name)
402 415
403 416 # No quote requires an escape
404 text = "%ls foo"
405 c = ip.Completer._complete(
406 cursor_line=0, cursor_pos=len(text), full_text=text
407 )[1]
408 self.assertEqual(c, [escaped])
417 c = _("%ls foo")[0]
418 self.assertEqual(c.text, escaped)
409 419
410 420 def test_all_completions_dups(self):
411 421 """
@@ -475,6 +485,17 b' class TestCompleter(unittest.TestCase):'
475 485 "encoding" in c.signature
476 486 ), "Signature of function was not found by completer"
477 487
488 def test_completions_have_type(self):
489 """
490 Lets make sure matchers provide completion type.
491 """
492 ip = get_ipython()
493 with provisionalcompleter():
494 ip.Completer.use_jedi = False
495 completions = ip.Completer.completions("%tim", 3)
496 c = next(completions) # should be `%time` or similar
497 assert c.type == "magic", "Type of magic was not assigned by completer"
498
478 499 @pytest.mark.xfail(reason="Known failure on jedi<=0.18.0")
479 500 def test_deduplicate_completions(self):
480 501 """
@@ -1273,3 +1294,153 b' class TestCompleter(unittest.TestCase):'
1273 1294 completions = completer.completions(text, len(text))
1274 1295 for c in completions:
1275 1296 self.assertEqual(c.text[0], "%")
1297
1298 def test_fwd_unicode_restricts(self):
1299 ip = get_ipython()
1300 completer = ip.Completer
1301 text = "\\ROMAN NUMERAL FIVE"
1302
1303 with provisionalcompleter():
1304 completer.use_jedi = True
1305 completions = [
1306 completion.text for completion in completer.completions(text, len(text))
1307 ]
1308 self.assertEqual(completions, ["\u2164"])
1309
1310 def test_dict_key_restrict_to_dicts(self):
1311 """Test that dict key suppresses non-dict completion items"""
1312 ip = get_ipython()
1313 c = ip.Completer
1314 d = {"abc": None}
1315 ip.user_ns["d"] = d
1316
1317 text = 'd["a'
1318
1319 def _():
1320 with provisionalcompleter():
1321 c.use_jedi = True
1322 return [
1323 completion.text for completion in c.completions(text, len(text))
1324 ]
1325
1326 completions = _()
1327 self.assertEqual(completions, ["abc"])
1328
1329 # check that it can be disabled in granular manner:
1330 cfg = Config()
1331 cfg.IPCompleter.suppress_competing_matchers = {
1332 "IPCompleter.dict_key_matcher": False
1333 }
1334 c.update_config(cfg)
1335
1336 completions = _()
1337 self.assertIn("abc", completions)
1338 self.assertGreater(len(completions), 1)
1339
1340 def test_matcher_suppression(self):
1341 @completion_matcher(identifier="a_matcher")
1342 def a_matcher(text):
1343 return ["completion_a"]
1344
1345 @completion_matcher(identifier="b_matcher", api_version=2)
1346 def b_matcher(context: CompletionContext):
1347 text = context.token
1348 result = {"completions": [SimpleCompletion("completion_b")]}
1349
1350 if text == "suppress c":
1351 result["suppress"] = {"c_matcher"}
1352
1353 if text.startswith("suppress all"):
1354 result["suppress"] = True
1355 if text == "suppress all but c":
1356 result["do_not_suppress"] = {"c_matcher"}
1357 if text == "suppress all but a":
1358 result["do_not_suppress"] = {"a_matcher"}
1359
1360 return result
1361
1362 @completion_matcher(identifier="c_matcher")
1363 def c_matcher(text):
1364 return ["completion_c"]
1365
1366 with custom_matchers([a_matcher, b_matcher, c_matcher]):
1367 ip = get_ipython()
1368 c = ip.Completer
1369
1370 def _(text, expected):
1371 c.use_jedi = False
1372 s, matches = c.complete(text)
1373 self.assertEqual(expected, matches)
1374
1375 _("do not suppress", ["completion_a", "completion_b", "completion_c"])
1376 _("suppress all", ["completion_b"])
1377 _("suppress all but a", ["completion_a", "completion_b"])
1378 _("suppress all but c", ["completion_b", "completion_c"])
1379
1380 def configure(suppression_config):
1381 cfg = Config()
1382 cfg.IPCompleter.suppress_competing_matchers = suppression_config
1383 c.update_config(cfg)
1384
1385 # test that configuration takes priority over the run-time decisions
1386
1387 configure(False)
1388 _("suppress all", ["completion_a", "completion_b", "completion_c"])
1389
1390 configure({"b_matcher": False})
1391 _("suppress all", ["completion_a", "completion_b", "completion_c"])
1392
1393 configure({"a_matcher": False})
1394 _("suppress all", ["completion_b"])
1395
1396 configure({"b_matcher": True})
1397 _("do not suppress", ["completion_b"])
1398
1399 def test_matcher_disabling(self):
1400 @completion_matcher(identifier="a_matcher")
1401 def a_matcher(text):
1402 return ["completion_a"]
1403
1404 @completion_matcher(identifier="b_matcher")
1405 def b_matcher(text):
1406 return ["completion_b"]
1407
1408 def _(expected):
1409 s, matches = c.complete("completion_")
1410 self.assertEqual(expected, matches)
1411
1412 with custom_matchers([a_matcher, b_matcher]):
1413 ip = get_ipython()
1414 c = ip.Completer
1415
1416 _(["completion_a", "completion_b"])
1417
1418 cfg = Config()
1419 cfg.IPCompleter.disable_matchers = ["b_matcher"]
1420 c.update_config(cfg)
1421
1422 _(["completion_a"])
1423
1424 cfg.IPCompleter.disable_matchers = []
1425 c.update_config(cfg)
1426
1427 def test_matcher_priority(self):
1428 @completion_matcher(identifier="a_matcher", priority=0, api_version=2)
1429 def a_matcher(text):
1430 return {"completions": [SimpleCompletion("completion_a")], "suppress": True}
1431
1432 @completion_matcher(identifier="b_matcher", priority=2, api_version=2)
1433 def b_matcher(text):
1434 return {"completions": [SimpleCompletion("completion_b")], "suppress": True}
1435
1436 def _(expected):
1437 s, matches = c.complete("completion_")
1438 self.assertEqual(expected, matches)
1439
1440 with custom_matchers([a_matcher, b_matcher]):
1441 ip = get_ipython()
1442 c = ip.Completer
1443
1444 _(["completion_b"])
1445 a_matcher.matcher_priority = 3
1446 _(["completion_a"])
@@ -103,6 +103,18 b' class InteractiveShellTestCase(unittest.TestCase):'
103 103 res = ip.run_cell("raise = 3")
104 104 self.assertIsInstance(res.error_before_exec, SyntaxError)
105 105
106 def test_open_standard_input_stream(self):
107 res = ip.run_cell("open(0)")
108 self.assertIsInstance(res.error_in_exec, ValueError)
109
110 def test_open_standard_output_stream(self):
111 res = ip.run_cell("open(1)")
112 self.assertIsInstance(res.error_in_exec, ValueError)
113
114 def test_open_standard_error_stream(self):
115 res = ip.run_cell("open(2)")
116 self.assertIsInstance(res.error_in_exec, ValueError)
117
106 118 def test_In_variable(self):
107 119 "Verify that In variable grows with user input (GH-284)"
108 120 oldlen = len(ip.user_ns['In'])
@@ -1,15 +1,11 b''
1 1 """Tests for the key interactiveshell module, where the main ipython class is defined.
2 2 """
3 #-----------------------------------------------------------------------------
4 # Module imports
5 #-----------------------------------------------------------------------------
6 3
4 import stack_data
5 import sys
7 6
8 # our own packages
7 SV_VERSION = tuple([int(x) for x in stack_data.__version__.split(".")[0:2]])
9 8
10 #-----------------------------------------------------------------------------
11 # Test functions
12 #-----------------------------------------------------------------------------
13 9
14 10 def test_reset():
15 11 """reset must clear most namespaces."""
@@ -170,7 +166,10 b' def doctest_tb_sysexit():'
170 166 """
171 167
172 168
173 def doctest_tb_sysexit_verbose():
169 if sys.version_info >= (3, 9):
170 if SV_VERSION < (0, 6):
171
172 def doctest_tb_sysexit_verbose_stack_data_05():
174 173 """
175 174 In [18]: %run simpleerr.py exit
176 175 An exception has occurred, use %tb to see the full traceback.
@@ -210,6 +209,50 b' def doctest_tb_sysexit_verbose():'
210 209 SystemExit: (2, 'Mode = exit')
211 210 """
212 211
212 else:
213 # currently the only difference is
214 # + mode = 'exit'
215
216 def doctest_tb_sysexit_verbose_stack_data_06():
217 """
218 In [18]: %run simpleerr.py exit
219 An exception has occurred, use %tb to see the full traceback.
220 SystemExit: (1, 'Mode = exit')
221
222 In [19]: %run simpleerr.py exit 2
223 An exception has occurred, use %tb to see the full traceback.
224 SystemExit: (2, 'Mode = exit')
225
226 In [23]: %xmode verbose
227 Exception reporting mode: Verbose
228
229 In [24]: %tb
230 ---------------------------------------------------------------------------
231 SystemExit Traceback (most recent call last)
232 <BLANKLINE>
233 ...
234 30 except IndexError:
235 31 mode = 'div'
236 ---> 33 bar(mode)
237 mode = 'exit'
238 <BLANKLINE>
239 ... in bar(mode='exit')
240 ... except:
241 ... stat = 1
242 ---> ... sysexit(stat, mode)
243 mode = 'exit'
244 stat = 2
245 ... else:
246 ... raise ValueError('Unknown mode')
247 <BLANKLINE>
248 ... in sysexit(stat=2, mode='exit')
249 10 def sysexit(stat, mode):
250 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
251 stat = 2
252 mode = 'exit'
253 <BLANKLINE>
254 SystemExit: (2, 'Mode = exit')
255 """
213 256
214 257 def test_run_cell():
215 258 import textwrap
@@ -84,7 +84,7 b' def test_extract_symbols_raises_exception_with_non_python_code():'
84 84 def test_magic_not_found():
85 85 # magic not found raises UsageError
86 86 with pytest.raises(UsageError):
87 _ip.magic('doesntexist')
87 _ip.run_line_magic("doesntexist", "")
88 88
89 89 # ensure result isn't success when a magic isn't found
90 90 result = _ip.run_cell('%doesntexist')
@@ -116,13 +116,14 b' def test_config():'
116 116 magic.
117 117 """
118 118 ## should not raise.
119 _ip.magic('config')
119 _ip.run_line_magic("config", "")
120
120 121
121 122 def test_config_available_configs():
122 123 """ test that config magic prints available configs in unique and
123 124 sorted order. """
124 125 with capture_output() as captured:
125 _ip.magic('config')
126 _ip.run_line_magic("config", "")
126 127
127 128 stdout = captured.stdout
128 129 config_classes = stdout.strip().split('\n')[1:]
@@ -131,7 +132,7 b' def test_config_available_configs():'
131 132 def test_config_print_class():
132 133 """ test that config with a classname prints the class's options. """
133 134 with capture_output() as captured:
134 _ip.magic('config TerminalInteractiveShell')
135 _ip.run_line_magic("config", "TerminalInteractiveShell")
135 136
136 137 stdout = captured.stdout
137 138 assert re.match(
@@ -144,7 +145,7 b' def test_rehashx():'
144 145 _ip.alias_manager.clear_aliases()
145 146 del _ip.db['syscmdlist']
146 147
147 _ip.magic('rehashx')
148 _ip.run_line_magic("rehashx", "")
148 149 # Practically ALL ipython development systems will have more than 10 aliases
149 150
150 151 assert len(_ip.alias_manager.aliases) > 10
@@ -277,11 +278,11 b' def test_macro():'
277 278 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
278 279 for i, cmd in enumerate(cmds, start=1):
279 280 ip.history_manager.store_inputs(i, cmd)
280 ip.magic("macro test 1-3")
281 ip.run_line_magic("macro", "test 1-3")
281 282 assert ip.user_ns["test"].value == "\n".join(cmds) + "\n"
282 283
283 284 # List macros
284 assert "test" in ip.magic("macro")
285 assert "test" in ip.run_line_magic("macro", "")
285 286
286 287
287 288 def test_macro_run():
@@ -302,7 +303,7 b' def test_magic_magic():'
302 303 """Test %magic"""
303 304 ip = get_ipython()
304 305 with capture_output() as captured:
305 ip.magic("magic")
306 ip.run_line_magic("magic", "")
306 307
307 308 stdout = captured.stdout
308 309 assert "%magic" in stdout
@@ -316,7 +317,7 b' def test_numpy_reset_array_undec():'
316 317 _ip.ex("import numpy as np")
317 318 _ip.ex("a = np.empty(2)")
318 319 assert "a" in _ip.user_ns
319 _ip.magic("reset -f array")
320 _ip.run_line_magic("reset", "-f array")
320 321 assert "a" not in _ip.user_ns
321 322
322 323
@@ -326,7 +327,7 b' def test_reset_out():'
326 327 # test '%reset -f out', make an Out prompt
327 328 _ip.run_cell("parrot", store_history=True)
328 329 assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")]
329 _ip.magic("reset -f out")
330 _ip.run_line_magic("reset", "-f out")
330 331 assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")]
331 332 assert len(_ip.user_ns["Out"]) == 0
332 333
@@ -336,7 +337,7 b' def test_reset_in():'
336 337 # test '%reset -f in'
337 338 _ip.run_cell("parrot", store_history=True)
338 339 assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
339 _ip.magic("%reset -f in")
340 _ip.run_line_magic("reset", "-f in")
340 341 assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
341 342 assert len(set(_ip.user_ns["In"])) == 1
342 343
@@ -344,10 +345,10 b' def test_reset_in():'
344 345 def test_reset_dhist():
345 346 "Test '%reset dhist' magic"
346 347 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
347 _ip.magic("cd " + os.path.dirname(pytest.__file__))
348 _ip.magic("cd -")
348 _ip.run_line_magic("cd", os.path.dirname(pytest.__file__))
349 _ip.run_line_magic("cd", "-")
349 350 assert len(_ip.user_ns["_dh"]) > 0
350 _ip.magic("reset -f dhist")
351 _ip.run_line_magic("reset", "-f dhist")
351 352 assert len(_ip.user_ns["_dh"]) == 0
352 353 _ip.run_cell("_dh = [d for d in tmp]") # restore
353 354
@@ -472,8 +473,8 b' def test_time_local_ns():'
472 473
473 474 def test_doctest_mode():
474 475 "Toggle doctest_mode twice, it should be a no-op and run without error"
475 _ip.magic('doctest_mode')
476 _ip.magic('doctest_mode')
476 _ip.run_line_magic("doctest_mode", "")
477 _ip.run_line_magic("doctest_mode", "")
477 478
478 479
479 480 def test_parse_options():
@@ -498,7 +499,9 b' def test_parse_options_preserve_non_option_string():'
498 499 def test_run_magic_preserve_code_block():
499 500 """Test to assert preservation of non-option part of magic-block, while running magic."""
500 501 _ip.user_ns["spaces"] = []
501 _ip.magic("timeit -n1 -r1 spaces.append([s.count(' ') for s in ['document']])")
502 _ip.run_line_magic(
503 "timeit", "-n1 -r1 spaces.append([s.count(' ') for s in ['document']])"
504 )
502 505 assert _ip.user_ns["spaces"] == [[0]]
503 506
504 507
@@ -509,13 +512,13 b' def test_dirops():'
509 512 startdir = os.getcwd()
510 513 ipdir = os.path.realpath(_ip.ipython_dir)
511 514 try:
512 _ip.magic('cd "%s"' % ipdir)
515 _ip.run_line_magic("cd", '"%s"' % ipdir)
513 516 assert curpath() == ipdir
514 _ip.magic('cd -')
517 _ip.run_line_magic("cd", "-")
515 518 assert curpath() == startdir
516 _ip.magic('pushd "%s"' % ipdir)
519 _ip.run_line_magic("pushd", '"%s"' % ipdir)
517 520 assert curpath() == ipdir
518 _ip.magic('popd')
521 _ip.run_line_magic("popd", "")
519 522 assert curpath() == startdir
520 523 finally:
521 524 os.chdir(startdir)
@@ -542,7 +545,7 b' def test_xmode():'
542 545 # Calling xmode three times should be a no-op
543 546 xmode = _ip.InteractiveTB.mode
544 547 for i in range(4):
545 _ip.magic("xmode")
548 _ip.run_line_magic("xmode", "")
546 549 assert _ip.InteractiveTB.mode == xmode
547 550
548 551 def test_reset_hard():
@@ -557,7 +560,7 b' def test_reset_hard():'
557 560 _ip.run_cell("a")
558 561
559 562 assert monitor == []
560 _ip.magic("reset -f")
563 _ip.run_line_magic("reset", "-f")
561 564 assert monitor == [1]
562 565
563 566 class TestXdel(tt.TempFileMixin):
@@ -570,14 +573,14 b' class TestXdel(tt.TempFileMixin):'
570 573 "a = A()\n")
571 574 self.mktmp(src)
572 575 # %run creates some hidden references...
573 _ip.magic("run %s" % self.fname)
576 _ip.run_line_magic("run", "%s" % self.fname)
574 577 # ... as does the displayhook.
575 578 _ip.run_cell("a")
576 579
577 580 monitor = _ip.user_ns["A"].monitor
578 581 assert monitor == []
579 582
580 _ip.magic("xdel a")
583 _ip.run_line_magic("xdel", "a")
581 584
582 585 # Check that a's __del__ method has been called.
583 586 gc.collect(0)
@@ -614,7 +617,7 b' def test_whos():'
614 617 def __repr__(self):
615 618 raise Exception()
616 619 _ip.user_ns['a'] = A()
617 _ip.magic("whos")
620 _ip.run_line_magic("whos", "")
618 621
619 622 def doctest_precision():
620 623 """doctest for %precision
@@ -655,12 +658,12 b' def test_psearch():'
655 658 def test_timeit_shlex():
656 659 """test shlex issues with timeit (#1109)"""
657 660 _ip.ex("def f(*a,**kw): pass")
658 _ip.magic('timeit -n1 "this is a bug".count(" ")')
659 _ip.magic('timeit -r1 -n1 f(" ", 1)')
660 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
661 _ip.magic('timeit -r1 -n1 ("a " + "b")')
662 _ip.magic('timeit -r1 -n1 f("a " + "b")')
663 _ip.magic('timeit -r1 -n1 f("a " + "b ")')
661 _ip.run_line_magic("timeit", '-n1 "this is a bug".count(" ")')
662 _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1)')
663 _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1, " ", 2, " ")')
664 _ip.run_line_magic("timeit", '-r1 -n1 ("a " + "b")')
665 _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b")')
666 _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b ")')
664 667
665 668
666 669 def test_timeit_special_syntax():
@@ -738,9 +741,9 b' def test_extension():'
738 741 try:
739 742 _ip.user_ns.pop('arq', None)
740 743 invalidate_caches() # Clear import caches
741 _ip.magic("load_ext daft_extension")
744 _ip.run_line_magic("load_ext", "daft_extension")
742 745 assert _ip.user_ns["arq"] == 185
743 _ip.magic("unload_ext daft_extension")
746 _ip.run_line_magic("unload_ext", "daft_extension")
744 747 assert 'arq' not in _ip.user_ns
745 748 finally:
746 749 sys.path.remove(daft_path)
@@ -755,17 +758,17 b' def test_notebook_export_json():'
755 758 _ip.history_manager.store_inputs(i, cmd)
756 759 with TemporaryDirectory() as td:
757 760 outfile = os.path.join(td, "nb.ipynb")
758 _ip.magic("notebook %s" % outfile)
761 _ip.run_line_magic("notebook", "%s" % outfile)
759 762
760 763
761 764 class TestEnv(TestCase):
762 765
763 766 def test_env(self):
764 env = _ip.magic("env")
767 env = _ip.run_line_magic("env", "")
765 768 self.assertTrue(isinstance(env, dict))
766 769
767 770 def test_env_secret(self):
768 env = _ip.magic("env")
771 env = _ip.run_line_magic("env", "")
769 772 hidden = "<hidden>"
770 773 with mock.patch.dict(
771 774 os.environ,
@@ -776,35 +779,35 b' class TestEnv(TestCase):'
776 779 "VAR": "abc"
777 780 }
778 781 ):
779 env = _ip.magic("env")
782 env = _ip.run_line_magic("env", "")
780 783 assert env["API_KEY"] == hidden
781 784 assert env["SECRET_THING"] == hidden
782 785 assert env["JUPYTER_TOKEN"] == hidden
783 786 assert env["VAR"] == "abc"
784 787
785 788 def test_env_get_set_simple(self):
786 env = _ip.magic("env var val1")
789 env = _ip.run_line_magic("env", "var val1")
787 790 self.assertEqual(env, None)
788 self.assertEqual(os.environ['var'], 'val1')
789 self.assertEqual(_ip.magic("env var"), 'val1')
790 env = _ip.magic("env var=val2")
791 self.assertEqual(os.environ["var"], "val1")
792 self.assertEqual(_ip.run_line_magic("env", "var"), "val1")
793 env = _ip.run_line_magic("env", "var=val2")
791 794 self.assertEqual(env, None)
792 795 self.assertEqual(os.environ['var'], 'val2')
793 796
794 797 def test_env_get_set_complex(self):
795 env = _ip.magic("env var 'val1 '' 'val2")
798 env = _ip.run_line_magic("env", "var 'val1 '' 'val2")
796 799 self.assertEqual(env, None)
797 800 self.assertEqual(os.environ['var'], "'val1 '' 'val2")
798 self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2")
799 env = _ip.magic('env var=val2 val3="val4')
801 self.assertEqual(_ip.run_line_magic("env", "var"), "'val1 '' 'val2")
802 env = _ip.run_line_magic("env", 'var=val2 val3="val4')
800 803 self.assertEqual(env, None)
801 804 self.assertEqual(os.environ['var'], 'val2 val3="val4')
802 805
803 806 def test_env_set_bad_input(self):
804 self.assertRaises(UsageError, lambda: _ip.magic("set_env var"))
807 self.assertRaises(UsageError, lambda: _ip.run_line_magic("set_env", "var"))
805 808
806 809 def test_env_set_whitespace(self):
807 self.assertRaises(UsageError, lambda: _ip.magic("env var A=B"))
810 self.assertRaises(UsageError, lambda: _ip.run_line_magic("env", "var A=B"))
808 811
809 812
810 813 class CellMagicTestCase(TestCase):
@@ -1308,7 +1311,7 b' def test_ls_magic():'
1308 1311 ip = get_ipython()
1309 1312 json_formatter = ip.display_formatter.formatters['application/json']
1310 1313 json_formatter.enabled = True
1311 lsmagic = ip.magic('lsmagic')
1314 lsmagic = ip.run_line_magic("lsmagic", "")
1312 1315 with warnings.catch_warnings(record=True) as w:
1313 1316 j = json_formatter(lsmagic)
1314 1317 assert sorted(j) == ["cell", "line"]
@@ -1358,16 +1361,16 b' def test_logging_magic_not_quiet():'
1358 1361
1359 1362
1360 1363 def test_time_no_var_expand():
1361 _ip.user_ns['a'] = 5
1362 _ip.user_ns['b'] = []
1363 _ip.magic('time b.append("{a}")')
1364 assert _ip.user_ns['b'] == ['{a}']
1364 _ip.user_ns["a"] = 5
1365 _ip.user_ns["b"] = []
1366 _ip.run_line_magic("time", 'b.append("{a}")')
1367 assert _ip.user_ns["b"] == ["{a}"]
1365 1368
1366 1369
1367 1370 # this is slow, put at the end for local testing.
1368 1371 def test_timeit_arguments():
1369 1372 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1370 _ip.magic("timeit -n1 -r1 a=('#')")
1373 _ip.run_line_magic("timeit", "-n1 -r1 a=('#')")
1371 1374
1372 1375
1373 1376 MINIMAL_LAZY_MAGIC = """
@@ -1442,7 +1445,7 b' def test_run_module_from_import_hook():'
1442 1445 sys.meta_path.insert(0, MyTempImporter())
1443 1446
1444 1447 with capture_output() as captured:
1445 _ip.magic("run -m my_tmp")
1448 _ip.run_line_magic("run", "-m my_tmp")
1446 1449 _ip.run_cell("import my_tmp")
1447 1450
1448 1451 output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n"
@@ -5,6 +5,7 b''
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 from contextlib import contextmanager
8 9 from inspect import signature, Signature, Parameter
9 10 import inspect
10 11 import os
@@ -43,7 +44,7 b' class SourceModuleMainTest:'
43 44 # defined, if any code is inserted above, the following line will need to be
44 45 # updated. Do NOT insert any whitespace between the next line and the function
45 46 # definition below.
46 THIS_LINE_NUMBER = 46 # Put here the actual number of this line
47 THIS_LINE_NUMBER = 47 # Put here the actual number of this line
47 48
48 49
49 50 def test_find_source_lines():
@@ -345,6 +346,70 b' def test_pdef():'
345 346 inspector.pdef(foo, 'foo')
346 347
347 348
349 @contextmanager
350 def cleanup_user_ns(**kwargs):
351 """
352 On exit delete all the keys that were not in user_ns before entering.
353
354 It does not restore old values !
355
356 Parameters
357 ----------
358
359 **kwargs
360 used to update ip.user_ns
361
362 """
363 try:
364 known = set(ip.user_ns.keys())
365 ip.user_ns.update(kwargs)
366 yield
367 finally:
368 added = set(ip.user_ns.keys()) - known
369 for k in added:
370 del ip.user_ns[k]
371
372
373 def test_pinfo_getindex():
374 def dummy():
375 """
376 MARKER
377 """
378
379 container = [dummy]
380 with cleanup_user_ns(container=container):
381 with AssertPrints("MARKER"):
382 ip._inspect("pinfo", "container[0]", detail_level=0)
383 assert "container" not in ip.user_ns.keys()
384
385
386 def test_qmark_getindex():
387 def dummy():
388 """
389 MARKER 2
390 """
391
392 container = [dummy]
393 with cleanup_user_ns(container=container):
394 with AssertPrints("MARKER 2"):
395 ip.run_cell("container[0]?")
396 assert "container" not in ip.user_ns.keys()
397
398
399 def test_qmark_getindex_negatif():
400 def dummy():
401 """
402 MARKER 3
403 """
404
405 container = [dummy]
406 with cleanup_user_ns(container=container):
407 with AssertPrints("MARKER 3"):
408 ip.run_cell("container[-1]?")
409 assert "container" not in ip.user_ns.keys()
410
411
412
348 413 def test_pinfo_nonascii():
349 414 # See gh-1177
350 415 from . import nonascii2
@@ -180,13 +180,13 b' class TestMagicRunPass(tt.TempFileMixin):'
180 180 _ip = get_ipython()
181 181 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
182 182 # See below and ticket https://bugs.launchpad.net/bugs/366353
183 _ip.magic('run %s' % self.fname)
183 _ip.run_line_magic("run", self.fname)
184 184
185 185 def run_tmpfile_p(self):
186 186 _ip = get_ipython()
187 187 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
188 188 # See below and ticket https://bugs.launchpad.net/bugs/366353
189 _ip.magic('run -p %s' % self.fname)
189 _ip.run_line_magic("run", "-p %s" % self.fname)
190 190
191 191 def test_builtins_id(self):
192 192 """Check that %run doesn't damage __builtins__ """
@@ -216,20 +216,20 b' class TestMagicRunPass(tt.TempFileMixin):'
216 216 def test_run_debug_twice(self):
217 217 # https://github.com/ipython/ipython/issues/10028
218 218 _ip = get_ipython()
219 with tt.fake_input(['c']):
220 _ip.magic('run -d %s' % self.fname)
221 with tt.fake_input(['c']):
222 _ip.magic('run -d %s' % self.fname)
219 with tt.fake_input(["c"]):
220 _ip.run_line_magic("run", "-d %s" % self.fname)
221 with tt.fake_input(["c"]):
222 _ip.run_line_magic("run", "-d %s" % self.fname)
223 223
224 224 def test_run_debug_twice_with_breakpoint(self):
225 225 """Make a valid python temp file."""
226 226 _ip = get_ipython()
227 with tt.fake_input(['b 2', 'c', 'c']):
228 _ip.magic('run -d %s' % self.fname)
227 with tt.fake_input(["b 2", "c", "c"]):
228 _ip.run_line_magic("run", "-d %s" % self.fname)
229 229
230 with tt.fake_input(['c']):
231 with tt.AssertNotPrints('KeyError'):
232 _ip.magic('run -d %s' % self.fname)
230 with tt.fake_input(["c"]):
231 with tt.AssertNotPrints("KeyError"):
232 _ip.run_line_magic("run", "-d %s" % self.fname)
233 233
234 234
235 235 class TestMagicRunSimple(tt.TempFileMixin):
@@ -239,7 +239,7 b' class TestMagicRunSimple(tt.TempFileMixin):'
239 239 src = ("class foo: pass\n"
240 240 "def f(): return foo()")
241 241 self.mktmp(src)
242 _ip.magic("run %s" % self.fname)
242 _ip.run_line_magic("run", str(self.fname))
243 243 _ip.run_cell("t = isinstance(f(), foo)")
244 244 assert _ip.user_ns["t"] is True
245 245
@@ -277,7 +277,7 b' class TestMagicRunSimple(tt.TempFileMixin):'
277 277 " break\n" % ("run " + empty.fname)
278 278 )
279 279 self.mktmp(src)
280 _ip.magic("run %s" % self.fname)
280 _ip.run_line_magic("run", str(self.fname))
281 281 _ip.run_cell("ip == get_ipython()")
282 282 assert _ip.user_ns["i"] == 4
283 283
@@ -288,8 +288,8 b' class TestMagicRunSimple(tt.TempFileMixin):'
288 288 with tt.TempFileMixin() as empty:
289 289 empty.mktmp("")
290 290
291 _ip.magic("run %s" % self.fname)
292 _ip.magic("run %s" % empty.fname)
291 _ip.run_line_magic("run", self.fname)
292 _ip.run_line_magic("run", empty.fname)
293 293 assert _ip.user_ns["afunc"]() == 1
294 294
295 295 def test_tclass(self):
@@ -323,22 +323,22 b' tclass.py: deleting object: C-third'
323 323 self.mktmp(src)
324 324 _ip.run_cell("zz = 23")
325 325 try:
326 _ip.magic("run -i %s" % self.fname)
326 _ip.run_line_magic("run", "-i %s" % self.fname)
327 327 assert _ip.user_ns["yy"] == 23
328 328 finally:
329 _ip.magic('reset -f')
329 _ip.run_line_magic("reset", "-f")
330 330
331 331 _ip.run_cell("zz = 23")
332 332 try:
333 _ip.magic("run -i %s" % self.fname)
333 _ip.run_line_magic("run", "-i %s" % self.fname)
334 334 assert _ip.user_ns["yy"] == 23
335 335 finally:
336 _ip.magic('reset -f')
336 _ip.run_line_magic("reset", "-f")
337 337
338 338 def test_unicode(self):
339 339 """Check that files in odd encodings are accepted."""
340 340 mydir = os.path.dirname(__file__)
341 na = os.path.join(mydir, 'nonascii.py')
341 na = os.path.join(mydir, "nonascii.py")
342 342 _ip.magic('run "%s"' % na)
343 343 assert _ip.user_ns["u"] == "ΠŽΡ‚β„–Π€"
344 344
@@ -347,9 +347,9 b' tclass.py: deleting object: C-third'
347 347 src = "t = __file__\n"
348 348 self.mktmp(src)
349 349 _missing = object()
350 file1 = _ip.user_ns.get('__file__', _missing)
351 _ip.magic('run %s' % self.fname)
352 file2 = _ip.user_ns.get('__file__', _missing)
350 file1 = _ip.user_ns.get("__file__", _missing)
351 _ip.run_line_magic("run", self.fname)
352 file2 = _ip.user_ns.get("__file__", _missing)
353 353
354 354 # Check that __file__ was equal to the filename in the script's
355 355 # namespace.
@@ -363,9 +363,9 b' tclass.py: deleting object: C-third'
363 363 src = "t = __file__\n"
364 364 self.mktmp(src, ext='.ipy')
365 365 _missing = object()
366 file1 = _ip.user_ns.get('__file__', _missing)
367 _ip.magic('run %s' % self.fname)
368 file2 = _ip.user_ns.get('__file__', _missing)
366 file1 = _ip.user_ns.get("__file__", _missing)
367 _ip.run_line_magic("run", self.fname)
368 file2 = _ip.user_ns.get("__file__", _missing)
369 369
370 370 # Check that __file__ was equal to the filename in the script's
371 371 # namespace.
@@ -378,18 +378,18 b' tclass.py: deleting object: C-third'
378 378 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
379 379 src = "pass"
380 380 self.mktmp(src)
381 _ip.magic('run -t -N 1 %s' % self.fname)
382 _ip.magic('run -t -N 10 %s' % self.fname)
381 _ip.run_line_magic("run", "-t -N 1 %s" % self.fname)
382 _ip.run_line_magic("run", "-t -N 10 %s" % self.fname)
383 383
384 384 def test_ignore_sys_exit(self):
385 385 """Test the -e option to ignore sys.exit()"""
386 386 src = "import sys; sys.exit(1)"
387 387 self.mktmp(src)
388 with tt.AssertPrints('SystemExit'):
389 _ip.magic('run %s' % self.fname)
388 with tt.AssertPrints("SystemExit"):
389 _ip.run_line_magic("run", self.fname)
390 390
391 with tt.AssertNotPrints('SystemExit'):
392 _ip.magic('run -e %s' % self.fname)
391 with tt.AssertNotPrints("SystemExit"):
392 _ip.run_line_magic("run", "-e %s" % self.fname)
393 393
394 394 def test_run_nb(self):
395 395 """Test %run notebook.ipynb"""
@@ -404,7 +404,7 b' tclass.py: deleting object: C-third'
404 404 src = writes(nb, version=4)
405 405 self.mktmp(src, ext='.ipynb')
406 406
407 _ip.magic("run %s" % self.fname)
407 _ip.run_line_magic("run", self.fname)
408 408
409 409 assert _ip.user_ns["answer"] == 42
410 410
@@ -478,12 +478,16 b' class TestMagicRunWithPackage(unittest.TestCase):'
478 478 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
479 479 self.tempdir.cleanup()
480 480
481 def check_run_submodule(self, submodule, opts=''):
482 _ip.user_ns.pop('x', None)
483 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
484 self.assertEqual(_ip.user_ns['x'], self.value,
485 'Variable `x` is not loaded from module `{0}`.'
486 .format(submodule))
481 def check_run_submodule(self, submodule, opts=""):
482 _ip.user_ns.pop("x", None)
483 _ip.run_line_magic(
484 "run", "{2} -m {0}.{1}".format(self.package, submodule, opts)
485 )
486 self.assertEqual(
487 _ip.user_ns["x"],
488 self.value,
489 "Variable `x` is not loaded from module `{0}`.".format(submodule),
490 )
487 491
488 492 def test_run_submodule_with_absolute_import(self):
489 493 self.check_run_submodule('absolute')
@@ -533,17 +537,17 b' def test_run__name__():'
533 537 f.write("q = __name__")
534 538
535 539 _ip.user_ns.pop("q", None)
536 _ip.magic("run {}".format(path))
540 _ip.run_line_magic("run", "{}".format(path))
537 541 assert _ip.user_ns.pop("q") == "__main__"
538 542
539 _ip.magic("run -n {}".format(path))
543 _ip.run_line_magic("run", "-n {}".format(path))
540 544 assert _ip.user_ns.pop("q") == "foo"
541 545
542 546 try:
543 _ip.magic("run -i -n {}".format(path))
547 _ip.run_line_magic("run", "-i -n {}".format(path))
544 548 assert _ip.user_ns.pop("q") == "foo"
545 549 finally:
546 _ip.magic('reset -f')
550 _ip.run_line_magic("reset", "-f")
547 551
548 552
549 553 def test_run_tb():
@@ -563,7 +567,7 b' def test_run_tb():'
563 567 )
564 568 )
565 569 with capture_output() as io:
566 _ip.magic('run {}'.format(path))
570 _ip.run_line_magic("run", "{}".format(path))
567 571 out = io.stdout
568 572 assert "execfile" not in out
569 573 assert "RuntimeError" in out
@@ -610,6 +610,8 b' class VerboseTB(TBTools):'
610 610 traceback, to be used with alternate interpreters (because their own code
611 611 would appear in the traceback)."""
612 612
613 _tb_highlight = "bg:ansiyellow"
614
613 615 def __init__(
614 616 self,
615 617 color_scheme: str = "Linux",
@@ -642,10 +644,8 b' class VerboseTB(TBTools):'
642 644 self.long_header = long_header
643 645 self.include_vars = include_vars
644 646 # By default we use linecache.checkcache, but the user can provide a
645 # different check_cache implementation. This is used by the IPython
646 # kernel to provide tracebacks for interactive code that is cached,
647 # by a compiler instance that flushes the linecache but preserves its
648 # own code cache.
647 # different check_cache implementation. This was formerly used by the
648 # IPython kernel for interactive code, but is no longer necessary.
649 649 if check_cache is None:
650 650 check_cache = linecache.checkcache
651 651 self.check_cache = check_cache
@@ -836,7 +836,7 b' class VerboseTB(TBTools):'
836 836 before = context - after
837 837 if self.has_colors:
838 838 style = get_style_by_name("default")
839 style = stack_data.style_with_executing_node(style, "bg:ansiyellow")
839 style = stack_data.style_with_executing_node(style, self._tb_highlight)
840 840 formatter = Terminal256Formatter(style=style)
841 841 else:
842 842 formatter = None
@@ -107,6 +107,10 b' Some of the known remaining caveats are:'
107 107 before it is reloaded are not upgraded.
108 108
109 109 - C extension modules cannot be reloaded, and so cannot be autoreloaded.
110
111 - While comparing Enum and Flag, the 'is' Identity Operator is used (even in the case '==' has been used (Similar to the 'None' keyword)).
112
113 - Reloading a module, or importing the same module by a different name, creates new Enums. These may look the same, but are not.
110 114 """
111 115
112 116 from IPython.core.magic import Magics, magics_class, line_magic
@@ -1,11 +1,14 b''
1 1 # encoding: utf-8
2 2 from IPython.testing import decorators as dec
3 3
4
4 5 def test_import_backgroundjobs():
5 6 from IPython.lib import backgroundjobs
6 7
8
7 9 def test_import_deepreload():
8 10 from IPython.lib import deepreload
9 11
12
10 13 def test_import_demo():
11 14 from IPython.lib import demo
@@ -981,8 +981,9 b' class IPythonDirective(Directive):'
981 981 self.shell.warning_is_error = warning_is_error
982 982
983 983 # setup bookmark for saving figures directory
984 self.shell.process_input_line('bookmark ipy_savedir %s'%savefig_dir,
985 store_history=False)
984 self.shell.process_input_line(
985 'bookmark ipy_savedir "%s"' % savefig_dir, store_history=False
986 )
986 987 self.shell.clear_cout()
987 988
988 989 return rgxin, rgxout, promptin, promptout
@@ -405,14 +405,14 b' class TerminalInteractiveShell(InteractiveShell):'
405 405 @observe('term_title')
406 406 def init_term_title(self, change=None):
407 407 # Enable or disable the terminal title.
408 if self.term_title:
408 if self.term_title and _is_tty:
409 409 toggle_set_term_title(True)
410 410 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
411 411 else:
412 412 toggle_set_term_title(False)
413 413
414 414 def restore_term_title(self):
415 if self.term_title:
415 if self.term_title and _is_tty:
416 416 restore_term_title()
417 417
418 418 def init_display_formatter(self):
@@ -711,9 +711,8 b' class TerminalInteractiveShell(InteractiveShell):'
711 711
712 712 active_eventloop = None
713 713 def enable_gui(self, gui=None):
714 if gui and (gui != 'inline') :
715 self.active_eventloop, self._inputhook =\
716 get_inputhook_name_and_func(gui)
714 if gui and (gui not in {"inline", "webagg"}):
715 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
717 716 else:
718 717 self.active_eventloop = self._inputhook = None
719 718
@@ -318,6 +318,7 b' class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):'
318 318 self.shell.mainloop()
319 319 else:
320 320 self.log.debug("IPython not interactive...")
321 self.shell.restore_term_title()
321 322 if not self.shell.last_execution_succeeded:
322 323 sys.exit(1)
323 324
@@ -41,7 +41,7 b' class TerminalMagics(Magics):'
41 41 def __init__(self, shell):
42 42 super(TerminalMagics, self).__init__(shell)
43 43
44 def store_or_execute(self, block, name):
44 def store_or_execute(self, block, name, store_history=False):
45 45 """ Execute a block, or store it in a variable, per the user's request.
46 46 """
47 47 if name:
@@ -53,7 +53,7 b' class TerminalMagics(Magics):'
53 53 self.shell.user_ns['pasted_block'] = b
54 54 self.shell.using_paste_magics = True
55 55 try:
56 self.shell.run_cell(b, store_history=True)
56 self.shell.run_cell(b, store_history)
57 57 finally:
58 58 self.shell.using_paste_magics = False
59 59
@@ -147,7 +147,7 b' class TerminalMagics(Magics):'
147 147
148 148 sentinel = opts.get('s', u'--')
149 149 block = '\n'.join(get_pasted_lines(sentinel, quiet=quiet))
150 self.store_or_execute(block, name)
150 self.store_or_execute(block, name, store_history=False)
151 151
152 152 @line_magic
153 153 def paste(self, parameter_s=''):
@@ -203,7 +203,7 b' class TerminalMagics(Magics):'
203 203 sys.stdout.write("\n")
204 204 sys.stdout.write("## -- End pasted text --\n")
205 205
206 self.store_or_execute(block, name)
206 self.store_or_execute(block, name, store_history=True)
207 207
208 208 # Class-level: add a '%cls' magic only on Windows
209 209 if sys.platform == 'win32':
@@ -31,8 +31,7 b' from prompt_toolkit import __version__ as ptk_version'
31 31
32 32 from IPython.core.async_helpers import get_asyncio_loop
33 33
34 PTK3 = ptk_version.startswith('3.')
35
34 PTK3 = ptk_version.startswith("3.")
36 35
37 36
38 37 def inputhook(context):
@@ -41,6 +41,7 b' import gtk, gobject'
41 41 # Enable threading in GTK. (Otherwise, GTK will keep the GIL.)
42 42 gtk.gdk.threads_init()
43 43
44
44 45 def inputhook(context):
45 46 """
46 47 When the eventloop of prompt-toolkit is idle, call this inputhook.
@@ -50,6 +51,7 b' def inputhook(context):'
50 51
51 52 :param context: An `InputHookContext` instance.
52 53 """
54
53 55 def _main_quit(*a, **kw):
54 56 gtk.main_quit()
55 57 return False
@@ -3,10 +3,12 b''
3 3
4 4 from gi.repository import Gtk, GLib
5 5
6
6 7 def _main_quit(*args, **kwargs):
7 8 Gtk.main_quit()
8 9 return False
9 10
11
10 12 def inputhook(context):
11 13 GLib.io_add_watch(context.fileno(), GLib.PRIORITY_DEFAULT, GLib.IO_IN, _main_quit)
12 14 Gtk.main()
1 NO CONTENT: modified file
@@ -1,2 +1,2 b''
1 1 x = 1
2 print('x is:',x)
2 print("x is:", x)
@@ -1,2 +1,2 b''
1 1 # GENERATED BY setup.py
2 commit = u""
2 commit = ""
@@ -2,7 +2,7 b''
2 2 """Decorators that don't go anywhere else.
3 3
4 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 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 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 from typing import Sequence
20
21 from IPython.utils.docs import GENERATING_DOCUMENTATION
22
19 23
20 24 #-----------------------------------------------------------------------------
21 25 # Code
@@ -48,6 +52,7 b' def flag_calls(func):'
48 52 wrapper.__doc__ = func.__doc__
49 53 return wrapper
50 54
55
51 56 def undoc(func):
52 57 """Mark a function or class as undocumented.
53 58
@@ -56,3 +61,23 b' def undoc(func):'
56 61 """
57 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
@@ -62,15 +62,27 b' def _restore_term_title():'
62 62 pass
63 63
64 64
65 _xterm_term_title_saved = False
66
67
65 68 def _set_term_title_xterm(title):
66 69 """ Change virtual terminal title in xterm-workalikes """
70 global _xterm_term_title_saved
71 # Only save the title the first time we set, otherwise restore will only
72 # go back one title (probably undoing a %cd title change).
73 if not _xterm_term_title_saved:
67 74 # save the current title to the xterm "stack"
68 sys.stdout.write('\033[22;0t')
75 sys.stdout.write("\033[22;0t")
76 _xterm_term_title_saved = True
69 77 sys.stdout.write('\033]0;%s\007' % title)
70 78
71 79
72 80 def _restore_term_title_xterm():
81 # Make sure the restore has at least one accompanying set.
82 global _xterm_term_title_saved
83 assert _xterm_term_title_saved
73 84 sys.stdout.write('\033[23;0t')
85 _xterm_term_title_saved = False
74 86
75 87
76 88 if os.name == 'posix':
@@ -19,7 +19,6 b' def test_base():'
19 19
20 20
21 21 def test_SubClass():
22
23 22 class SubClass(Base):
24 23 y = 2
25 24
@@ -53,7 +52,7 b' def test_misbehaving_object_without_trait_names():'
53 52
54 53 class SillierWithDir(MisbehavingGetattr):
55 54 def __dir__(self):
56 return ['some_method']
55 return ["some_method"]
57 56
58 57 for bad_klass in (MisbehavingGetattr, SillierWithDir):
59 58 obj = bad_klass()
@@ -13,8 +13,8 b''
13 13 .. image:: https://raster.shields.io/badge/Follows-NEP29-brightgreen.png
14 14 :target: https://numpy.org/neps/nep-0029-deprecation_policy.html
15 15
16 .. image:: https://tidelift.com/subscription/pkg/pypi-ipython
17 :target: https://tidelift.com/badges/package/pypi/ipython?style=flat
16 .. image:: https://tidelift.com/badges/package/pypi/ipython?style=flat
17 :target: https://tidelift.com/subscription/pkg/pypi-ipython
18 18
19 19
20 20 ===========================================
@@ -41,6 +41,14 b' else:'
41 41 html_theme = "sphinx_rtd_theme"
42 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 52 # If your extensions are in another directory, add it here. If the directory
45 53 # is relative to the documentation root, use os.path.abspath to make it
46 54 # absolute, like shown here.
@@ -69,7 +69,7 b' shell:'
69 69
70 70 /home/bob >>> # it works
71 71
72 See ``IPython/example/utils/cwd_prompt.py`` for an example of how to write an
72 See ``IPython/example/utils/cwd_prompt.py`` for an example of how to write
73 73 extensions to customise prompts.
74 74
75 75 Inside IPython or in a startup script, you can use a custom prompts class
@@ -6,8 +6,7 b' IPython extensions'
6 6
7 7 A level above configuration are IPython extensions, Python modules which modify
8 8 the behaviour of the shell. They are referred to by an importable module name,
9 and can be placed anywhere you'd normally import from, or in
10 ``.ipython/extensions/``.
9 and can be placed anywhere you'd normally import from.
11 10
12 11 Getting extensions
13 12 ==================
@@ -71,10 +70,7 b' Useful :class:`InteractiveShell` methods include :meth:`~IPython.core.interactiv'
71 70 :ref:`defining_magics`
72 71
73 72 You can put your extension modules anywhere you want, as long as they can be
74 imported by Python's standard import mechanism. However, to make it easy to
75 write extensions, you can also put your extensions in :file:`extensions/`
76 within the :ref:`IPython directory <ipythondir>`. This directory is
77 added to :data:`sys.path` automatically.
73 imported by Python's standard import mechanism.
78 74
79 75 When your extension is ready for general use, please add it to the `extensions
80 76 index <https://github.com/ipython/ipython/wiki/Extensions-Index>`_. We also
@@ -2,6 +2,70 b''
2 2 8.x Series
3 3 ============
4 4
5 .. _version 8.6.0:
6
7 IPython 8.6.0
8 -------------
9
10 Back to a more regular release schedule (at least I try), as Friday is
11 already over by more than 24h hours. This is a slightly bigger release with a
12 few new features that contain no less then 25 PRs.
13
14 We'll notably found a couple of non negligible changes:
15
16 The ``install_ext`` and related functions have been removed after being
17 deprecated for years. You can use pip to install extensions. ``pip`` did not
18 exists when ``install_ext`` was introduced. You can still load local extensions
19 without installing them. Just set your ``sys.path`` for example. :ghpull:`13744`
20
21 IPython now have extra entry points that that the major *and minor* version of
22 python. For some of you this mean that you can do a quick ``ipython3.10`` to
23 launch IPython from the Python 3.10 interpreter, while still using Python 3.11
24 as your main Python. :ghpull:`13743`
25
26 The completer matcher API have been improved. See :ghpull:`13745`. This should
27 improve the type inference and improve dict keys completions in many use case.
28 Tanks ``@krassowski`` for all the works, and the D.E. Shaw group for sponsoring
29 it.
30
31 The color of error nodes in tracebacks can now be customized. See
32 :ghpull:`13756`. This is a private attribute until someone find the time to
33 properly add a configuration option. Note that with Python 3.11 that also show
34 the relevant nodes in traceback, it would be good to leverage this informations
35 (plus the "did you mean" info added on attribute errors). But that's likely work
36 I won't have time to do before long, so contributions welcome.
37
38 As we follow NEP 29, we removed support for numpy 1.19 :ghpull:`13760`.
39
40
41 The ``open()`` function present in the user namespace by default will now refuse
42 to open the file descriptors 0,1,2 (stdin, out, err), to avoid crashing IPython.
43 This mostly occurs in teaching context when incorrect values get passed around.
44
45
46 The ``?``, ``??``, and corresponding ``pinfo``, ``pinfo2`` magics can now find
47 objects insides arrays. That is to say, the following now works::
48
49
50 >>> def my_func(*arg, **kwargs):pass
51 >>> container = [my_func]
52 >>> container[0]?
53
54
55 If ``container`` define a custom ``getitem``, this __will__ trigger the custom
56 method. So don't put side effects in your ``getitems``. Thanks the D.E. Shaw
57 group for the request and sponsoring the work.
58
59
60 As usual you can find the full list of PRs on GitHub under `the 8.6 milestone
61 <https://github.com/ipython/ipython/pulls?q=milestone%3A8.6>`__.
62
63 Thanks to all hacktoberfest contributors, please contribute to
64 `closember.org <https://closember.org/>`__.
65
66 Thanks to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
67 work on IPython and related libraries.
68
5 69 .. _version 8.5.0:
6 70
7 71 IPython 8.5.0
@@ -24,14 +24,9 b' import inspect'
24 24 import os
25 25 import re
26 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 30 class FuncClsScanner(ast.NodeVisitor):
36 31 """Scan a module for top-level functions and classes.
37 32
@@ -62,11 +57,15 b' class FuncClsScanner(ast.NodeVisitor):'
62 57 self.functions.append(node.name)
63 58
64 59 def visit_ClassDef(self, node):
65 if not (node.name.startswith('_') or self.has_undoc_decorator(node)) \
66 and node.name not in self.classes_seen:
67 cls = Obj(name=node.name)
68 cls.has_init = any(isinstance(n, ast.FunctionDef) and \
69 n.name=='__init__' for n in node.body)
60 if (
61 not (node.name.startswith("_") or self.has_undoc_decorator(node))
62 and node.name not in self.classes_seen
63 ):
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 69 self.classes.append(cls)
71 70 self.classes_seen.add(node.name)
72 71
@@ -221,7 +220,11 b' class ApiDocWriter(object):'
221 220 funcs, classes = [], []
222 221 for name, obj in ns.items():
223 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 228 classes.append(cls)
226 229 elif inspect.isfunction(obj):
227 230 funcs.append(name)
@@ -279,10 +282,18 b' class ApiDocWriter(object):'
279 282 self.rst_section_levels[2] * len(subhead) + '\n'
280 283
281 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 287 # must NOT exclude from index to keep cross-refs working
284 ad += ' :members:\n' \
285 ' :show-inheritance:\n'
288 ad += " :members:\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 297 if c.has_init:
287 298 ad += '\n .. automethod:: __init__\n'
288 299
@@ -54,6 +54,7 b' doc ='
54 54 matplotlib
55 55 stack_data
56 56 pytest<7
57 typing_extensions
57 58 %(test)s
58 59 kernel =
59 60 ipykernel
@@ -78,7 +79,7 b' test_extra ='
78 79 curio
79 80 matplotlib!=3.2.0
80 81 nbformat
81 numpy>=1.19
82 numpy>=1.20
82 83 pandas
83 84 trio
84 85 all =
@@ -211,14 +211,20 b' def find_entry_points():'
211 211 use, our own build_scripts_entrypt class below parses these and builds
212 212 command line scripts.
213 213
214 Each of our entry points gets both a plain name, e.g. ipython, and one
215 suffixed with the Python major version number, e.g. ipython3.
214 Each of our entry points gets a plain name, e.g. ipython, a name
215 suffixed with the Python major version number, e.g. ipython3, and
216 a name suffixed with the Python major.minor version number, eg. ipython3.8.
216 217 """
217 218 ep = [
218 219 'ipython%s = IPython:start_ipython',
219 220 ]
220 suffix = str(sys.version_info[0])
221 return [e % '' for e in ep] + [e % suffix for e in ep]
221 major_suffix = str(sys.version_info[0])
222 minor_suffix = ".".join([str(sys.version_info[0]), str(sys.version_info[1])])
223 return (
224 [e % "" for e in ep]
225 + [e % major_suffix for e in ep]
226 + [e % minor_suffix for e in ep]
227 )
222 228
223 229 class install_lib_symlink(Command):
224 230 user_options = [
@@ -340,7 +346,7 b' def git_prebuild(pkg_dir, build_cmd=build_py):'
340 346 out_file.writelines(
341 347 [
342 348 "# GENERATED BY setup.py\n",
343 'commit = u"%s"\n' % repo_commit,
349 'commit = "%s"\n' % repo_commit,
344 350 ]
345 351 )
346 352
General Comments 0
You need to be logged in to leave comments. Login now