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==2 |
|
|
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 |
|
|
|
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, (973 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,7 +250,26 b' try:' | |||
|
153 | 250 | JEDI_INSTALLED = True |
|
154 | 251 | except ImportError: |
|
155 | 252 | JEDI_INSTALLED = False |
|
156 | #----------------------------------------------------------------------------- | |
|
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 | ||
|
272 | # ----------------------------------------------------------------------------- | |
|
157 | 273 | # Globals |
|
158 | 274 | #----------------------------------------------------------------------------- |
|
159 | 275 | |
@@ -166,7 +282,7 b' except ImportError:' | |||
|
166 | 282 | _UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)] |
|
167 | 283 | |
|
168 | 284 | # Public API |
|
169 |
__all__ = [ |
|
|
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,7 +1227,20 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 | |
|
927 | def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]: | |
|
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 | ||
|
1243 | def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]: | |
|
928 | 1244 | """Match Unicode characters back to Unicode name |
|
929 | 1245 | |
|
930 | 1246 | This does ``β`` -> ``\\snowman`` |
@@ -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 | |
|
966 | def back_latex_name_matches(text:str) -> Tuple[str, Sequence[str]] : | |
|
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 | ||
|
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( |
|
|
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( |
|
|
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_matche |
|
|
1181 |
self.magic_color_matche |
|
|
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[ |
|
|
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_matche |
|
|
1599 | return [self.dict_key_matcher] | |
|
1198 | 1600 | |
|
1199 | 1601 | if self.use_jedi: |
|
1200 | 1602 | return [ |
|
1201 | 1603 | *self.custom_matchers, |
|
1202 |
self. |
|
|
1203 |
self. |
|
|
1204 |
self. |
|
|
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. |
|
|
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_matche |
|
|
1212 |
self. |
|
|
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,7 +1641,15 b' class IPCompleter(Completer):' | |||
|
1231 | 1641 | return [f.replace("\\","/") |
|
1232 | 1642 | for f in self.glob("%s*" % text)] |
|
1233 | 1643 | |
|
1234 | def file_matches(self, text:str)->List[str]: | |
|
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 | ||
|
1652 | def file_matches(self, text: str) -> List[str]: | |
|
1235 | 1653 | """Match filenames, expanding ~USER type strings. |
|
1236 | 1654 | |
|
1237 | 1655 | Most of the seemingly convoluted logic in this completer is an |
@@ -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 | |
|
1316 | def magic_matches(self, text:str): | |
|
1317 | """Match magics""" | |
|
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 | ||
|
1748 | def magic_matches(self, text: str): | |
|
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 | |
|
1358 | def magic_config_matches(self, text:str) -> List[str]: | |
|
1359 | """ Match class names and attributes for %config magic """ | |
|
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 | ||
|
1801 | def magic_config_matches(self, text: str) -> List[str]: | |
|
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 | |
|
1393 | def magic_color_matches(self, text:str) -> List[str] : | |
|
1394 | """ Match color schemes for %colors magic""" | |
|
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 | ||
|
1847 | def magic_color_matches(self, text: str) -> List[str]: | |
|
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.Completion |
|
|
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 | |
|
1657 | def dict_key_matches(self, text:str) -> List[str]: | |
|
1658 | "Match string keys in a dictionary, after e.g. 'foo[' " | |
|
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 | ||
|
2151 | def dict_key_matches(self, text: str) -> List[str]: | |
|
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,8 +2255,16 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 |
def unicode_name_matches(text:str) -> Tuple[str, List[str]] |
|
|
2267 | def unicode_name_matches(text: str) -> Tuple[str, List[str]]: | |
|
1763 | 2268 | """Match Latex-like syntax for unicode characters base |
|
1764 | 2269 | on the name of the character. |
|
1765 | 2270 | |
@@ -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. | |
|
1783 | 2291 |
|
|
1784 | def latex_matches(self, text:str) -> Tuple[str, Sequence[str]]: | |
|
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 | ) | |
|
2298 | ||
|
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( |
|
|
1992 |
|
|
|
1993 | 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) | |
|
2540 | yield Completion( | |
|
2541 | start=offset - delta, | |
|
2542 | end=offset, | |
|
2543 | text=jm.name_with_symbols, | |
|
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 |
|
|
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 | ) | |
|
2006 | 2563 | |
|
2007 | # I'm unsure if this is always true, so let's assert and see if it | |
|
2008 | # crash | |
|
2009 | 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>') | |
|
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 | |
|
2572 | ||
|
2573 | # I'm unsure if this is always true, so let's assert and see if it | |
|
2574 | # crash | |
|
2575 | assert before.endswith(matched_text) | |
|
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) | |
|
2012 | 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: |
@@ -2104,98 +2727,156 b' class IPCompleter(Completer):' | |||
|
2104 | 2727 | # if text is either None or an empty string, rely on the line buffer |
|
2105 | 2728 | if (not line_buffer) and full_text: |
|
2106 | 2729 | line_buffer = full_text.split('\n')[cursor_line] |
|
2107 | 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 | ||
|
2730 | if not text: # issue #11508: check line_buffer before calling split_line | |
|
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 |
|
|
|
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) | |
|
2153 | ||
|
2154 | if self.merge_completions: | |
|
2155 | matches = [] | |
|
2156 | for matcher in self.matchers: | |
|
2157 | try: | |
|
2158 | matches.extend([(m, matcher.__qualname__) | |
|
2159 | for m in matcher(text)]) | |
|
2160 | except: | |
|
2161 | # Show the ugly traceback if the matcher causes an | |
|
2162 | # exception, but do NOT crash the kernel! | |
|
2163 | 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 | |
|
2170 | ||
|
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) | |
|
2757 | jedi_matcher_id = _get_matcher_id(self._jedi_matcher) | |
|
2178 | 2758 | |
|
2179 | _filtered_matches = sorted(filtered_matches, key=lambda x: completions_sorting_key(x[0])) | |
|
2759 | suppressed_matchers = set() | |
|
2180 | 2760 | |
|
2181 | custom_res = [(m, 'custom') for m in self.dispatch_custom_completer(text) or []] | |
|
2182 | ||
|
2183 | _filtered_matches = custom_res or _filtered_matches | |
|
2184 | ||
|
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] | |
|
2761 | matchers = { | |
|
2762 | _get_matcher_id(matcher): matcher | |
|
2763 | for matcher in sorted( | |
|
2764 | self.matchers, key=_get_matcher_priority, reverse=True | |
|
2765 | ) | |
|
2766 | } | |
|
2188 | 2767 | |
|
2189 | self.matches = _matches | |
|
2768 | for matcher_id, matcher in matchers.items(): | |
|
2769 | api_version = _get_matcher_api_version(matcher) | |
|
2770 | matcher_id = _get_matcher_id(matcher) | |
|
2190 | 2771 | |
|
2191 | return _CompleteResult(text, _matches, origins, completions) | |
|
2192 | ||
|
2193 | def fwd_unicode_match(self, text:str) -> Tuple[str, Sequence[str]]: | |
|
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 | |
|
2780 | ||
|
2781 | try: | |
|
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}") | |
|
2790 | except: | |
|
2791 | # Show the ugly traceback if the matcher causes an | |
|
2792 | # exception, but do NOT crash the kernel! | |
|
2793 | sys.excepthook(*sys.exc_info()) | |
|
2794 | continue | |
|
2795 | ||
|
2796 | # set default value for matched fragment if suffix was not selected. | |
|
2797 | result["matched_fragment"] = result.get("matched_fragment", context.token) | |
|
2798 | ||
|
2799 | if not suppressed_matchers: | |
|
2800 | suppression_recommended = result.get("suppress", False) | |
|
2801 | ||
|
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"]) | |
|
2811 | ||
|
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 | ) | |
|
2835 | ||
|
2836 | # populate legacy stateful API | |
|
2837 | self.matches = matches | |
|
2838 | ||
|
2839 | return results | |
|
2840 | ||
|
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 | ) | |
|
2870 | ||
|
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 |
|
|
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.re |
|
|
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 | 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))) | |
|
91 | mod = import_module(module_str) | |
|
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} | |
|
433 | (?!\d)[\w*]+ # Variable name | |
|
434 | (\.(?!\d)[\w*]+)* # .etc.etc | |
|
435 | ) | |
|
436 | (\?\??)$ # ? or ?? | |
|
437 | """, | |
|
438 | re.VERBOSE) | |
|
432 | ||
|
433 | _help_end_re = re.compile( | |
|
434 | r"""(%{0,2} | |
|
435 | (?!\d)[\w*]+ # Variable name | |
|
436 | (\.(?!\d)[\w*]+|\[-?[0-9]+\])* # .etc.etc or [0], we only support literal integers. | |
|
437 | ) | |
|
438 | (\?\??)$ # ? or ?? | |
|
439 | """, | |
|
440 | re.VERBOSE, | |
|
441 | ) | |
|
442 | ||
|
439 | 443 | |
|
440 | 444 | class HelpEnd(TokenTransformBase): |
|
441 | 445 | """Transformer for help syntax: obj? and obj??""" |
@@ -464,10 +468,11 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]) | |
|
468 | indent, content = piece[:self.start_col], piece[self.start_col:] | |
|
469 | lines_before = lines[:self.start_line] | |
|
470 |
lines_ |
|
|
471 | ||
|
472 | piece = "".join(lines[self.start_line : self.q_line + 1]) | |
|
473 | indent, content = piece[: self.start_col], piece[self.start_col :] | |
|
474 | lines_before = lines[: self.start_line] | |
|
475 | lines_after = lines[self.q_line + 1 :] | |
|
471 | 476 | |
|
472 | 477 | m = _help_end_re.search(content) |
|
473 | 478 | if not m: |
@@ -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 |
|
|
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,14 +226,17 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>' % ( | |
|
217 | name, | |
|
218 |
|
|
|
219 |
|
|
|
220 |
self |
|
|
221 |
|
|
|
222 |
self.s |
|
|
223 |
self. |
|
|
229 | return ( | |
|
230 | '<%s object at %x, raw_cell="%s" store_history=%s silent=%s shell_futures=%s cell_id=%s>' | |
|
231 | % ( | |
|
232 | name, | |
|
233 | id(self), | |
|
234 | raw_cell, | |
|
235 | self.store_history, | |
|
236 | self.silent, | |
|
237 | self.shell_futures, | |
|
238 | self.cell_id, | |
|
239 | ) | |
|
224 | 240 | ) |
|
225 | 241 | |
|
226 | 242 | |
@@ -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 = |
|
|
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,7 +1629,10 b' class InteractiveShell(SingletonConfigurable):' | |||
|
1579 | 1629 | if idx == len(oname_rest) - 1: |
|
1580 | 1630 | obj = self._getattr_property(obj, part) |
|
1581 | 1631 | else: |
|
1582 |
|
|
|
1632 | if is_integer_string(part): | |
|
1633 | obj = obj[int(part)] | |
|
1634 | else: | |
|
1635 | obj = getattr(obj, part) | |
|
1583 | 1636 | except: |
|
1584 | 1637 | # Blanket except b/c some badly implemented objects |
|
1585 | 1638 | # allow __getattr__ to raise exceptions other than |
@@ -1643,7 +1696,10 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. |
|
1646 |
|
|
|
1699 | if is_integer_string(attrname): | |
|
1700 | return obj[int(attrname)] | |
|
1701 | else: | |
|
1702 | attr = getattr(type(obj), attrname) | |
|
1647 | 1703 | except AttributeError: |
|
1648 | 1704 | pass |
|
1649 | 1705 | else: |
@@ -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 un |
|
|
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 = |
|
|
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 = |
|
|
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, |
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
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,46 +166,93 b' def doctest_tb_sysexit():' | |||
|
170 | 166 | """ |
|
171 | 167 | |
|
172 | 168 | |
|
173 | def doctest_tb_sysexit_verbose(): | |
|
174 | """ | |
|
175 | In [18]: %run simpleerr.py exit | |
|
176 | An exception has occurred, use %tb to see the full traceback. | |
|
177 | SystemExit: (1, 'Mode = exit') | |
|
178 | ||
|
179 | In [19]: %run simpleerr.py exit 2 | |
|
180 | An exception has occurred, use %tb to see the full traceback. | |
|
181 | SystemExit: (2, 'Mode = exit') | |
|
182 | ||
|
183 | In [23]: %xmode verbose | |
|
184 | Exception reporting mode: Verbose | |
|
185 | ||
|
186 | In [24]: %tb | |
|
187 | --------------------------------------------------------------------------- | |
|
188 | SystemExit Traceback (most recent call last) | |
|
189 | <BLANKLINE> | |
|
190 | ... | |
|
191 | 30 except IndexError: | |
|
192 | 31 mode = 'div' | |
|
193 | ---> 33 bar(mode) | |
|
194 | mode = 'exit' | |
|
195 | <BLANKLINE> | |
|
196 | ... in bar(mode='exit') | |
|
197 | ... except: | |
|
198 | ... stat = 1 | |
|
199 | ---> ... sysexit(stat, mode) | |
|
200 |
|
|
|
201 |
stat = |
|
|
202 | ... else: | |
|
203 | ... raise ValueError('Unknown mode') | |
|
204 | <BLANKLINE> | |
|
205 | ... in sysexit(stat=2, mode='exit') | |
|
206 | 10 def sysexit(stat, mode): | |
|
207 | ---> 11 raise SystemExit(stat, f"Mode = {mode}") | |
|
208 | stat = 2 | |
|
209 | <BLANKLINE> | |
|
210 | SystemExit: (2, 'Mode = exit') | |
|
211 | """ | |
|
212 | ||
|
169 | if sys.version_info >= (3, 9): | |
|
170 | if SV_VERSION < (0, 6): | |
|
171 | ||
|
172 | def doctest_tb_sysexit_verbose_stack_data_05(): | |
|
173 | """ | |
|
174 | In [18]: %run simpleerr.py exit | |
|
175 | An exception has occurred, use %tb to see the full traceback. | |
|
176 | SystemExit: (1, 'Mode = exit') | |
|
177 | ||
|
178 | In [19]: %run simpleerr.py exit 2 | |
|
179 | An exception has occurred, use %tb to see the full traceback. | |
|
180 | SystemExit: (2, 'Mode = exit') | |
|
181 | ||
|
182 | In [23]: %xmode verbose | |
|
183 | Exception reporting mode: Verbose | |
|
184 | ||
|
185 | In [24]: %tb | |
|
186 | --------------------------------------------------------------------------- | |
|
187 | SystemExit Traceback (most recent call last) | |
|
188 | <BLANKLINE> | |
|
189 | ... | |
|
190 | 30 except IndexError: | |
|
191 | 31 mode = 'div' | |
|
192 | ---> 33 bar(mode) | |
|
193 | mode = 'exit' | |
|
194 | <BLANKLINE> | |
|
195 | ... in bar(mode='exit') | |
|
196 | ... except: | |
|
197 | ... stat = 1 | |
|
198 | ---> ... sysexit(stat, mode) | |
|
199 | mode = 'exit' | |
|
200 | stat = 2 | |
|
201 | ... else: | |
|
202 | ... raise ValueError('Unknown mode') | |
|
203 | <BLANKLINE> | |
|
204 | ... in sysexit(stat=2, mode='exit') | |
|
205 | 10 def sysexit(stat, mode): | |
|
206 | ---> 11 raise SystemExit(stat, f"Mode = {mode}") | |
|
207 | stat = 2 | |
|
208 | <BLANKLINE> | |
|
209 | SystemExit: (2, 'Mode = exit') | |
|
210 | """ | |
|
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( |
|
|
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( |
|
|
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( |
|
|
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( |
|
|
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( |
|
|
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(" |
|
|
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 |
|
|
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( |
|
|
476 |
_ip.magic( |
|
|
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(' |
|
|
515 | _ip.run_line_magic("cd", '"%s"' % ipdir) | |
|
513 | 516 | assert curpath() == ipdir |
|
514 |
_ip.magic( |
|
|
517 | _ip.run_line_magic("cd", "-") | |
|
515 | 518 | assert curpath() == startdir |
|
516 |
_ip.magic(' |
|
|
519 | _ip.run_line_magic("pushd", '"%s"' % ipdir) | |
|
517 | 520 | assert curpath() == ipdir |
|
518 |
_ip.magic( |
|
|
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(' |
|
|
659 |
_ip.magic(' |
|
|
660 |
_ip.magic(' |
|
|
661 |
_ip.magic(' |
|
|
662 |
_ip.magic(' |
|
|
663 |
_ip.magic(' |
|
|
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[ |
|
|
789 |
self.assertEqual(_ip.magic("env var"), |
|
|
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(' |
|
|
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( |
|
|
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[ |
|
|
1362 |
_ip.user_ns[ |
|
|
1363 |
_ip.magic(' |
|
|
1364 |
assert _ip.user_ns[ |
|
|
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 = 4 |
|
|
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( |
|
|
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( |
|
|
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([ |
|
|
220 |
_ip.magic( |
|
|
221 |
with tt.fake_input([ |
|
|
222 |
_ip.magic( |
|
|
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([ |
|
|
228 |
_ip.magic( |
|
|
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([ |
|
|
231 |
with tt.AssertNotPrints( |
|
|
232 |
_ip.magic( |
|
|
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 |
|
|
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 |
|
|
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 |
|
|
292 |
_ip.magic("run |
|
|
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( |
|
|
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( |
|
|
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, |
|
|
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( |
|
|
351 |
_ip.magic( |
|
|
352 |
file2 = _ip.user_ns.get( |
|
|
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( |
|
|
367 |
_ip.magic( |
|
|
368 |
file2 = _ip.user_ns.get( |
|
|
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( |
|
|
382 |
_ip.magic( |
|
|
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( |
|
|
389 |
_ip.magic( |
|
|
388 | with tt.AssertPrints("SystemExit"): | |
|
389 | _ip.run_line_magic("run", self.fname) | |
|
390 | 390 | |
|
391 |
with tt.AssertNotPrints( |
|
|
392 |
_ip.magic( |
|
|
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 |
|
|
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( |
|
|
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( |
|
|
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( |
|
|
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 |
|
|
646 |
# |
|
|
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, |
|
|
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( |
|
|
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 |
|
|
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 |
|
|
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( |
|
|
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() |
@@ -782,7 +782,7 b' def _init_checker_class() -> Type["IPDoctestOutputChecker"]:' | |||
|
782 | 782 | precision = 0 if fraction is None else len(fraction) |
|
783 | 783 | if exponent is not None: |
|
784 | 784 | precision -= int(exponent) |
|
785 |
if float(w.group()) == approx(float(g.group()), abs=10 |
|
|
785 | if float(w.group()) == approx(float(g.group()), abs=10**-precision): | |
|
786 | 786 | # They're close enough. Replace the text we actually |
|
787 | 787 | # got with the text we want, so that it will match when we |
|
788 | 788 | # check the string literally. |
@@ -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 """ |
|
67 | # save the current title to the xterm "stack" | |
|
68 | sys.stdout.write('\033[22;0t') | |
|
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: | |
|
74 | # save the current title to the xterm "stack" | |
|
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 [ |
|
|
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/ |
|
|
17 |
:target: https://tidelift.com/ |
|
|
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 |
|
|
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 |
|
|
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. |
|
|
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 | |
@@ -42,7 +37,7 b' class FuncClsScanner(ast.NodeVisitor):' | |||
|
42 | 37 | self.classes = [] |
|
43 | 38 | self.classes_seen = set() |
|
44 | 39 | self.functions = [] |
|
45 | ||
|
40 | ||
|
46 | 41 | @staticmethod |
|
47 | 42 | def has_undoc_decorator(node): |
|
48 | 43 | return any(isinstance(d, ast.Name) and d.id == 'undoc' \ |
@@ -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 += |
|
|
285 |
|
|
|
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. |
|
|
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 |
|
|
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 = |
|
|
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