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 | - name: Install dependencies |
|
28 | - name: Install dependencies | |
29 | run: | |
|
29 | run: | | |
30 | python -m pip install --upgrade pip |
|
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 | - name: Lint with darker |
|
32 | - name: Lint with darker | |
33 | run: | |
|
33 | run: | | |
34 | darker -r 60625f241f298b5039cb2debc365db38aa7bb522 --check --diff . || ( |
|
34 | darker -r 60625f241f298b5039cb2debc365db38aa7bb522 --check --diff . || ( |
@@ -24,7 +24,6 b' __pycache__' | |||||
24 | .cache |
|
24 | .cache | |
25 | .coverage |
|
25 | .coverage | |
26 | *.swp |
|
26 | *.swp | |
27 | .vscode |
|
|||
28 | .pytest_cache |
|
27 | .pytest_cache | |
29 | .python-version |
|
28 | .python-version | |
30 | venv*/ |
|
29 | venv*/ |
@@ -73,25 +73,6 b' class CachingCompiler(codeop.Compile):' | |||||
73 | def __init__(self): |
|
73 | def __init__(self): | |
74 | codeop.Compile.__init__(self) |
|
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 | # Caching a dictionary { filename: execution_count } for nicely |
|
76 | # Caching a dictionary { filename: execution_count } for nicely | |
96 | # rendered tracebacks. The filename corresponds to the filename |
|
77 | # rendered tracebacks. The filename corresponds to the filename | |
97 | # argument used for the builtins.compile function. |
|
78 | # argument used for the builtins.compile function. | |
@@ -161,14 +142,24 b' class CachingCompiler(codeop.Compile):' | |||||
161 | # Save the execution count |
|
142 | # Save the execution count | |
162 | self._filename_map[name] = number |
|
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 | entry = ( |
|
156 | entry = ( | |
165 | len(transformed_code), |
|
157 | len(transformed_code), | |
166 |
|
|
158 | None, | |
167 | [line + "\n" for line in transformed_code.splitlines()], |
|
159 | [line + "\n" for line in transformed_code.splitlines()], | |
168 | name, |
|
160 | name, | |
169 | ) |
|
161 | ) | |
170 | linecache.cache[name] = entry |
|
162 | linecache.cache[name] = entry | |
171 | linecache._ipython_cache[name] = entry |
|
|||
172 | return name |
|
163 | return name | |
173 |
|
164 | |||
174 | @contextmanager |
|
165 | @contextmanager | |
@@ -187,10 +178,22 b' class CachingCompiler(codeop.Compile):' | |||||
187 |
|
178 | |||
188 |
|
179 | |||
189 | def check_linecache_ipython(*args): |
|
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 |
|
192 | import warnings | |
193 | linecache._checkcache_ori(*args) |
|
193 | ||
194 | # Then, update back the cache with our data, so that tracebacks related |
|
194 | warnings.warn( | |
195 | # to our compiled codes can be produced. |
|
195 | "Deprecated Since IPython 8.6, Just call linecache.checkcache() directly.", | |
196 | linecache.cache.update(linecache._ipython_cache) |
|
196 | DeprecationWarning, | |
|
197 | stacklevel=2, | |||
|
198 | ) | |||
|
199 | linecache.checkcache() |
This diff has been collapsed as it changes many lines, (919 lines changed) Show them Hide them | |||||
@@ -100,6 +100,73 b' option.' | |||||
100 |
|
100 | |||
101 | Be sure to update :any:`jedi` to the latest stable version or to try the |
|
101 | Be sure to update :any:`jedi` to the latest stable version or to try the | |
102 | current development version to get better completions. |
|
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 | # Some of this code originated from rlcompleter in the Python standard library |
|
176 | # Some of this code originated from rlcompleter in the Python standard library | |
110 | # Copyright (C) 2001 Python Software Foundation, www.python.org |
|
177 | # Copyright (C) 2001 Python Software Foundation, www.python.org | |
111 |
|
178 | |||
112 |
|
179 | from __future__ import annotations | ||
113 | import builtins as builtin_mod |
|
180 | import builtins as builtin_mod | |
114 | import glob |
|
181 | import glob | |
115 | import inspect |
|
182 | import inspect | |
@@ -124,9 +191,26 b' import unicodedata' | |||||
124 | import uuid |
|
191 | import uuid | |
125 | import warnings |
|
192 | import warnings | |
126 | from contextlib import contextmanager |
|
193 | from contextlib import contextmanager | |
|
194 | from dataclasses import dataclass | |||
|
195 | from functools import cached_property, partial | |||
127 | from importlib import import_module |
|
196 | from importlib import import_module | |
128 | from types import SimpleNamespace |
|
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 | from IPython.core.error import TryNext |
|
215 | from IPython.core.error import TryNext | |
132 | from IPython.core.inputtransformer2 import ESC_MAGIC |
|
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 | from IPython.core.oinspect import InspectColors |
|
218 | from IPython.core.oinspect import InspectColors | |
135 | from IPython.testing.skipdoctest import skip_doctest |
|
219 | from IPython.testing.skipdoctest import skip_doctest | |
136 | from IPython.utils import generics |
|
220 | from IPython.utils import generics | |
|
221 | from IPython.utils.decorators import sphinx_options | |||
137 | from IPython.utils.dir2 import dir2, get_real_method |
|
222 | from IPython.utils.dir2 import dir2, get_real_method | |
|
223 | from IPython.utils.docs import GENERATING_DOCUMENTATION | |||
138 | from IPython.utils.path import ensure_dir_exists |
|
224 | from IPython.utils.path import ensure_dir_exists | |
139 | from IPython.utils.process import arg_split |
|
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 | from traitlets.config.configurable import Configurable |
|
237 | from traitlets.config.configurable import Configurable | |
142 |
|
238 | |||
143 | import __main__ |
|
239 | import __main__ | |
@@ -145,6 +241,7 b' import __main__' | |||||
145 | # skip module docstests |
|
241 | # skip module docstests | |
146 | __skip_doctest__ = True |
|
242 | __skip_doctest__ = True | |
147 |
|
243 | |||
|
244 | ||||
148 | try: |
|
245 | try: | |
149 | import jedi |
|
246 | import jedi | |
150 | jedi.settings.case_insensitive_completion = False |
|
247 | jedi.settings.case_insensitive_completion = False | |
@@ -153,6 +250,25 b' try:' | |||||
153 | JEDI_INSTALLED = True |
|
250 | JEDI_INSTALLED = True | |
154 | except ImportError: |
|
251 | except ImportError: | |
155 | JEDI_INSTALLED = False |
|
252 | JEDI_INSTALLED = False | |
|
253 | ||||
|
254 | ||||
|
255 | if TYPE_CHECKING or GENERATING_DOCUMENTATION: | |||
|
256 | from typing import cast | |||
|
257 | from typing_extensions import TypedDict, NotRequired, Protocol, TypeAlias | |||
|
258 | else: | |||
|
259 | ||||
|
260 | def cast(obj, type_): | |||
|
261 | """Workaround for `TypeError: MatcherAPIv2() takes no arguments`""" | |||
|
262 | return obj | |||
|
263 | ||||
|
264 | # do not require on runtime | |||
|
265 | NotRequired = Tuple # requires Python >=3.11 | |||
|
266 | TypedDict = Dict # by extension of `NotRequired` requires 3.11 too | |||
|
267 | Protocol = object # requires Python >=3.8 | |||
|
268 | TypeAlias = Any # requires Python >=3.10 | |||
|
269 | if GENERATING_DOCUMENTATION: | |||
|
270 | from typing import TypedDict | |||
|
271 | ||||
156 | #----------------------------------------------------------------------------- |
|
272 | # ----------------------------------------------------------------------------- | |
157 | # Globals |
|
273 | # Globals | |
158 | #----------------------------------------------------------------------------- |
|
274 | #----------------------------------------------------------------------------- | |
@@ -166,7 +282,7 b' except ImportError:' | |||||
166 | _UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)] |
|
282 | _UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)] | |
167 |
|
283 | |||
168 | # Public API |
|
284 | # Public API | |
169 |
__all__ = [ |
|
285 | __all__ = ["Completer", "IPCompleter"] | |
170 |
|
286 | |||
171 | if sys.platform == 'win32': |
|
287 | if sys.platform == 'win32': | |
172 | PROTECTABLES = ' ' |
|
288 | PROTECTABLES = ' ' | |
@@ -177,6 +293,8 b' else:' | |||||
177 | # may have trouble processing. |
|
293 | # may have trouble processing. | |
178 | MATCHES_LIMIT = 500 |
|
294 | MATCHES_LIMIT = 500 | |
179 |
|
295 | |||
|
296 | # Completion type reported when no type can be inferred. | |||
|
297 | _UNKNOWN_TYPE = "<unknown>" | |||
180 |
|
298 | |||
181 | class ProvisionalCompleterWarning(FutureWarning): |
|
299 | class ProvisionalCompleterWarning(FutureWarning): | |
182 | """ |
|
300 | """ | |
@@ -355,9 +473,12 b' class _FakeJediCompletion:' | |||||
355 | return '<Fake completion object jedi has crashed>' |
|
473 | return '<Fake completion object jedi has crashed>' | |
356 |
|
474 | |||
357 |
|
475 | |||
|
476 | _JediCompletionLike = Union[jedi.api.Completion, _FakeJediCompletion] | |||
|
477 | ||||
|
478 | ||||
358 | class Completion: |
|
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 | .. warning:: |
|
483 | .. warning:: | |
363 |
|
484 | |||
@@ -417,6 +538,188 b' class Completion:' | |||||
417 | return hash((self.start, self.end, self.text)) |
|
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 | _IC = Iterable[Completion] |
|
723 | _IC = Iterable[Completion] | |
421 |
|
724 | |||
422 |
|
725 | |||
@@ -924,6 +1227,19 b' def _safe_isinstance(obj, module, class_name):' | |||||
924 | return (module in sys.modules and |
|
1227 | return (module in sys.modules and | |
925 | isinstance(obj, getattr(import_module(module), class_name))) |
|
1228 | isinstance(obj, getattr(import_module(module), class_name))) | |
926 |
|
1229 | |||
|
1230 | ||||
|
1231 | @context_matcher() | |||
|
1232 | def back_unicode_name_matcher(context: CompletionContext): | |||
|
1233 | """Match Unicode characters back to Unicode name | |||
|
1234 | ||||
|
1235 | Same as :any:`back_unicode_name_matches`, but adopted to new Matcher API. | |||
|
1236 | """ | |||
|
1237 | fragment, matches = back_unicode_name_matches(context.text_until_cursor) | |||
|
1238 | return _convert_matcher_v1_result_to_v2( | |||
|
1239 | matches, type="unicode", fragment=fragment, suppress_if_matches=True | |||
|
1240 | ) | |||
|
1241 | ||||
|
1242 | ||||
927 | def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]: |
|
1243 | def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]: | |
928 | """Match Unicode characters back to Unicode name |
|
1244 | """Match Unicode characters back to Unicode name | |
929 |
|
1245 | |||
@@ -934,6 +1250,9 b' def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]:' | |||||
934 |
|
1250 | |||
935 | This will not either back-complete standard sequences like \\n, \\b ... |
|
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 | Returns |
|
1256 | Returns | |
938 | ======= |
|
1257 | ======= | |
939 |
|
1258 | |||
@@ -943,7 +1262,6 b' def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]:' | |||||
943 | empty string, |
|
1262 | empty string, | |
944 | - a sequence (of 1), name for the match Unicode character, preceded by |
|
1263 | - a sequence (of 1), name for the match Unicode character, preceded by | |
945 | backslash, or empty if no match. |
|
1264 | backslash, or empty if no match. | |
946 |
|
||||
947 | """ |
|
1265 | """ | |
948 | if len(text)<2: |
|
1266 | if len(text)<2: | |
949 | return '', () |
|
1267 | return '', () | |
@@ -963,11 +1281,26 b' def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]:' | |||||
963 | pass |
|
1281 | pass | |
964 | return '', () |
|
1282 | return '', () | |
965 |
|
1283 | |||
|
1284 | ||||
|
1285 | @context_matcher() | |||
|
1286 | def back_latex_name_matcher(context: CompletionContext): | |||
|
1287 | """Match latex characters back to unicode name | |||
|
1288 | ||||
|
1289 | Same as :any:`back_latex_name_matches`, but adopted to new Matcher API. | |||
|
1290 | """ | |||
|
1291 | fragment, matches = back_latex_name_matches(context.text_until_cursor) | |||
|
1292 | return _convert_matcher_v1_result_to_v2( | |||
|
1293 | matches, type="latex", fragment=fragment, suppress_if_matches=True | |||
|
1294 | ) | |||
|
1295 | ||||
|
1296 | ||||
966 |
def back_latex_name_matches(text:str) -> Tuple[str, Sequence[str]] |
|
1297 | def back_latex_name_matches(text: str) -> Tuple[str, Sequence[str]]: | |
967 | """Match latex characters back to unicode name |
|
1298 | """Match latex characters back to unicode name | |
968 |
|
1299 | |||
969 | This does ``\\ℵ`` -> ``\\aleph`` |
|
1300 | This does ``\\ℵ`` -> ``\\aleph`` | |
970 |
|
1301 | |||
|
1302 | .. deprecated:: 8.6 | |||
|
1303 | You can use :meth:`back_latex_name_matcher` instead. | |||
971 | """ |
|
1304 | """ | |
972 | if len(text)<2: |
|
1305 | if len(text)<2: | |
973 | return '', () |
|
1306 | return '', () | |
@@ -1042,11 +1375,23 b' def _make_signature(completion)-> str:' | |||||
1042 | for p in signature.defined_names()) if f]) |
|
1375 | for p in signature.defined_names()) if f]) | |
1043 |
|
1376 | |||
1044 |
|
1377 | |||
1045 | class _CompleteResult(NamedTuple): |
|
1378 | _CompleteResult = Dict[str, MatcherResult] | |
1046 | matched_text : str |
|
1379 | ||
1047 | matches: Sequence[str] |
|
1380 | ||
1048 | matches_origin: Sequence[str] |
|
1381 | def _convert_matcher_v1_result_to_v2( | |
1049 | jedi_matches: Any |
|
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 | class IPCompleter(Completer): |
|
1397 | class IPCompleter(Completer): | |
@@ -1062,17 +1407,59 b' class IPCompleter(Completer):' | |||||
1062 | else: |
|
1407 | else: | |
1063 | self.splitter.delims = DELIMS |
|
1408 | self.splitter.delims = DELIMS | |
1064 |
|
1409 | |||
1065 |
dict_keys_only = Bool( |
|
1410 | dict_keys_only = Bool( | |
1066 | help="""Whether to show dict key matches only""") |
|
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 | help="""Whether to merge completion results into a single list |
|
1446 | help="""Whether to merge completion results into a single list | |
1070 |
|
1447 | |||
1071 | If False, only the completion results from the first non-empty |
|
1448 | If False, only the completion results from the first non-empty | |
1072 | completer will be returned. |
|
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 | ).tag(config=True) |
|
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 | help="""Instruct the completer to omit private method names |
|
1463 | help="""Instruct the completer to omit private method names | |
1077 |
|
1464 | |||
1078 | Specifically, when completing on ``object.<tab>``. |
|
1465 | Specifically, when completing on ``object.<tab>``. | |
@@ -1148,7 +1535,7 b' class IPCompleter(Completer):' | |||||
1148 | namespace=namespace, |
|
1535 | namespace=namespace, | |
1149 | global_namespace=global_namespace, |
|
1536 | global_namespace=global_namespace, | |
1150 | config=config, |
|
1537 | config=config, | |
1151 | **kwargs |
|
1538 | **kwargs, | |
1152 | ) |
|
1539 | ) | |
1153 |
|
1540 | |||
1154 | # List where completion matches will be stored |
|
1541 | # List where completion matches will be stored | |
@@ -1177,8 +1564,8 b' class IPCompleter(Completer):' | |||||
1177 | #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)') |
|
1564 | #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)') | |
1178 |
|
1565 | |||
1179 | self.magic_arg_matchers = [ |
|
1566 | self.magic_arg_matchers = [ | |
1180 |
self.magic_config_matche |
|
1567 | self.magic_config_matcher, | |
1181 |
self.magic_color_matche |
|
1568 | self.magic_color_matcher, | |
1182 | ] |
|
1569 | ] | |
1183 |
|
1570 | |||
1184 | # This is set externally by InteractiveShell |
|
1571 | # This is set externally by InteractiveShell | |
@@ -1190,27 +1577,50 b' class IPCompleter(Completer):' | |||||
1190 | # attribute through the `@unicode_names` property. |
|
1577 | # attribute through the `@unicode_names` property. | |
1191 | self._unicode_names = None |
|
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 | @property |
|
1595 | @property | |
1194 |
def matchers(self) -> List[ |
|
1596 | def matchers(self) -> List[Matcher]: | |
1195 | """All active matcher routines for completion""" |
|
1597 | """All active matcher routines for completion""" | |
1196 | if self.dict_keys_only: |
|
1598 | if self.dict_keys_only: | |
1197 |
return [self.dict_key_matche |
|
1599 | return [self.dict_key_matcher] | |
1198 |
|
1600 | |||
1199 | if self.use_jedi: |
|
1601 | if self.use_jedi: | |
1200 | return [ |
|
1602 | return [ | |
1201 | *self.custom_matchers, |
|
1603 | *self.custom_matchers, | |
1202 |
self. |
|
1604 | *self._backslash_combining_matchers, | |
1203 |
self. |
|
1605 | *self.magic_arg_matchers, | |
1204 |
self. |
|
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 | else: |
|
1612 | else: | |
1207 | return [ |
|
1613 | return [ | |
1208 | *self.custom_matchers, |
|
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 | self.python_matches, |
|
1621 | self.python_matches, | |
1211 |
self.file_matche |
|
1622 | self.file_matcher, | |
1212 |
self. |
|
1623 | self.python_func_kw_matcher, | |
1213 | self.python_func_kw_matches, |
|
|||
1214 | ] |
|
1624 | ] | |
1215 |
|
1625 | |||
1216 | def all_completions(self, text:str) -> List[str]: |
|
1626 | def all_completions(self, text:str) -> List[str]: | |
@@ -1231,6 +1641,14 b' class IPCompleter(Completer):' | |||||
1231 | return [f.replace("\\","/") |
|
1641 | return [f.replace("\\","/") | |
1232 | for f in self.glob("%s*" % text)] |
|
1642 | for f in self.glob("%s*" % text)] | |
1233 |
|
1643 | |||
|
1644 | @context_matcher() | |||
|
1645 | def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult: | |||
|
1646 | """Same as :any:`file_matches`, but adopted to new Matcher API.""" | |||
|
1647 | matches = self.file_matches(context.token) | |||
|
1648 | # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter, | |||
|
1649 | # starts with `/home/`, `C:\`, etc) | |||
|
1650 | return _convert_matcher_v1_result_to_v2(matches, type="path") | |||
|
1651 | ||||
1234 | def file_matches(self, text:str)->List[str]: |
|
1652 | def file_matches(self, text: str) -> List[str]: | |
1235 | """Match filenames, expanding ~USER type strings. |
|
1653 | """Match filenames, expanding ~USER type strings. | |
1236 |
|
1654 | |||
@@ -1243,7 +1661,11 b' class IPCompleter(Completer):' | |||||
1243 | only the parts after what's already been typed (instead of the |
|
1661 | only the parts after what's already been typed (instead of the | |
1244 | full completions, as is normally done). I don't think with the |
|
1662 | full completions, as is normally done). I don't think with the | |
1245 | current (as of Python 2.3) Python readline it's possible to do |
|
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 | # chars that require escaping with backslash - i.e. chars |
|
1670 | # chars that require escaping with backslash - i.e. chars | |
1249 | # that readline treats incorrectly as delimiters, but we |
|
1671 | # that readline treats incorrectly as delimiters, but we | |
@@ -1313,8 +1735,22 b' class IPCompleter(Completer):' | |||||
1313 | # Mark directories in input list by appending '/' to their names. |
|
1735 | # Mark directories in input list by appending '/' to their names. | |
1314 | return [x+'/' if os.path.isdir(x) else x for x in matches] |
|
1736 | return [x+'/' if os.path.isdir(x) else x for x in matches] | |
1315 |
|
1737 | |||
|
1738 | @context_matcher() | |||
|
1739 | def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult: | |||
|
1740 | """Match magics.""" | |||
|
1741 | text = context.token | |||
|
1742 | matches = self.magic_matches(text) | |||
|
1743 | result = _convert_matcher_v1_result_to_v2(matches, type="magic") | |||
|
1744 | is_magic_prefix = len(text) > 0 and text[0] == "%" | |||
|
1745 | result["suppress"] = is_magic_prefix and bool(result["completions"]) | |||
|
1746 | return result | |||
|
1747 | ||||
1316 | def magic_matches(self, text:str): |
|
1748 | def magic_matches(self, text: str): | |
1317 |
"""Match magics |
|
1749 | """Match magics. | |
|
1750 | ||||
|
1751 | .. deprecated:: 8.6 | |||
|
1752 | You can use :meth:`magic_matcher` instead. | |||
|
1753 | """ | |||
1318 | # Get all shell magics now rather than statically, so magics loaded at |
|
1754 | # Get all shell magics now rather than statically, so magics loaded at | |
1319 | # runtime show up too. |
|
1755 | # runtime show up too. | |
1320 | lsm = self.shell.magics_manager.lsmagic() |
|
1756 | lsm = self.shell.magics_manager.lsmagic() | |
@@ -1355,8 +1791,19 b' class IPCompleter(Completer):' | |||||
1355 |
|
1791 | |||
1356 | return comp |
|
1792 | return comp | |
1357 |
|
1793 | |||
|
1794 | @context_matcher() | |||
|
1795 | def magic_config_matcher(self, context: CompletionContext) -> SimpleMatcherResult: | |||
|
1796 | """Match class names and attributes for %config magic.""" | |||
|
1797 | # NOTE: uses `line_buffer` equivalent for compatibility | |||
|
1798 | matches = self.magic_config_matches(context.line_with_cursor) | |||
|
1799 | return _convert_matcher_v1_result_to_v2(matches, type="param") | |||
|
1800 | ||||
1358 | def magic_config_matches(self, text:str) -> List[str]: |
|
1801 | def magic_config_matches(self, text: str) -> List[str]: | |
1359 |
""" |
|
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 | texts = text.strip().split() |
|
1807 | texts = text.strip().split() | |
1361 |
|
1808 | |||
1362 | if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'): |
|
1809 | if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'): | |
@@ -1390,8 +1837,19 b' class IPCompleter(Completer):' | |||||
1390 | if attr.startswith(texts[1]) ] |
|
1837 | if attr.startswith(texts[1]) ] | |
1391 | return [] |
|
1838 | return [] | |
1392 |
|
1839 | |||
|
1840 | @context_matcher() | |||
|
1841 | def magic_color_matcher(self, context: CompletionContext) -> SimpleMatcherResult: | |||
|
1842 | """Match color schemes for %colors magic.""" | |||
|
1843 | # NOTE: uses `line_buffer` equivalent for compatibility | |||
|
1844 | matches = self.magic_color_matches(context.line_with_cursor) | |||
|
1845 | return _convert_matcher_v1_result_to_v2(matches, type="param") | |||
|
1846 | ||||
1393 |
def magic_color_matches(self, text:str) -> List[str] |
|
1847 | def magic_color_matches(self, text: str) -> List[str]: | |
1394 |
""" |
|
1848 | """Match color schemes for %colors magic. | |
|
1849 | ||||
|
1850 | .. deprecated:: 8.6 | |||
|
1851 | You can use :meth:`magic_color_matcher` instead. | |||
|
1852 | """ | |||
1395 | texts = text.split() |
|
1853 | texts = text.split() | |
1396 | if text.endswith(' '): |
|
1854 | if text.endswith(' '): | |
1397 | # .split() strips off the trailing whitespace. Add '' back |
|
1855 | # .split() strips off the trailing whitespace. Add '' back | |
@@ -1404,9 +1862,24 b' class IPCompleter(Completer):' | |||||
1404 | if color.startswith(prefix) ] |
|
1862 | if color.startswith(prefix) ] | |
1405 | return [] |
|
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 | cursor position. |
|
1883 | cursor position. | |
1411 |
|
1884 | |||
1412 | Parameters |
|
1885 | Parameters | |
@@ -1422,6 +1895,9 b' class IPCompleter(Completer):' | |||||
1422 | ----- |
|
1895 | ----- | |
1423 | If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion` |
|
1896 | If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion` | |
1424 | object containing a string with the Jedi debug information attached. |
|
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 | namespaces = [self.namespace] |
|
1902 | namespaces = [self.namespace] | |
1427 | if self.global_namespace is not None: |
|
1903 | if self.global_namespace is not None: | |
@@ -1558,8 +2034,18 b' class IPCompleter(Completer):' | |||||
1558 |
|
2034 | |||
1559 | return list(set(ret)) |
|
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 | def python_func_kw_matches(self, text): |
|
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 | if "." in text: # a parameter cannot be dotted |
|
2050 | if "." in text: # a parameter cannot be dotted | |
1565 | return [] |
|
2051 | return [] | |
@@ -1654,9 +2140,20 b' class IPCompleter(Completer):' | |||||
1654 | return obj.dtype.names or [] |
|
2140 | return obj.dtype.names or [] | |
1655 | return [] |
|
2141 | return [] | |
1656 |
|
2142 | |||
|
2143 | @context_matcher() | |||
|
2144 | def dict_key_matcher(self, context: CompletionContext) -> SimpleMatcherResult: | |||
|
2145 | """Match string keys in a dictionary, after e.g. ``foo[``.""" | |||
|
2146 | matches = self.dict_key_matches(context.token) | |||
|
2147 | return _convert_matcher_v1_result_to_v2( | |||
|
2148 | matches, type="dict key", suppress_if_matches=True | |||
|
2149 | ) | |||
|
2150 | ||||
1657 | def dict_key_matches(self, text:str) -> List[str]: |
|
2151 | def dict_key_matches(self, text: str) -> List[str]: | |
1658 |
"Match string keys in a dictionary, after e.g. |
|
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 | if self.__dict_key_regexps is not None: |
|
2158 | if self.__dict_key_regexps is not None: | |
1662 | regexps = self.__dict_key_regexps |
|
2159 | regexps = self.__dict_key_regexps | |
@@ -1758,6 +2255,14 b' class IPCompleter(Completer):' | |||||
1758 |
|
2255 | |||
1759 | return [leading + k + suf for k in matches] |
|
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 | @staticmethod |
|
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 | """Match Latex-like syntax for unicode characters base |
|
2268 | """Match Latex-like syntax for unicode characters base | |
@@ -1780,11 +2285,24 b' class IPCompleter(Completer):' | |||||
1780 | pass |
|
2285 | pass | |
1781 | return '', [] |
|
2286 | return '', [] | |
1782 |
|
2287 | |||
|
2288 | @context_matcher() | |||
|
2289 | def latex_name_matcher(self, context: CompletionContext): | |||
|
2290 | """Match Latex syntax for unicode characters. | |||
|
2291 | ||||
|
2292 | This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` | |||
|
2293 | """ | |||
|
2294 | fragment, matches = self.latex_matches(context.text_until_cursor) | |||
|
2295 | return _convert_matcher_v1_result_to_v2( | |||
|
2296 | matches, type="latex", fragment=fragment, suppress_if_matches=True | |||
|
2297 | ) | |||
1783 |
|
2298 | |||
1784 | def latex_matches(self, text:str) -> Tuple[str, Sequence[str]]: |
|
2299 | def latex_matches(self, text: str) -> Tuple[str, Sequence[str]]: | |
1785 | """Match Latex syntax for unicode characters. |
|
2300 | """Match Latex syntax for unicode characters. | |
1786 |
|
2301 | |||
1787 | This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` |
|
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 | slashpos = text.rfind('\\') |
|
2307 | slashpos = text.rfind('\\') | |
1790 | if slashpos > -1: |
|
2308 | if slashpos > -1: | |
@@ -1801,7 +2319,25 b' class IPCompleter(Completer):' | |||||
1801 | return s, matches |
|
2319 | return s, matches | |
1802 | return '', () |
|
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 | def dispatch_custom_completer(self, text): |
|
2336 | def dispatch_custom_completer(self, text): | |
|
2337 | """ | |||
|
2338 | .. deprecated:: 8.6 | |||
|
2339 | You can use :meth:`custom_completer_matcher` instead. | |||
|
2340 | """ | |||
1805 | if not self.custom_completers: |
|
2341 | if not self.custom_completers: | |
1806 | return |
|
2342 | return | |
1807 |
|
2343 | |||
@@ -1955,12 +2491,25 b' class IPCompleter(Completer):' | |||||
1955 | """ |
|
2491 | """ | |
1956 | deadline = time.monotonic() + _timeout |
|
2492 | deadline = time.monotonic() + _timeout | |
1957 |
|
2493 | |||
1958 |
|
||||
1959 | before = full_text[:offset] |
|
2494 | before = full_text[:offset] | |
1960 | cursor_line, cursor_column = position_to_cursor(full_text, offset) |
|
2495 | cursor_line, cursor_column = position_to_cursor(full_text, offset) | |
1961 |
|
2496 | |||
1962 | matched_text, matches, matches_origin, jedi_matches = self._complete( |
|
2497 | jedi_matcher_id = _get_matcher_id(self._jedi_matcher) | |
1963 | full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column) |
|
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 | iter_jm = iter(jedi_matches) |
|
2514 | iter_jm = iter(jedi_matches) | |
1966 | if _timeout: |
|
2515 | if _timeout: | |
@@ -1988,28 +2537,57 b' class IPCompleter(Completer):' | |||||
1988 |
|
2537 | |||
1989 | for jm in iter_jm: |
|
2538 | for jm in iter_jm: | |
1990 | delta = len(jm.name_with_symbols) - len(jm.complete) |
|
2539 | delta = len(jm.name_with_symbols) - len(jm.complete) | |
1991 |
yield Completion( |
|
2540 | yield Completion( | |
|
2541 | start=offset - delta, | |||
1992 |
|
|
2542 | end=offset, | |
1993 |
|
|
2543 | text=jm.name_with_symbols, | |
1994 |
|
|
2544 | type=_UNKNOWN_TYPE, # don't compute type for speed | |
1995 |
|
|
2545 | _origin="jedi", | |
1996 |
|
|
2546 | signature="", | |
1997 |
|
2547 | ) | ||
1998 |
|
||||
1999 | start_offset = before.rfind(matched_text) |
|
|||
2000 |
|
2548 | |||
2001 | # TODO: |
|
2549 | # TODO: | |
2002 | # Suppress this, right now just for debug. |
|
2550 | # Suppress this, right now just for debug. | |
2003 |
if jedi_matches and |
|
2551 | if jedi_matches and non_jedi_results and self.debug: | |
2004 | yield Completion(start=start_offset, end=offset, text='--jedi/ipython--', |
|
2552 | some_start_offset = before.rfind( | |
2005 | _origin='debug', type='none', signature='') |
|
2553 | next(iter(non_jedi_results.values()))["matched_fragment"] | |
|
2554 | ) | |||
|
2555 | yield Completion( | |||
|
2556 | start=some_start_offset, | |||
|
2557 | end=offset, | |||
|
2558 | text="--jedi/ipython--", | |||
|
2559 | _origin="debug", | |||
|
2560 | type="none", | |||
|
2561 | signature="", | |||
|
2562 | ) | |||
|
2563 | ||||
|
2564 | ordered = [] | |||
|
2565 | sortable = [] | |||
|
2566 | ||||
|
2567 | for origin, result in non_jedi_results.items(): | |||
|
2568 | matched_text = result["matched_fragment"] | |||
|
2569 | start_offset = before.rfind(matched_text) | |||
|
2570 | is_ordered = result.get("ordered", False) | |||
|
2571 | container = ordered if is_ordered else sortable | |||
2006 |
|
2572 | |||
2007 | # I'm unsure if this is always true, so let's assert and see if it |
|
2573 | # I'm unsure if this is always true, so let's assert and see if it | |
2008 | # crash |
|
2574 | # crash | |
2009 | assert before.endswith(matched_text) |
|
2575 | assert before.endswith(matched_text) | |
2010 | for m, t in zip(matches, matches_origin): |
|
|||
2011 | yield Completion(start=start_offset, end=offset, text=m, _origin=t, signature='', type='<unknown>') |
|
|||
2012 |
|
2576 | |||
|
2577 | for simple_completion in result["completions"]: | |||
|
2578 | completion = Completion( | |||
|
2579 | start=start_offset, | |||
|
2580 | end=offset, | |||
|
2581 | text=simple_completion.text, | |||
|
2582 | _origin=origin, | |||
|
2583 | signature="", | |||
|
2584 | type=simple_completion.type or _UNKNOWN_TYPE, | |||
|
2585 | ) | |||
|
2586 | container.append(completion) | |||
|
2587 | ||||
|
2588 | yield from list(self._deduplicate(ordered + self._sort(sortable)))[ | |||
|
2589 | :MATCHES_LIMIT | |||
|
2590 | ] | |||
2013 |
|
2591 | |||
2014 | def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]: |
|
2592 | def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]: | |
2015 | """Find completions for the given text and line context. |
|
2593 | """Find completions for the given text and line context. | |
@@ -2050,7 +2628,56 b' class IPCompleter(Completer):' | |||||
2050 | PendingDeprecationWarning) |
|
2628 | PendingDeprecationWarning) | |
2051 | # potential todo, FOLD the 3rd throw away argument of _complete |
|
2629 | # potential todo, FOLD the 3rd throw away argument of _complete | |
2052 | # into the first 2 one. |
|
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 | def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, |
|
2682 | def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, | |
2056 | full_text=None) -> _CompleteResult: |
|
2683 | full_text=None) -> _CompleteResult: | |
@@ -2085,14 +2712,10 b' class IPCompleter(Completer):' | |||||
2085 |
|
2712 | |||
2086 | Returns |
|
2713 | Returns | |
2087 | ------- |
|
2714 | ------- | |
2088 | A tuple of N elements which are (likely): |
|
2715 | An ordered dictionary where keys are identifiers of completion | |
2089 | matched_text: ? the text that the complete matched |
|
2716 | matchers and values are ``MatcherResult``s. | |
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. |
|
|||
2093 | """ |
|
2717 | """ | |
2094 |
|
2718 | |||
2095 |
|
||||
2096 | # if the cursor position isn't given, the only sane assumption we can |
|
2719 | # if the cursor position isn't given, the only sane assumption we can | |
2097 | # make is that it's at the end of the line (the common case) |
|
2720 | # make is that it's at the end of the line (the common case) | |
2098 | if cursor_pos is None: |
|
2721 | if cursor_pos is None: | |
@@ -2105,97 +2728,155 b' class IPCompleter(Completer):' | |||||
2105 | if (not line_buffer) and full_text: |
|
2728 | if (not line_buffer) and full_text: | |
2106 | line_buffer = full_text.split('\n')[cursor_line] |
|
2729 | line_buffer = full_text.split('\n')[cursor_line] | |
2107 | if not text: # issue #11508: check line_buffer before calling split_line |
|
2730 | if not text: # issue #11508: check line_buffer before calling split_line | |
2108 | text = self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else '' |
|
2731 | text = ( | |
2109 |
|
2732 | self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else "" | ||
2110 | if self.backslash_combining_completions: |
|
2733 | ) | |
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 |
|
||||
2124 |
|
2734 | |||
2125 | # If no line buffer is given, assume the input text is all there was |
|
2735 | # If no line buffer is given, assume the input text is all there was | |
2126 | if line_buffer is None: |
|
2736 | if line_buffer is None: | |
2127 | line_buffer = text |
|
2737 | line_buffer = text | |
2128 |
|
2738 | |||
|
2739 | # deprecated - do not use `line_buffer` in new code. | |||
2129 | self.line_buffer = line_buffer |
|
2740 | self.line_buffer = line_buffer | |
2130 | self.text_until_cursor = self.line_buffer[:cursor_pos] |
|
2741 | self.text_until_cursor = self.line_buffer[:cursor_pos] | |
2131 |
|
2742 | |||
2132 | # Do magic arg matches |
|
2743 | if not full_text: | |
2133 | for matcher in self.magic_arg_matchers: |
|
2744 | full_text = line_buffer | |
2134 | matches = list(matcher(line_buffer))[:MATCHES_LIMIT] |
|
2745 | ||
2135 | if matches: |
|
2746 | context = CompletionContext( | |
2136 | origins = [matcher.__qualname__] * len(matches) |
|
2747 | full_text=full_text, | |
2137 | return _CompleteResult(text, matches, origins, ()) |
|
2748 | cursor_position=cursor_pos, | |
|
2749 | cursor_line=cursor_line, | |||
|
2750 | token=text, | |||
|
2751 | limit=MATCHES_LIMIT, | |||
|
2752 | ) | |||
2138 |
|
2753 | |||
2139 | # Start with a clean slate of completions |
|
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 |
|
2757 | jedi_matcher_id = _get_matcher_id(self._jedi_matcher) | |
2143 | # different types of objects. The rlcomplete() method could then |
|
2758 | ||
2144 | # simply collapse the dict into a list for readline, but we'd have |
|
2759 | suppressed_matchers = set() | |
2145 | # richer completion semantics in other environments. |
|
2760 | ||
2146 | is_magic_prefix = len(text) > 0 and text[0] == "%" |
|
2761 | matchers = { | |
2147 | completions: Iterable[Any] = [] |
|
2762 | _get_matcher_id(matcher): matcher | |
2148 | if self.use_jedi and not is_magic_prefix: |
|
2763 | for matcher in sorted( | |
2149 | if not full_text: |
|
2764 | self.matchers, key=_get_matcher_priority, reverse=True | |
2150 | full_text = line_buffer |
|
2765 | ) | |
2151 | completions = self._jedi_matches( |
|
2766 | } | |
2152 | cursor_pos, cursor_line, full_text) |
|
2767 | ||
|
2768 | for matcher_id, matcher in matchers.items(): | |||
|
2769 | api_version = _get_matcher_api_version(matcher) | |||
|
2770 | matcher_id = _get_matcher_id(matcher) | |||
|
2771 | ||||
|
2772 | if matcher_id in self.disable_matchers: | |||
|
2773 | continue | |||
|
2774 | ||||
|
2775 | if matcher_id in results: | |||
|
2776 | warnings.warn(f"Duplicate matcher ID: {matcher_id}.") | |||
|
2777 | ||||
|
2778 | if matcher_id in suppressed_matchers: | |||
|
2779 | continue | |||
2153 |
|
2780 | |||
2154 | if self.merge_completions: |
|
|||
2155 | matches = [] |
|
|||
2156 | for matcher in self.matchers: |
|
|||
2157 |
|
|
2781 | try: | |
2158 | matches.extend([(m, matcher.__qualname__) |
|
2782 | if api_version == 1: | |
2159 | for m in matcher(text)]) |
|
2783 | result = _convert_matcher_v1_result_to_v2( | |
|
2784 | matcher(text), type=_UNKNOWN_TYPE | |||
|
2785 | ) | |||
|
2786 | elif api_version == 2: | |||
|
2787 | result = cast(matcher, MatcherAPIv2)(context) | |||
|
2788 | else: | |||
|
2789 | raise ValueError(f"Unsupported API version {api_version}") | |||
2160 |
|
|
2790 | except: | |
2161 |
|
|
2791 | # Show the ugly traceback if the matcher causes an | |
2162 |
|
|
2792 | # exception, but do NOT crash the kernel! | |
2163 |
|
|
2793 | sys.excepthook(*sys.exc_info()) | |
2164 | else: |
|
2794 | continue | |
2165 | for matcher in self.matchers: |
|
|||
2166 | matches = [(m, matcher.__qualname__) |
|
|||
2167 | for m in matcher(text)] |
|
|||
2168 | if matches: |
|
|||
2169 | break |
|
|||
2170 |
|
2795 | |||
2171 | seen = set() |
|
2796 | # set default value for matched fragment if suffix was not selected. | |
2172 | filtered_matches = set() |
|
2797 | result["matched_fragment"] = result.get("matched_fragment", context.token) | |
2173 | for m in matches: |
|
|||
2174 | t, c = m |
|
|||
2175 | if t not in seen: |
|
|||
2176 | filtered_matches.add(m) |
|
|||
2177 | seen.add(t) |
|
|||
2178 |
|
2798 | |||
2179 | _filtered_matches = sorted(filtered_matches, key=lambda x: completions_sorting_key(x[0])) |
|
2799 | if not suppressed_matchers: | |
|
2800 | suppression_recommended = result.get("suppress", False) | |||
2180 |
|
2801 | |||
2181 | custom_res = [(m, 'custom') for m in self.dispatch_custom_completer(text) or []] |
|
2802 | suppression_config = ( | |
|
2803 | self.suppress_competing_matchers.get(matcher_id, None) | |||
|
2804 | if isinstance(self.suppress_competing_matchers, dict) | |||
|
2805 | else self.suppress_competing_matchers | |||
|
2806 | ) | |||
|
2807 | should_suppress = ( | |||
|
2808 | (suppression_config is True) | |||
|
2809 | or (suppression_recommended and (suppression_config is not False)) | |||
|
2810 | ) and len(result["completions"]) | |||
2182 |
|
2811 | |||
2183 | _filtered_matches = custom_res or _filtered_matches |
|
2812 | if should_suppress: | |
|
2813 | suppression_exceptions = result.get("do_not_suppress", set()) | |||
|
2814 | try: | |||
|
2815 | to_suppress = set(suppression_recommended) | |||
|
2816 | except TypeError: | |||
|
2817 | to_suppress = set(matchers) | |||
|
2818 | suppressed_matchers = to_suppress - suppression_exceptions | |||
|
2819 | ||||
|
2820 | new_results = {} | |||
|
2821 | for previous_matcher_id, previous_result in results.items(): | |||
|
2822 | if previous_matcher_id not in suppressed_matchers: | |||
|
2823 | new_results[previous_matcher_id] = previous_result | |||
|
2824 | results = new_results | |||
|
2825 | ||||
|
2826 | results[matcher_id] = result | |||
|
2827 | ||||
|
2828 | _, matches = self._arrange_and_extract( | |||
|
2829 | results, | |||
|
2830 | # TODO Jedi completions non included in legacy stateful API; was this deliberate or omission? | |||
|
2831 | # if it was omission, we can remove the filtering step, otherwise remove this comment. | |||
|
2832 | skip_matchers={jedi_matcher_id}, | |||
|
2833 | abort_if_offset_changes=False, | |||
|
2834 | ) | |||
2184 |
|
2835 | |||
2185 | _filtered_matches = _filtered_matches[:MATCHES_LIMIT] |
|
2836 | # populate legacy stateful API | |
2186 |
|
|
2837 | self.matches = matches | |
2187 | origins = [m[1] for m in _filtered_matches] |
|
|||
2188 |
|
2838 | |||
2189 | self.matches = _matches |
|
2839 | return results | |
2190 |
|
2840 | |||
2191 | return _CompleteResult(text, _matches, origins, completions) |
|
2841 | @staticmethod | |
|
2842 | def _deduplicate( | |||
|
2843 | matches: Sequence[SimpleCompletion], | |||
|
2844 | ) -> Iterable[SimpleCompletion]: | |||
|
2845 | filtered_matches = {} | |||
|
2846 | for match in matches: | |||
|
2847 | text = match.text | |||
|
2848 | if ( | |||
|
2849 | text not in filtered_matches | |||
|
2850 | or filtered_matches[text].type == _UNKNOWN_TYPE | |||
|
2851 | ): | |||
|
2852 | filtered_matches[text] = match | |||
|
2853 | ||||
|
2854 | return filtered_matches.values() | |||
|
2855 | ||||
|
2856 | @staticmethod | |||
|
2857 | def _sort(matches: Sequence[SimpleCompletion]): | |||
|
2858 | return sorted(matches, key=lambda x: completions_sorting_key(x.text)) | |||
|
2859 | ||||
|
2860 | @context_matcher() | |||
|
2861 | def fwd_unicode_matcher(self, context: CompletionContext): | |||
|
2862 | """Same as :any:`fwd_unicode_match`, but adopted to new Matcher API.""" | |||
|
2863 | # TODO: use `context.limit` to terminate early once we matched the maximum | |||
|
2864 | # number that will be used downstream; can be added as an optional to | |||
|
2865 | # `fwd_unicode_match(text: str, limit: int = None)` or we could re-implement here. | |||
|
2866 | fragment, matches = self.fwd_unicode_match(context.text_until_cursor) | |||
|
2867 | return _convert_matcher_v1_result_to_v2( | |||
|
2868 | matches, type="unicode", fragment=fragment, suppress_if_matches=True | |||
|
2869 | ) | |||
2192 |
|
2870 | |||
2193 | def fwd_unicode_match(self, text:str) -> Tuple[str, Sequence[str]]: |
|
2871 | def fwd_unicode_match(self, text: str) -> Tuple[str, Sequence[str]]: | |
2194 | """ |
|
2872 | """ | |
2195 | Forward match a string starting with a backslash with a list of |
|
2873 | Forward match a string starting with a backslash with a list of | |
2196 | potential Unicode completions. |
|
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 | Returns |
|
2881 | Returns | |
2201 | ------- |
|
2882 | ------- |
@@ -389,7 +389,19 b' class DisplayObject(object):' | |||||
389 |
|
389 | |||
390 |
|
390 | |||
391 | class TextDisplayObject(DisplayObject): |
|
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 | def _check_data(self): |
|
405 | def _check_data(self): | |
394 | if self.data is not None and not isinstance(self.data, str): |
|
406 | if self.data is not None and not isinstance(self.data, str): | |
395 | raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data)) |
|
407 | raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data)) | |
@@ -613,8 +625,9 b' class JSON(DisplayObject):' | |||||
613 | def _repr_json_(self): |
|
625 | def _repr_json_(self): | |
614 | return self._data_and_metadata() |
|
626 | return self._data_and_metadata() | |
615 |
|
627 | |||
|
628 | ||||
616 | _css_t = """var link = document.createElement("link"); |
|
629 | _css_t = """var link = document.createElement("link"); | |
617 |
link.re |
|
630 | link.rel = "stylesheet"; | |
618 | link.type = "text/css"; |
|
631 | link.type = "text/css"; | |
619 | link.href = "%s"; |
|
632 | link.href = "%s"; | |
620 | document.head.appendChild(link); |
|
633 | document.head.appendChild(link); |
@@ -88,13 +88,7 b' class ExtensionManager(Configurable):' | |||||
88 |
|
88 | |||
89 | with self.shell.builtin_trap: |
|
89 | with self.shell.builtin_trap: | |
90 | if module_str not in sys.modules: |
|
90 | if module_str not in sys.modules: | |
91 | with prepended_to_syspath(self.ipython_extension_dir): |
|
|||
92 |
|
|
91 | mod = import_module(module_str) | |
93 | if mod.__file__.startswith(self.ipython_extension_dir): |
|
|||
94 | print(("Loading extensions from {dir} is deprecated. " |
|
|||
95 | "We recommend managing extensions like any " |
|
|||
96 | "other Python packages, in site-packages.").format( |
|
|||
97 | dir=compress_user(self.ipython_extension_dir))) |
|
|||
98 | mod = sys.modules[module_str] |
|
92 | mod = sys.modules[module_str] | |
99 | if self._call_load_ipython_extension(mod): |
|
93 | if self._call_load_ipython_extension(mod): | |
100 | self.loaded.add(module_str) |
|
94 | self.loaded.add(module_str) | |
@@ -155,13 +149,3 b' class ExtensionManager(Configurable):' | |||||
155 | if hasattr(mod, 'unload_ipython_extension'): |
|
149 | if hasattr(mod, 'unload_ipython_extension'): | |
156 | mod.unload_ipython_extension(self.shell) |
|
150 | mod.unload_ipython_extension(self.shell) | |
157 | return True |
|
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 | return lines_before + [new_line] + lines_after |
|
430 | return lines_before + [new_line] + lines_after | |
431 |
|
431 | |||
432 | _help_end_re = re.compile(r"""(%{0,2} |
|
432 | ||
|
433 | _help_end_re = re.compile( | |||
|
434 | r"""(%{0,2} | |||
433 |
|
|
435 | (?!\d)[\w*]+ # Variable name | |
434 | (\.(?!\d)[\w*]+)* # .etc.etc |
|
436 | (\.(?!\d)[\w*]+|\[-?[0-9]+\])* # .etc.etc or [0], we only support literal integers. | |
435 | ) |
|
437 | ) | |
436 |
|
|
438 | (\?\??)$ # ? or ?? | |
437 | """, |
|
439 | """, | |
438 | re.VERBOSE) |
|
440 | re.VERBOSE, | |
|
441 | ) | |||
|
442 | ||||
439 |
|
443 | |||
440 | class HelpEnd(TokenTransformBase): |
|
444 | class HelpEnd(TokenTransformBase): | |
441 | """Transformer for help syntax: obj? and obj??""" |
|
445 | """Transformer for help syntax: obj? and obj??""" | |
@@ -464,7 +468,8 b' class HelpEnd(TokenTransformBase):' | |||||
464 | def transform(self, lines): |
|
468 | def transform(self, lines): | |
465 | """Transform a help command found by the ``find()`` classmethod. |
|
469 | """Transform a help command found by the ``find()`` classmethod. | |
466 | """ |
|
470 | """ | |
467 | piece = ''.join(lines[self.start_line:self.q_line+1]) |
|
471 | ||
|
472 | piece = "".join(lines[self.start_line : self.q_line + 1]) | |||
468 | indent, content = piece[:self.start_col], piece[self.start_col:] |
|
473 | indent, content = piece[: self.start_col], piece[self.start_col :] | |
469 | lines_before = lines[:self.start_line] |
|
474 | lines_before = lines[: self.start_line] | |
470 | lines_after = lines[self.q_line + 1:] |
|
475 | lines_after = lines[self.q_line + 1 :] | |
@@ -543,8 +548,13 b' def has_sunken_brackets(tokens: List[tokenize.TokenInfo]):' | |||||
543 |
|
548 | |||
544 | def show_linewise_tokens(s: str): |
|
549 | def show_linewise_tokens(s: str): | |
545 | """For investigation and debugging""" |
|
550 | """For investigation and debugging""" | |
546 | if not s.endswith('\n'): |
|
551 | warnings.warn( | |
547 | s += '\n' |
|
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 | lines = s.splitlines(keepends=True) |
|
558 | lines = s.splitlines(keepends=True) | |
549 | for line in make_tokens_by_line(lines): |
|
559 | for line in make_tokens_by_line(lines): | |
550 | print("Line -------") |
|
560 | print("Line -------") |
@@ -61,7 +61,7 b' from IPython.core import magic, oinspect, page, prefilter, ultratb' | |||||
61 | from IPython.core.alias import Alias, AliasManager |
|
61 | from IPython.core.alias import Alias, AliasManager | |
62 | from IPython.core.autocall import ExitAutocall |
|
62 | from IPython.core.autocall import ExitAutocall | |
63 | from IPython.core.builtin_trap import BuiltinTrap |
|
63 | from IPython.core.builtin_trap import BuiltinTrap | |
64 |
from IPython.core.compilerop import CachingCompiler |
|
64 | from IPython.core.compilerop import CachingCompiler | |
65 | from IPython.core.debugger import InterruptiblePdb |
|
65 | from IPython.core.debugger import InterruptiblePdb | |
66 | from IPython.core.display_trap import DisplayTrap |
|
66 | from IPython.core.display_trap import DisplayTrap | |
67 | from IPython.core.displayhook import DisplayHook |
|
67 | from IPython.core.displayhook import DisplayHook | |
@@ -147,6 +147,19 b" dedent_re = re.compile(r'^\\s+raise|^\\s+return|^\\s+pass')" | |||||
147 | # Utilities |
|
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 | @undoc |
|
163 | @undoc | |
151 | def softspace(file, newvalue): |
|
164 | def softspace(file, newvalue): | |
152 | """Copied from code.py, to remove the dependency""" |
|
165 | """Copied from code.py, to remove the dependency""" | |
@@ -213,7 +226,9 b' class ExecutionInfo(object):' | |||||
213 | raw_cell = ( |
|
226 | raw_cell = ( | |
214 | (self.raw_cell[:50] + "..") if len(self.raw_cell) > 50 else self.raw_cell |
|
227 | (self.raw_cell[:50] + "..") if len(self.raw_cell) > 50 else self.raw_cell | |
215 | ) |
|
228 | ) | |
216 | return '<%s object at %x, raw_cell="%s" store_history=%s silent=%s shell_futures=%s cell_id=%s>' % ( |
|
229 | return ( | |
|
230 | '<%s object at %x, raw_cell="%s" store_history=%s silent=%s shell_futures=%s cell_id=%s>' | |||
|
231 | % ( | |||
217 | name, |
|
232 | name, | |
218 | id(self), |
|
233 | id(self), | |
219 | raw_cell, |
|
234 | raw_cell, | |
@@ -222,6 +237,7 b' class ExecutionInfo(object):' | |||||
222 | self.shell_futures, |
|
237 | self.shell_futures, | |
223 | self.cell_id, |
|
238 | self.cell_id, | |
224 | ) |
|
239 | ) | |
|
240 | ) | |||
225 |
|
241 | |||
226 |
|
242 | |||
227 | class ExecutionResult(object): |
|
243 | class ExecutionResult(object): | |
@@ -254,6 +270,16 b' class ExecutionResult(object):' | |||||
254 | return '<%s object at %x, execution_count=%s error_before_exec=%s error_in_exec=%s info=%s result=%s>' %\ |
|
270 | return '<%s object at %x, execution_count=%s error_before_exec=%s error_in_exec=%s info=%s result=%s>' %\ | |
255 | (name, id(self), self.execution_count, self.error_before_exec, self.error_in_exec, repr(self.info), repr(self.result)) |
|
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 | class InteractiveShell(SingletonConfigurable): |
|
284 | class InteractiveShell(SingletonConfigurable): | |
259 | """An enhanced, interactive shell for Python.""" |
|
285 | """An enhanced, interactive shell for Python.""" | |
@@ -1307,6 +1333,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
1307 |
|
1333 | |||
1308 | ns['exit'] = self.exiter |
|
1334 | ns['exit'] = self.exiter | |
1309 | ns['quit'] = self.exiter |
|
1335 | ns['quit'] = self.exiter | |
|
1336 | ns["open"] = _modified_open | |||
1310 |
|
1337 | |||
1311 | # Sync what we've added so far to user_ns_hidden so these aren't seen |
|
1338 | # Sync what we've added so far to user_ns_hidden so these aren't seen | |
1312 | # by %who |
|
1339 | # by %who | |
@@ -1537,10 +1564,33 b' class InteractiveShell(SingletonConfigurable):' | |||||
1537 | Has special code to detect magic functions. |
|
1564 | Has special code to detect magic functions. | |
1538 | """ |
|
1565 | """ | |
1539 | oname = oname.strip() |
|
1566 | oname = oname.strip() | |
1540 | if not oname.startswith(ESC_MAGIC) and \ |
|
1567 | raw_parts = oname.split(".") | |
1541 | not oname.startswith(ESC_MAGIC2) and \ |
|
1568 | parts = [] | |
1542 | not all(a.isidentifier() for a in oname.split(".")): |
|
1569 | parts_ok = True | |
1543 | return {'found': False} |
|
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 | if namespaces is None: |
|
1595 | if namespaces is None: | |
1546 | # Namespaces to search in: |
|
1596 | # Namespaces to search in: | |
@@ -1562,7 +1612,7 b' class InteractiveShell(SingletonConfigurable):' | |||||
1562 | # Look for the given name by splitting it in parts. If the head is |
|
1612 | # Look for the given name by splitting it in parts. If the head is | |
1563 | # found, then we look for all the remaining parts as members, and only |
|
1613 | # found, then we look for all the remaining parts as members, and only | |
1564 | # declare success if we can find them all. |
|
1614 | # declare success if we can find them all. | |
1565 |
oname_parts = |
|
1615 | oname_parts = parts | |
1566 | oname_head, oname_rest = oname_parts[0],oname_parts[1:] |
|
1616 | oname_head, oname_rest = oname_parts[0],oname_parts[1:] | |
1567 | for nsname,ns in namespaces: |
|
1617 | for nsname,ns in namespaces: | |
1568 | try: |
|
1618 | try: | |
@@ -1579,6 +1629,9 b' class InteractiveShell(SingletonConfigurable):' | |||||
1579 | if idx == len(oname_rest) - 1: |
|
1629 | if idx == len(oname_rest) - 1: | |
1580 | obj = self._getattr_property(obj, part) |
|
1630 | obj = self._getattr_property(obj, part) | |
1581 | else: |
|
1631 | else: | |
|
1632 | if is_integer_string(part): | |||
|
1633 | obj = obj[int(part)] | |||
|
1634 | else: | |||
1582 | obj = getattr(obj, part) |
|
1635 | obj = getattr(obj, part) | |
1583 | except: |
|
1636 | except: | |
1584 | # Blanket except b/c some badly implemented objects |
|
1637 | # Blanket except b/c some badly implemented objects | |
@@ -1643,6 +1696,9 b' class InteractiveShell(SingletonConfigurable):' | |||||
1643 | # |
|
1696 | # | |
1644 | # The universal alternative is to traverse the mro manually |
|
1697 | # The universal alternative is to traverse the mro manually | |
1645 | # searching for attrname in class dicts. |
|
1698 | # searching for attrname in class dicts. | |
|
1699 | if is_integer_string(attrname): | |||
|
1700 | return obj[int(attrname)] | |||
|
1701 | else: | |||
1646 | attr = getattr(type(obj), attrname) |
|
1702 | attr = getattr(type(obj), attrname) | |
1647 | except AttributeError: |
|
1703 | except AttributeError: | |
1648 | pass |
|
1704 | pass | |
@@ -1765,7 +1821,6 b' class InteractiveShell(SingletonConfigurable):' | |||||
1765 | self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain', |
|
1821 | self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain', | |
1766 | color_scheme='NoColor', |
|
1822 | color_scheme='NoColor', | |
1767 | tb_offset = 1, |
|
1823 | tb_offset = 1, | |
1768 | check_cache=check_linecache_ipython, |
|
|||
1769 | debugger_cls=self.debugger_cls, parent=self) |
|
1824 | debugger_cls=self.debugger_cls, parent=self) | |
1770 |
|
1825 | |||
1771 | # The instance will store a pointer to the system-wide exception hook, |
|
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 | oname = args and args or '_' |
|
297 | oname = args and args or '_' | |
298 | info = self.shell._ofind(oname) |
|
298 | info = self.shell._ofind(oname) | |
299 | if info['found']: |
|
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 | page.page(txt) |
|
304 | page.page(txt) | |
302 | else: |
|
305 | else: | |
303 | print('Object `%s` not found' % oname) |
|
306 | print('Object `%s` not found' % oname) |
@@ -80,6 +80,9 b' class ConfigMagics(Magics):' | |||||
80 | Enable debug for the Completer. Mostly print extra information for |
|
80 | Enable debug for the Completer. Mostly print extra information for | |
81 | experimental jedi integration. |
|
81 | experimental jedi integration. | |
82 | Current: False |
|
82 | Current: False | |
|
83 | IPCompleter.disable_matchers=<list-item-1>... | |||
|
84 | List of matchers to disable. | |||
|
85 | Current: [] | |||
83 | IPCompleter.greedy=<Bool> |
|
86 | IPCompleter.greedy=<Bool> | |
84 | Activate greedy completion |
|
87 | Activate greedy completion | |
85 | PENDING DEPRECATION. this is now mostly taken care of with Jedi. |
|
88 | PENDING DEPRECATION. this is now mostly taken care of with Jedi. | |
@@ -102,6 +105,8 b' class ConfigMagics(Magics):' | |||||
102 | Whether to merge completion results into a single list |
|
105 | Whether to merge completion results into a single list | |
103 | If False, only the completion results from the first non-empty |
|
106 | If False, only the completion results from the first non-empty | |
104 | completer will be returned. |
|
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 | Current: True |
|
110 | Current: True | |
106 | IPCompleter.omit__names=<Enum> |
|
111 | IPCompleter.omit__names=<Enum> | |
107 | Instruct the completer to omit private method names |
|
112 | Instruct the completer to omit private method names | |
@@ -117,6 +122,24 b' class ConfigMagics(Magics):' | |||||
117 | IPCompleter.profiler_output_dir=<Unicode> |
|
122 | IPCompleter.profiler_output_dir=<Unicode> | |
118 | Template for path at which to output profile data for completions. |
|
123 | Template for path at which to output profile data for completions. | |
119 | Current: '.completion_profiles' |
|
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 | IPCompleter.use_jedi=<Bool> |
|
143 | IPCompleter.use_jedi=<Bool> | |
121 | Experimental: Use Jedi to generate autocompletions. Default to True if jedi |
|
144 | Experimental: Use Jedi to generate autocompletions. Default to True if jedi | |
122 | is installed. |
|
145 | is installed. |
@@ -492,7 +492,7 b' class NamespaceMagics(Magics):' | |||||
492 | --aggressive |
|
492 | --aggressive | |
493 | Try to aggressively remove modules from sys.modules ; this |
|
493 | Try to aggressively remove modules from sys.modules ; this | |
494 | may allow you to reimport Python modules that have been updated and |
|
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 | in |
|
497 | in | |
498 | reset input history |
|
498 | reset input history |
@@ -26,6 +26,7 b' backends = {' | |||||
26 | "qt": "Qt5Agg", |
|
26 | "qt": "Qt5Agg", | |
27 | "osx": "MacOSX", |
|
27 | "osx": "MacOSX", | |
28 | "nbagg": "nbAgg", |
|
28 | "nbagg": "nbAgg", | |
|
29 | "webagg": "WebAgg", | |||
29 | "notebook": "nbAgg", |
|
30 | "notebook": "nbAgg", | |
30 | "agg": "agg", |
|
31 | "agg": "agg", | |
31 | "svg": "svg", |
|
32 | "svg": "svg", |
@@ -16,7 +16,7 b'' | |||||
16 | # release. 'dev' as a _version_extra string means this is a development |
|
16 | # release. 'dev' as a _version_extra string means this is a development | |
17 | # version |
|
17 | # version | |
18 | _version_major = 8 |
|
18 | _version_major = 8 | |
19 |
_version_minor = |
|
19 | _version_minor = 7 | |
20 | _version_patch = 0 |
|
20 | _version_patch = 0 | |
21 | _version_extra = ".dev" |
|
21 | _version_extra = ".dev" | |
22 | # _version_extra = "rc1" |
|
22 | # _version_extra = "rc1" | |
@@ -36,7 +36,7 b' version_info = (_version_major, _version_minor, _version_patch, _version_extra)' | |||||
36 | kernel_protocol_version_info = (5, 0) |
|
36 | kernel_protocol_version_info = (5, 0) | |
37 | kernel_protocol_version = "%i.%i" % kernel_protocol_version_info |
|
37 | kernel_protocol_version = "%i.%i" % kernel_protocol_version_info | |
38 |
|
38 | |||
39 |
license = |
|
39 | license = "BSD-3-Clause" | |
40 |
|
40 | |||
41 | authors = {'Fernando' : ('Fernando Perez','fperez.net@gmail.com'), |
|
41 | authors = {'Fernando' : ('Fernando Perez','fperez.net@gmail.com'), | |
42 | 'Janko' : ('Janko Hauser','jhauser@zscout.de'), |
|
42 | 'Janko' : ('Janko Hauser','jhauser@zscout.de'), |
@@ -1,4 +1,4 b'' | |||||
1 | # coding: iso-8859-5 |
|
1 | # coding: iso-8859-5 | |
2 | # (Unlikely to be the default encoding for most testers.) |
|
2 | # (Unlikely to be the default encoding for most testers.) | |
3 | # ������������������� <- Cyrillic characters |
|
3 | # ������������������� <- Cyrillic characters | |
4 |
u = |
|
4 | u = "����" |
@@ -24,6 +24,9 b' from IPython.core.completer import (' | |||||
24 | provisionalcompleter, |
|
24 | provisionalcompleter, | |
25 | match_dict_keys, |
|
25 | match_dict_keys, | |
26 | _deduplicate_completions, |
|
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 | ip.Completer.greedy = greedy_original |
|
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 | def test_protect_filename(): |
|
125 | def test_protect_filename(): | |
113 | if sys.platform == "win32": |
|
126 | if sys.platform == "win32": | |
114 | pairs = [ |
|
127 | pairs = [ | |
@@ -298,7 +311,7 b' class TestCompleter(unittest.TestCase):' | |||||
298 | ip = get_ipython() |
|
311 | ip = get_ipython() | |
299 |
|
312 | |||
300 | name, matches = ip.complete("\\â…¤") |
|
313 | name, matches = ip.complete("\\â…¤") | |
301 |
self.assertEqual(matches, |
|
314 | self.assertEqual(matches, ["\\ROMAN NUMERAL FIVE"]) | |
302 |
|
315 | |||
303 | def test_forward_unicode_completion(self): |
|
316 | def test_forward_unicode_completion(self): | |
304 | ip = get_ipython() |
|
317 | ip = get_ipython() | |
@@ -379,6 +392,12 b' class TestCompleter(unittest.TestCase):' | |||||
379 |
|
392 | |||
380 | def test_quoted_file_completions(self): |
|
393 | def test_quoted_file_completions(self): | |
381 | ip = get_ipython() |
|
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 | with TemporaryWorkingDirectory(): |
|
401 | with TemporaryWorkingDirectory(): | |
383 | name = "foo'bar" |
|
402 | name = "foo'bar" | |
384 | open(name, "w", encoding="utf-8").close() |
|
403 | open(name, "w", encoding="utf-8").close() | |
@@ -387,25 +406,16 b' class TestCompleter(unittest.TestCase):' | |||||
387 | escaped = name if sys.platform == "win32" else "foo\\'bar" |
|
406 | escaped = name if sys.platform == "win32" else "foo\\'bar" | |
388 |
|
407 | |||
389 | # Single quote matches embedded single quote |
|
408 | # Single quote matches embedded single quote | |
390 |
|
|
409 | c = _("open('foo")[0] | |
391 | c = ip.Completer._complete( |
|
410 | self.assertEqual(c.text, escaped) | |
392 | cursor_line=0, cursor_pos=len(text), full_text=text |
|
|||
393 | )[1] |
|
|||
394 | self.assertEqual(c, [escaped]) |
|
|||
395 |
|
411 | |||
396 | # Double quote requires no escape |
|
412 | # Double quote requires no escape | |
397 |
|
|
413 | c = _('open("foo')[0] | |
398 | c = ip.Completer._complete( |
|
414 | self.assertEqual(c.text, name) | |
399 | cursor_line=0, cursor_pos=len(text), full_text=text |
|
|||
400 | )[1] |
|
|||
401 | self.assertEqual(c, [name]) |
|
|||
402 |
|
415 | |||
403 | # No quote requires an escape |
|
416 | # No quote requires an escape | |
404 |
|
|
417 | c = _("%ls foo")[0] | |
405 | c = ip.Completer._complete( |
|
418 | self.assertEqual(c.text, escaped) | |
406 | cursor_line=0, cursor_pos=len(text), full_text=text |
|
|||
407 | )[1] |
|
|||
408 | self.assertEqual(c, [escaped]) |
|
|||
409 |
|
419 | |||
410 | def test_all_completions_dups(self): |
|
420 | def test_all_completions_dups(self): | |
411 | """ |
|
421 | """ | |
@@ -475,6 +485,17 b' class TestCompleter(unittest.TestCase):' | |||||
475 | "encoding" in c.signature |
|
485 | "encoding" in c.signature | |
476 | ), "Signature of function was not found by completer" |
|
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 | @pytest.mark.xfail(reason="Known failure on jedi<=0.18.0") |
|
499 | @pytest.mark.xfail(reason="Known failure on jedi<=0.18.0") | |
479 | def test_deduplicate_completions(self): |
|
500 | def test_deduplicate_completions(self): | |
480 | """ |
|
501 | """ | |
@@ -1273,3 +1294,153 b' class TestCompleter(unittest.TestCase):' | |||||
1273 | completions = completer.completions(text, len(text)) |
|
1294 | completions = completer.completions(text, len(text)) | |
1274 | for c in completions: |
|
1295 | for c in completions: | |
1275 | self.assertEqual(c.text[0], "%") |
|
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 | res = ip.run_cell("raise = 3") |
|
103 | res = ip.run_cell("raise = 3") | |
104 | self.assertIsInstance(res.error_before_exec, SyntaxError) |
|
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 | def test_In_variable(self): |
|
118 | def test_In_variable(self): | |
107 | "Verify that In variable grows with user input (GH-284)" |
|
119 | "Verify that In variable grows with user input (GH-284)" | |
108 | oldlen = len(ip.user_ns['In']) |
|
120 | oldlen = len(ip.user_ns['In']) |
@@ -1,15 +1,11 b'' | |||||
1 | """Tests for the key interactiveshell module, where the main ipython class is defined. |
|
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 | def test_reset(): |
|
10 | def test_reset(): | |
15 | """reset must clear most namespaces.""" |
|
11 | """reset must clear most namespaces.""" | |
@@ -170,7 +166,10 b' def doctest_tb_sysexit():' | |||||
170 | """ |
|
166 | """ | |
171 |
|
167 | |||
172 |
|
168 | |||
173 | def doctest_tb_sysexit_verbose(): |
|
169 | if sys.version_info >= (3, 9): | |
|
170 | if SV_VERSION < (0, 6): | |||
|
171 | ||||
|
172 | def doctest_tb_sysexit_verbose_stack_data_05(): | |||
174 | """ |
|
173 | """ | |
175 | In [18]: %run simpleerr.py exit |
|
174 | In [18]: %run simpleerr.py exit | |
176 | An exception has occurred, use %tb to see the full traceback. |
|
175 | An exception has occurred, use %tb to see the full traceback. | |
@@ -210,6 +209,50 b' def doctest_tb_sysexit_verbose():' | |||||
210 | SystemExit: (2, 'Mode = exit') |
|
209 | SystemExit: (2, 'Mode = exit') | |
211 | """ |
|
210 | """ | |
212 |
|
211 | |||
|
212 | else: | |||
|
213 | # currently the only difference is | |||
|
214 | # + mode = 'exit' | |||
|
215 | ||||
|
216 | def doctest_tb_sysexit_verbose_stack_data_06(): | |||
|
217 | """ | |||
|
218 | In [18]: %run simpleerr.py exit | |||
|
219 | An exception has occurred, use %tb to see the full traceback. | |||
|
220 | SystemExit: (1, 'Mode = exit') | |||
|
221 | ||||
|
222 | In [19]: %run simpleerr.py exit 2 | |||
|
223 | An exception has occurred, use %tb to see the full traceback. | |||
|
224 | SystemExit: (2, 'Mode = exit') | |||
|
225 | ||||
|
226 | In [23]: %xmode verbose | |||
|
227 | Exception reporting mode: Verbose | |||
|
228 | ||||
|
229 | In [24]: %tb | |||
|
230 | --------------------------------------------------------------------------- | |||
|
231 | SystemExit Traceback (most recent call last) | |||
|
232 | <BLANKLINE> | |||
|
233 | ... | |||
|
234 | 30 except IndexError: | |||
|
235 | 31 mode = 'div' | |||
|
236 | ---> 33 bar(mode) | |||
|
237 | mode = 'exit' | |||
|
238 | <BLANKLINE> | |||
|
239 | ... in bar(mode='exit') | |||
|
240 | ... except: | |||
|
241 | ... stat = 1 | |||
|
242 | ---> ... sysexit(stat, mode) | |||
|
243 | mode = 'exit' | |||
|
244 | stat = 2 | |||
|
245 | ... else: | |||
|
246 | ... raise ValueError('Unknown mode') | |||
|
247 | <BLANKLINE> | |||
|
248 | ... in sysexit(stat=2, mode='exit') | |||
|
249 | 10 def sysexit(stat, mode): | |||
|
250 | ---> 11 raise SystemExit(stat, f"Mode = {mode}") | |||
|
251 | stat = 2 | |||
|
252 | mode = 'exit' | |||
|
253 | <BLANKLINE> | |||
|
254 | SystemExit: (2, 'Mode = exit') | |||
|
255 | """ | |||
213 |
|
256 | |||
214 | def test_run_cell(): |
|
257 | def test_run_cell(): | |
215 | import textwrap |
|
258 | import textwrap |
@@ -84,7 +84,7 b' def test_extract_symbols_raises_exception_with_non_python_code():' | |||||
84 | def test_magic_not_found(): |
|
84 | def test_magic_not_found(): | |
85 | # magic not found raises UsageError |
|
85 | # magic not found raises UsageError | |
86 | with pytest.raises(UsageError): |
|
86 | with pytest.raises(UsageError): | |
87 |
_ip.magic( |
|
87 | _ip.run_line_magic("doesntexist", "") | |
88 |
|
88 | |||
89 | # ensure result isn't success when a magic isn't found |
|
89 | # ensure result isn't success when a magic isn't found | |
90 | result = _ip.run_cell('%doesntexist') |
|
90 | result = _ip.run_cell('%doesntexist') | |
@@ -116,13 +116,14 b' def test_config():' | |||||
116 | magic. |
|
116 | magic. | |
117 | """ |
|
117 | """ | |
118 | ## should not raise. |
|
118 | ## should not raise. | |
119 |
_ip.magic( |
|
119 | _ip.run_line_magic("config", "") | |
|
120 | ||||
120 |
|
121 | |||
121 | def test_config_available_configs(): |
|
122 | def test_config_available_configs(): | |
122 | """ test that config magic prints available configs in unique and |
|
123 | """ test that config magic prints available configs in unique and | |
123 | sorted order. """ |
|
124 | sorted order. """ | |
124 | with capture_output() as captured: |
|
125 | with capture_output() as captured: | |
125 |
_ip.magic( |
|
126 | _ip.run_line_magic("config", "") | |
126 |
|
127 | |||
127 | stdout = captured.stdout |
|
128 | stdout = captured.stdout | |
128 | config_classes = stdout.strip().split('\n')[1:] |
|
129 | config_classes = stdout.strip().split('\n')[1:] | |
@@ -131,7 +132,7 b' def test_config_available_configs():' | |||||
131 | def test_config_print_class(): |
|
132 | def test_config_print_class(): | |
132 | """ test that config with a classname prints the class's options. """ |
|
133 | """ test that config with a classname prints the class's options. """ | |
133 | with capture_output() as captured: |
|
134 | with capture_output() as captured: | |
134 |
_ip.magic( |
|
135 | _ip.run_line_magic("config", "TerminalInteractiveShell") | |
135 |
|
136 | |||
136 | stdout = captured.stdout |
|
137 | stdout = captured.stdout | |
137 | assert re.match( |
|
138 | assert re.match( | |
@@ -144,7 +145,7 b' def test_rehashx():' | |||||
144 | _ip.alias_manager.clear_aliases() |
|
145 | _ip.alias_manager.clear_aliases() | |
145 | del _ip.db['syscmdlist'] |
|
146 | del _ip.db['syscmdlist'] | |
146 |
|
147 | |||
147 |
_ip.magic( |
|
148 | _ip.run_line_magic("rehashx", "") | |
148 | # Practically ALL ipython development systems will have more than 10 aliases |
|
149 | # Practically ALL ipython development systems will have more than 10 aliases | |
149 |
|
150 | |||
150 | assert len(_ip.alias_manager.aliases) > 10 |
|
151 | assert len(_ip.alias_manager.aliases) > 10 | |
@@ -277,11 +278,11 b' def test_macro():' | |||||
277 | cmds = ["a=1", "def b():\n return a**2", "print(a,b())"] |
|
278 | cmds = ["a=1", "def b():\n return a**2", "print(a,b())"] | |
278 | for i, cmd in enumerate(cmds, start=1): |
|
279 | for i, cmd in enumerate(cmds, start=1): | |
279 | ip.history_manager.store_inputs(i, cmd) |
|
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 | assert ip.user_ns["test"].value == "\n".join(cmds) + "\n" |
|
282 | assert ip.user_ns["test"].value == "\n".join(cmds) + "\n" | |
282 |
|
283 | |||
283 | # List macros |
|
284 | # List macros | |
284 | assert "test" in ip.magic("macro") |
|
285 | assert "test" in ip.run_line_magic("macro", "") | |
285 |
|
286 | |||
286 |
|
287 | |||
287 | def test_macro_run(): |
|
288 | def test_macro_run(): | |
@@ -302,7 +303,7 b' def test_magic_magic():' | |||||
302 | """Test %magic""" |
|
303 | """Test %magic""" | |
303 | ip = get_ipython() |
|
304 | ip = get_ipython() | |
304 | with capture_output() as captured: |
|
305 | with capture_output() as captured: | |
305 | ip.magic("magic") |
|
306 | ip.run_line_magic("magic", "") | |
306 |
|
307 | |||
307 | stdout = captured.stdout |
|
308 | stdout = captured.stdout | |
308 | assert "%magic" in stdout |
|
309 | assert "%magic" in stdout | |
@@ -316,7 +317,7 b' def test_numpy_reset_array_undec():' | |||||
316 | _ip.ex("import numpy as np") |
|
317 | _ip.ex("import numpy as np") | |
317 | _ip.ex("a = np.empty(2)") |
|
318 | _ip.ex("a = np.empty(2)") | |
318 | assert "a" in _ip.user_ns |
|
319 | assert "a" in _ip.user_ns | |
319 | _ip.magic("reset -f array") |
|
320 | _ip.run_line_magic("reset", "-f array") | |
320 | assert "a" not in _ip.user_ns |
|
321 | assert "a" not in _ip.user_ns | |
321 |
|
322 | |||
322 |
|
323 | |||
@@ -326,7 +327,7 b' def test_reset_out():' | |||||
326 | # test '%reset -f out', make an Out prompt |
|
327 | # test '%reset -f out', make an Out prompt | |
327 | _ip.run_cell("parrot", store_history=True) |
|
328 | _ip.run_cell("parrot", store_history=True) | |
328 | assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")] |
|
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 | assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")] |
|
331 | assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")] | |
331 | assert len(_ip.user_ns["Out"]) == 0 |
|
332 | assert len(_ip.user_ns["Out"]) == 0 | |
332 |
|
333 | |||
@@ -336,7 +337,7 b' def test_reset_in():' | |||||
336 | # test '%reset -f in' |
|
337 | # test '%reset -f in' | |
337 | _ip.run_cell("parrot", store_history=True) |
|
338 | _ip.run_cell("parrot", store_history=True) | |
338 | assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")] |
|
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 | assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")] |
|
341 | assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")] | |
341 | assert len(set(_ip.user_ns["In"])) == 1 |
|
342 | assert len(set(_ip.user_ns["In"])) == 1 | |
342 |
|
343 | |||
@@ -344,10 +345,10 b' def test_reset_in():' | |||||
344 | def test_reset_dhist(): |
|
345 | def test_reset_dhist(): | |
345 | "Test '%reset dhist' magic" |
|
346 | "Test '%reset dhist' magic" | |
346 | _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing |
|
347 | _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing | |
347 |
_ip.magic("cd |
|
348 | _ip.run_line_magic("cd", os.path.dirname(pytest.__file__)) | |
348 | _ip.magic("cd -") |
|
349 | _ip.run_line_magic("cd", "-") | |
349 | assert len(_ip.user_ns["_dh"]) > 0 |
|
350 | assert len(_ip.user_ns["_dh"]) > 0 | |
350 | _ip.magic("reset -f dhist") |
|
351 | _ip.run_line_magic("reset", "-f dhist") | |
351 | assert len(_ip.user_ns["_dh"]) == 0 |
|
352 | assert len(_ip.user_ns["_dh"]) == 0 | |
352 | _ip.run_cell("_dh = [d for d in tmp]") # restore |
|
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 | def test_doctest_mode(): |
|
474 | def test_doctest_mode(): | |
474 | "Toggle doctest_mode twice, it should be a no-op and run without error" |
|
475 | "Toggle doctest_mode twice, it should be a no-op and run without error" | |
475 |
_ip.magic( |
|
476 | _ip.run_line_magic("doctest_mode", "") | |
476 |
_ip.magic( |
|
477 | _ip.run_line_magic("doctest_mode", "") | |
477 |
|
478 | |||
478 |
|
479 | |||
479 | def test_parse_options(): |
|
480 | def test_parse_options(): | |
@@ -498,7 +499,9 b' def test_parse_options_preserve_non_option_string():' | |||||
498 | def test_run_magic_preserve_code_block(): |
|
499 | def test_run_magic_preserve_code_block(): | |
499 | """Test to assert preservation of non-option part of magic-block, while running magic.""" |
|
500 | """Test to assert preservation of non-option part of magic-block, while running magic.""" | |
500 | _ip.user_ns["spaces"] = [] |
|
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 | assert _ip.user_ns["spaces"] == [[0]] |
|
505 | assert _ip.user_ns["spaces"] == [[0]] | |
503 |
|
506 | |||
504 |
|
507 | |||
@@ -509,13 +512,13 b' def test_dirops():' | |||||
509 | startdir = os.getcwd() |
|
512 | startdir = os.getcwd() | |
510 | ipdir = os.path.realpath(_ip.ipython_dir) |
|
513 | ipdir = os.path.realpath(_ip.ipython_dir) | |
511 | try: |
|
514 | try: | |
512 |
_ip.magic(' |
|
515 | _ip.run_line_magic("cd", '"%s"' % ipdir) | |
513 | assert curpath() == ipdir |
|
516 | assert curpath() == ipdir | |
514 |
_ip.magic( |
|
517 | _ip.run_line_magic("cd", "-") | |
515 | assert curpath() == startdir |
|
518 | assert curpath() == startdir | |
516 |
_ip.magic(' |
|
519 | _ip.run_line_magic("pushd", '"%s"' % ipdir) | |
517 | assert curpath() == ipdir |
|
520 | assert curpath() == ipdir | |
518 |
_ip.magic( |
|
521 | _ip.run_line_magic("popd", "") | |
519 | assert curpath() == startdir |
|
522 | assert curpath() == startdir | |
520 | finally: |
|
523 | finally: | |
521 | os.chdir(startdir) |
|
524 | os.chdir(startdir) | |
@@ -542,7 +545,7 b' def test_xmode():' | |||||
542 | # Calling xmode three times should be a no-op |
|
545 | # Calling xmode three times should be a no-op | |
543 | xmode = _ip.InteractiveTB.mode |
|
546 | xmode = _ip.InteractiveTB.mode | |
544 | for i in range(4): |
|
547 | for i in range(4): | |
545 | _ip.magic("xmode") |
|
548 | _ip.run_line_magic("xmode", "") | |
546 | assert _ip.InteractiveTB.mode == xmode |
|
549 | assert _ip.InteractiveTB.mode == xmode | |
547 |
|
550 | |||
548 | def test_reset_hard(): |
|
551 | def test_reset_hard(): | |
@@ -557,7 +560,7 b' def test_reset_hard():' | |||||
557 | _ip.run_cell("a") |
|
560 | _ip.run_cell("a") | |
558 |
|
561 | |||
559 | assert monitor == [] |
|
562 | assert monitor == [] | |
560 | _ip.magic("reset -f") |
|
563 | _ip.run_line_magic("reset", "-f") | |
561 | assert monitor == [1] |
|
564 | assert monitor == [1] | |
562 |
|
565 | |||
563 | class TestXdel(tt.TempFileMixin): |
|
566 | class TestXdel(tt.TempFileMixin): | |
@@ -570,14 +573,14 b' class TestXdel(tt.TempFileMixin):' | |||||
570 | "a = A()\n") |
|
573 | "a = A()\n") | |
571 | self.mktmp(src) |
|
574 | self.mktmp(src) | |
572 | # %run creates some hidden references... |
|
575 | # %run creates some hidden references... | |
573 | _ip.magic("run %s" % self.fname) |
|
576 | _ip.run_line_magic("run", "%s" % self.fname) | |
574 | # ... as does the displayhook. |
|
577 | # ... as does the displayhook. | |
575 | _ip.run_cell("a") |
|
578 | _ip.run_cell("a") | |
576 |
|
579 | |||
577 | monitor = _ip.user_ns["A"].monitor |
|
580 | monitor = _ip.user_ns["A"].monitor | |
578 | assert monitor == [] |
|
581 | assert monitor == [] | |
579 |
|
582 | |||
580 | _ip.magic("xdel a") |
|
583 | _ip.run_line_magic("xdel", "a") | |
581 |
|
584 | |||
582 | # Check that a's __del__ method has been called. |
|
585 | # Check that a's __del__ method has been called. | |
583 | gc.collect(0) |
|
586 | gc.collect(0) | |
@@ -614,7 +617,7 b' def test_whos():' | |||||
614 | def __repr__(self): |
|
617 | def __repr__(self): | |
615 | raise Exception() |
|
618 | raise Exception() | |
616 | _ip.user_ns['a'] = A() |
|
619 | _ip.user_ns['a'] = A() | |
617 | _ip.magic("whos") |
|
620 | _ip.run_line_magic("whos", "") | |
618 |
|
621 | |||
619 | def doctest_precision(): |
|
622 | def doctest_precision(): | |
620 | """doctest for %precision |
|
623 | """doctest for %precision | |
@@ -655,12 +658,12 b' def test_psearch():' | |||||
655 | def test_timeit_shlex(): |
|
658 | def test_timeit_shlex(): | |
656 | """test shlex issues with timeit (#1109)""" |
|
659 | """test shlex issues with timeit (#1109)""" | |
657 | _ip.ex("def f(*a,**kw): pass") |
|
660 | _ip.ex("def f(*a,**kw): pass") | |
658 |
_ip.magic(' |
|
661 | _ip.run_line_magic("timeit", '-n1 "this is a bug".count(" ")') | |
659 |
_ip.magic(' |
|
662 | _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1)') | |
660 |
_ip.magic(' |
|
663 | _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1, " ", 2, " ")') | |
661 |
_ip.magic(' |
|
664 | _ip.run_line_magic("timeit", '-r1 -n1 ("a " + "b")') | |
662 |
_ip.magic(' |
|
665 | _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b")') | |
663 |
_ip.magic(' |
|
666 | _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b ")') | |
664 |
|
667 | |||
665 |
|
668 | |||
666 | def test_timeit_special_syntax(): |
|
669 | def test_timeit_special_syntax(): | |
@@ -738,9 +741,9 b' def test_extension():' | |||||
738 | try: |
|
741 | try: | |
739 | _ip.user_ns.pop('arq', None) |
|
742 | _ip.user_ns.pop('arq', None) | |
740 | invalidate_caches() # Clear import caches |
|
743 | invalidate_caches() # Clear import caches | |
741 | _ip.magic("load_ext daft_extension") |
|
744 | _ip.run_line_magic("load_ext", "daft_extension") | |
742 | assert _ip.user_ns["arq"] == 185 |
|
745 | assert _ip.user_ns["arq"] == 185 | |
743 | _ip.magic("unload_ext daft_extension") |
|
746 | _ip.run_line_magic("unload_ext", "daft_extension") | |
744 | assert 'arq' not in _ip.user_ns |
|
747 | assert 'arq' not in _ip.user_ns | |
745 | finally: |
|
748 | finally: | |
746 | sys.path.remove(daft_path) |
|
749 | sys.path.remove(daft_path) | |
@@ -755,17 +758,17 b' def test_notebook_export_json():' | |||||
755 | _ip.history_manager.store_inputs(i, cmd) |
|
758 | _ip.history_manager.store_inputs(i, cmd) | |
756 | with TemporaryDirectory() as td: |
|
759 | with TemporaryDirectory() as td: | |
757 | outfile = os.path.join(td, "nb.ipynb") |
|
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 | class TestEnv(TestCase): |
|
764 | class TestEnv(TestCase): | |
762 |
|
765 | |||
763 | def test_env(self): |
|
766 | def test_env(self): | |
764 | env = _ip.magic("env") |
|
767 | env = _ip.run_line_magic("env", "") | |
765 | self.assertTrue(isinstance(env, dict)) |
|
768 | self.assertTrue(isinstance(env, dict)) | |
766 |
|
769 | |||
767 | def test_env_secret(self): |
|
770 | def test_env_secret(self): | |
768 | env = _ip.magic("env") |
|
771 | env = _ip.run_line_magic("env", "") | |
769 | hidden = "<hidden>" |
|
772 | hidden = "<hidden>" | |
770 | with mock.patch.dict( |
|
773 | with mock.patch.dict( | |
771 | os.environ, |
|
774 | os.environ, | |
@@ -776,35 +779,35 b' class TestEnv(TestCase):' | |||||
776 | "VAR": "abc" |
|
779 | "VAR": "abc" | |
777 | } |
|
780 | } | |
778 | ): |
|
781 | ): | |
779 | env = _ip.magic("env") |
|
782 | env = _ip.run_line_magic("env", "") | |
780 | assert env["API_KEY"] == hidden |
|
783 | assert env["API_KEY"] == hidden | |
781 | assert env["SECRET_THING"] == hidden |
|
784 | assert env["SECRET_THING"] == hidden | |
782 | assert env["JUPYTER_TOKEN"] == hidden |
|
785 | assert env["JUPYTER_TOKEN"] == hidden | |
783 | assert env["VAR"] == "abc" |
|
786 | assert env["VAR"] == "abc" | |
784 |
|
787 | |||
785 | def test_env_get_set_simple(self): |
|
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 | self.assertEqual(env, None) |
|
790 | self.assertEqual(env, None) | |
788 |
self.assertEqual(os.environ[ |
|
791 | self.assertEqual(os.environ["var"], "val1") | |
789 |
self.assertEqual(_ip.magic("env var"), |
|
792 | self.assertEqual(_ip.run_line_magic("env", "var"), "val1") | |
790 | env = _ip.magic("env var=val2") |
|
793 | env = _ip.run_line_magic("env", "var=val2") | |
791 | self.assertEqual(env, None) |
|
794 | self.assertEqual(env, None) | |
792 | self.assertEqual(os.environ['var'], 'val2') |
|
795 | self.assertEqual(os.environ['var'], 'val2') | |
793 |
|
796 | |||
794 | def test_env_get_set_complex(self): |
|
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 | self.assertEqual(env, None) |
|
799 | self.assertEqual(env, None) | |
797 | self.assertEqual(os.environ['var'], "'val1 '' 'val2") |
|
800 | self.assertEqual(os.environ['var'], "'val1 '' 'val2") | |
798 | self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2") |
|
801 | self.assertEqual(_ip.run_line_magic("env", "var"), "'val1 '' 'val2") | |
799 |
env = _ip.magic(' |
|
802 | env = _ip.run_line_magic("env", 'var=val2 val3="val4') | |
800 | self.assertEqual(env, None) |
|
803 | self.assertEqual(env, None) | |
801 | self.assertEqual(os.environ['var'], 'val2 val3="val4') |
|
804 | self.assertEqual(os.environ['var'], 'val2 val3="val4') | |
802 |
|
805 | |||
803 | def test_env_set_bad_input(self): |
|
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 | def test_env_set_whitespace(self): |
|
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 | class CellMagicTestCase(TestCase): |
|
813 | class CellMagicTestCase(TestCase): | |
@@ -1308,7 +1311,7 b' def test_ls_magic():' | |||||
1308 | ip = get_ipython() |
|
1311 | ip = get_ipython() | |
1309 | json_formatter = ip.display_formatter.formatters['application/json'] |
|
1312 | json_formatter = ip.display_formatter.formatters['application/json'] | |
1310 | json_formatter.enabled = True |
|
1313 | json_formatter.enabled = True | |
1311 |
lsmagic = ip.magic( |
|
1314 | lsmagic = ip.run_line_magic("lsmagic", "") | |
1312 | with warnings.catch_warnings(record=True) as w: |
|
1315 | with warnings.catch_warnings(record=True) as w: | |
1313 | j = json_formatter(lsmagic) |
|
1316 | j = json_formatter(lsmagic) | |
1314 | assert sorted(j) == ["cell", "line"] |
|
1317 | assert sorted(j) == ["cell", "line"] | |
@@ -1358,16 +1361,16 b' def test_logging_magic_not_quiet():' | |||||
1358 |
|
1361 | |||
1359 |
|
1362 | |||
1360 | def test_time_no_var_expand(): |
|
1363 | def test_time_no_var_expand(): | |
1361 |
_ip.user_ns[ |
|
1364 | _ip.user_ns["a"] = 5 | |
1362 |
_ip.user_ns[ |
|
1365 | _ip.user_ns["b"] = [] | |
1363 |
_ip.magic(' |
|
1366 | _ip.run_line_magic("time", 'b.append("{a}")') | |
1364 |
assert _ip.user_ns[ |
|
1367 | assert _ip.user_ns["b"] == ["{a}"] | |
1365 |
|
1368 | |||
1366 |
|
1369 | |||
1367 | # this is slow, put at the end for local testing. |
|
1370 | # this is slow, put at the end for local testing. | |
1368 | def test_timeit_arguments(): |
|
1371 | def test_timeit_arguments(): | |
1369 | "Test valid timeit arguments, should not cause SyntaxError (GH #1269)" |
|
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 | MINIMAL_LAZY_MAGIC = """ |
|
1376 | MINIMAL_LAZY_MAGIC = """ | |
@@ -1442,7 +1445,7 b' def test_run_module_from_import_hook():' | |||||
1442 | sys.meta_path.insert(0, MyTempImporter()) |
|
1445 | sys.meta_path.insert(0, MyTempImporter()) | |
1443 |
|
1446 | |||
1444 | with capture_output() as captured: |
|
1447 | with capture_output() as captured: | |
1445 | _ip.magic("run -m my_tmp") |
|
1448 | _ip.run_line_magic("run", "-m my_tmp") | |
1446 | _ip.run_cell("import my_tmp") |
|
1449 | _ip.run_cell("import my_tmp") | |
1447 |
|
1450 | |||
1448 | output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n" |
|
1451 | output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n" |
@@ -5,6 +5,7 b'' | |||||
5 | # Distributed under the terms of the Modified BSD License. |
|
5 | # Distributed under the terms of the Modified BSD License. | |
6 |
|
6 | |||
7 |
|
7 | |||
|
8 | from contextlib import contextmanager | |||
8 | from inspect import signature, Signature, Parameter |
|
9 | from inspect import signature, Signature, Parameter | |
9 | import inspect |
|
10 | import inspect | |
10 | import os |
|
11 | import os | |
@@ -43,7 +44,7 b' class SourceModuleMainTest:' | |||||
43 | # defined, if any code is inserted above, the following line will need to be |
|
44 | # defined, if any code is inserted above, the following line will need to be | |
44 | # updated. Do NOT insert any whitespace between the next line and the function |
|
45 | # updated. Do NOT insert any whitespace between the next line and the function | |
45 | # definition below. |
|
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 | def test_find_source_lines(): |
|
50 | def test_find_source_lines(): | |
@@ -345,6 +346,70 b' def test_pdef():' | |||||
345 | inspector.pdef(foo, 'foo') |
|
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 | def test_pinfo_nonascii(): |
|
413 | def test_pinfo_nonascii(): | |
349 | # See gh-1177 |
|
414 | # See gh-1177 | |
350 | from . import nonascii2 |
|
415 | from . import nonascii2 |
@@ -180,13 +180,13 b' class TestMagicRunPass(tt.TempFileMixin):' | |||||
180 | _ip = get_ipython() |
|
180 | _ip = get_ipython() | |
181 | # This fails on Windows if self.tmpfile.name has spaces or "~" in it. |
|
181 | # This fails on Windows if self.tmpfile.name has spaces or "~" in it. | |
182 | # See below and ticket https://bugs.launchpad.net/bugs/366353 |
|
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 | def run_tmpfile_p(self): |
|
185 | def run_tmpfile_p(self): | |
186 | _ip = get_ipython() |
|
186 | _ip = get_ipython() | |
187 | # This fails on Windows if self.tmpfile.name has spaces or "~" in it. |
|
187 | # This fails on Windows if self.tmpfile.name has spaces or "~" in it. | |
188 | # See below and ticket https://bugs.launchpad.net/bugs/366353 |
|
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 | def test_builtins_id(self): |
|
191 | def test_builtins_id(self): | |
192 | """Check that %run doesn't damage __builtins__ """ |
|
192 | """Check that %run doesn't damage __builtins__ """ | |
@@ -216,20 +216,20 b' class TestMagicRunPass(tt.TempFileMixin):' | |||||
216 | def test_run_debug_twice(self): |
|
216 | def test_run_debug_twice(self): | |
217 | # https://github.com/ipython/ipython/issues/10028 |
|
217 | # https://github.com/ipython/ipython/issues/10028 | |
218 | _ip = get_ipython() |
|
218 | _ip = get_ipython() | |
219 |
with tt.fake_input([ |
|
219 | with tt.fake_input(["c"]): | |
220 |
_ip.magic( |
|
220 | _ip.run_line_magic("run", "-d %s" % self.fname) | |
221 |
with tt.fake_input([ |
|
221 | with tt.fake_input(["c"]): | |
222 |
_ip.magic( |
|
222 | _ip.run_line_magic("run", "-d %s" % self.fname) | |
223 |
|
223 | |||
224 | def test_run_debug_twice_with_breakpoint(self): |
|
224 | def test_run_debug_twice_with_breakpoint(self): | |
225 | """Make a valid python temp file.""" |
|
225 | """Make a valid python temp file.""" | |
226 | _ip = get_ipython() |
|
226 | _ip = get_ipython() | |
227 |
with tt.fake_input([ |
|
227 | with tt.fake_input(["b 2", "c", "c"]): | |
228 |
_ip.magic( |
|
228 | _ip.run_line_magic("run", "-d %s" % self.fname) | |
229 |
|
229 | |||
230 |
with tt.fake_input([ |
|
230 | with tt.fake_input(["c"]): | |
231 |
with tt.AssertNotPrints( |
|
231 | with tt.AssertNotPrints("KeyError"): | |
232 |
_ip.magic( |
|
232 | _ip.run_line_magic("run", "-d %s" % self.fname) | |
233 |
|
233 | |||
234 |
|
234 | |||
235 | class TestMagicRunSimple(tt.TempFileMixin): |
|
235 | class TestMagicRunSimple(tt.TempFileMixin): | |
@@ -239,7 +239,7 b' class TestMagicRunSimple(tt.TempFileMixin):' | |||||
239 | src = ("class foo: pass\n" |
|
239 | src = ("class foo: pass\n" | |
240 | "def f(): return foo()") |
|
240 | "def f(): return foo()") | |
241 | self.mktmp(src) |
|
241 | self.mktmp(src) | |
242 |
_ip.magic("run |
|
242 | _ip.run_line_magic("run", str(self.fname)) | |
243 | _ip.run_cell("t = isinstance(f(), foo)") |
|
243 | _ip.run_cell("t = isinstance(f(), foo)") | |
244 | assert _ip.user_ns["t"] is True |
|
244 | assert _ip.user_ns["t"] is True | |
245 |
|
245 | |||
@@ -277,7 +277,7 b' class TestMagicRunSimple(tt.TempFileMixin):' | |||||
277 | " break\n" % ("run " + empty.fname) |
|
277 | " break\n" % ("run " + empty.fname) | |
278 | ) |
|
278 | ) | |
279 | self.mktmp(src) |
|
279 | self.mktmp(src) | |
280 |
_ip.magic("run |
|
280 | _ip.run_line_magic("run", str(self.fname)) | |
281 | _ip.run_cell("ip == get_ipython()") |
|
281 | _ip.run_cell("ip == get_ipython()") | |
282 | assert _ip.user_ns["i"] == 4 |
|
282 | assert _ip.user_ns["i"] == 4 | |
283 |
|
283 | |||
@@ -288,8 +288,8 b' class TestMagicRunSimple(tt.TempFileMixin):' | |||||
288 | with tt.TempFileMixin() as empty: |
|
288 | with tt.TempFileMixin() as empty: | |
289 | empty.mktmp("") |
|
289 | empty.mktmp("") | |
290 |
|
290 | |||
291 |
_ip.magic("run |
|
291 | _ip.run_line_magic("run", self.fname) | |
292 |
_ip.magic("run |
|
292 | _ip.run_line_magic("run", empty.fname) | |
293 | assert _ip.user_ns["afunc"]() == 1 |
|
293 | assert _ip.user_ns["afunc"]() == 1 | |
294 |
|
294 | |||
295 | def test_tclass(self): |
|
295 | def test_tclass(self): | |
@@ -323,22 +323,22 b' tclass.py: deleting object: C-third' | |||||
323 | self.mktmp(src) |
|
323 | self.mktmp(src) | |
324 | _ip.run_cell("zz = 23") |
|
324 | _ip.run_cell("zz = 23") | |
325 | try: |
|
325 | try: | |
326 | _ip.magic("run -i %s" % self.fname) |
|
326 | _ip.run_line_magic("run", "-i %s" % self.fname) | |
327 | assert _ip.user_ns["yy"] == 23 |
|
327 | assert _ip.user_ns["yy"] == 23 | |
328 | finally: |
|
328 | finally: | |
329 |
_ip.magic( |
|
329 | _ip.run_line_magic("reset", "-f") | |
330 |
|
330 | |||
331 | _ip.run_cell("zz = 23") |
|
331 | _ip.run_cell("zz = 23") | |
332 | try: |
|
332 | try: | |
333 | _ip.magic("run -i %s" % self.fname) |
|
333 | _ip.run_line_magic("run", "-i %s" % self.fname) | |
334 | assert _ip.user_ns["yy"] == 23 |
|
334 | assert _ip.user_ns["yy"] == 23 | |
335 | finally: |
|
335 | finally: | |
336 |
_ip.magic( |
|
336 | _ip.run_line_magic("reset", "-f") | |
337 |
|
337 | |||
338 | def test_unicode(self): |
|
338 | def test_unicode(self): | |
339 | """Check that files in odd encodings are accepted.""" |
|
339 | """Check that files in odd encodings are accepted.""" | |
340 | mydir = os.path.dirname(__file__) |
|
340 | mydir = os.path.dirname(__file__) | |
341 |
na = os.path.join(mydir, |
|
341 | na = os.path.join(mydir, "nonascii.py") | |
342 | _ip.magic('run "%s"' % na) |
|
342 | _ip.magic('run "%s"' % na) | |
343 | assert _ip.user_ns["u"] == "Ўт№Ф" |
|
343 | assert _ip.user_ns["u"] == "Ўт№Ф" | |
344 |
|
344 | |||
@@ -347,9 +347,9 b' tclass.py: deleting object: C-third' | |||||
347 | src = "t = __file__\n" |
|
347 | src = "t = __file__\n" | |
348 | self.mktmp(src) |
|
348 | self.mktmp(src) | |
349 | _missing = object() |
|
349 | _missing = object() | |
350 |
file1 = _ip.user_ns.get( |
|
350 | file1 = _ip.user_ns.get("__file__", _missing) | |
351 |
_ip.magic( |
|
351 | _ip.run_line_magic("run", self.fname) | |
352 |
file2 = _ip.user_ns.get( |
|
352 | file2 = _ip.user_ns.get("__file__", _missing) | |
353 |
|
353 | |||
354 | # Check that __file__ was equal to the filename in the script's |
|
354 | # Check that __file__ was equal to the filename in the script's | |
355 | # namespace. |
|
355 | # namespace. | |
@@ -363,9 +363,9 b' tclass.py: deleting object: C-third' | |||||
363 | src = "t = __file__\n" |
|
363 | src = "t = __file__\n" | |
364 | self.mktmp(src, ext='.ipy') |
|
364 | self.mktmp(src, ext='.ipy') | |
365 | _missing = object() |
|
365 | _missing = object() | |
366 |
file1 = _ip.user_ns.get( |
|
366 | file1 = _ip.user_ns.get("__file__", _missing) | |
367 |
_ip.magic( |
|
367 | _ip.run_line_magic("run", self.fname) | |
368 |
file2 = _ip.user_ns.get( |
|
368 | file2 = _ip.user_ns.get("__file__", _missing) | |
369 |
|
369 | |||
370 | # Check that __file__ was equal to the filename in the script's |
|
370 | # Check that __file__ was equal to the filename in the script's | |
371 | # namespace. |
|
371 | # namespace. | |
@@ -378,18 +378,18 b' tclass.py: deleting object: C-third' | |||||
378 | """ Test that %run -t -N<N> does not raise a TypeError for N > 1.""" |
|
378 | """ Test that %run -t -N<N> does not raise a TypeError for N > 1.""" | |
379 | src = "pass" |
|
379 | src = "pass" | |
380 | self.mktmp(src) |
|
380 | self.mktmp(src) | |
381 |
_ip.magic( |
|
381 | _ip.run_line_magic("run", "-t -N 1 %s" % self.fname) | |
382 |
_ip.magic( |
|
382 | _ip.run_line_magic("run", "-t -N 10 %s" % self.fname) | |
383 |
|
383 | |||
384 | def test_ignore_sys_exit(self): |
|
384 | def test_ignore_sys_exit(self): | |
385 | """Test the -e option to ignore sys.exit()""" |
|
385 | """Test the -e option to ignore sys.exit()""" | |
386 | src = "import sys; sys.exit(1)" |
|
386 | src = "import sys; sys.exit(1)" | |
387 | self.mktmp(src) |
|
387 | self.mktmp(src) | |
388 |
with tt.AssertPrints( |
|
388 | with tt.AssertPrints("SystemExit"): | |
389 |
_ip.magic( |
|
389 | _ip.run_line_magic("run", self.fname) | |
390 |
|
390 | |||
391 |
with tt.AssertNotPrints( |
|
391 | with tt.AssertNotPrints("SystemExit"): | |
392 |
_ip.magic( |
|
392 | _ip.run_line_magic("run", "-e %s" % self.fname) | |
393 |
|
393 | |||
394 | def test_run_nb(self): |
|
394 | def test_run_nb(self): | |
395 | """Test %run notebook.ipynb""" |
|
395 | """Test %run notebook.ipynb""" | |
@@ -404,7 +404,7 b' tclass.py: deleting object: C-third' | |||||
404 | src = writes(nb, version=4) |
|
404 | src = writes(nb, version=4) | |
405 | self.mktmp(src, ext='.ipynb') |
|
405 | self.mktmp(src, ext='.ipynb') | |
406 |
|
406 | |||
407 |
_ip.magic("run |
|
407 | _ip.run_line_magic("run", self.fname) | |
408 |
|
408 | |||
409 | assert _ip.user_ns["answer"] == 42 |
|
409 | assert _ip.user_ns["answer"] == 42 | |
410 |
|
410 | |||
@@ -478,12 +478,16 b' class TestMagicRunWithPackage(unittest.TestCase):' | |||||
478 | sys.path[:] = [p for p in sys.path if p != self.tempdir.name] |
|
478 | sys.path[:] = [p for p in sys.path if p != self.tempdir.name] | |
479 | self.tempdir.cleanup() |
|
479 | self.tempdir.cleanup() | |
480 |
|
480 | |||
481 |
def check_run_submodule(self, submodule, opts= |
|
481 | def check_run_submodule(self, submodule, opts=""): | |
482 |
_ip.user_ns.pop( |
|
482 | _ip.user_ns.pop("x", None) | |
483 | _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts)) |
|
483 | _ip.run_line_magic( | |
484 | self.assertEqual(_ip.user_ns['x'], self.value, |
|
484 | "run", "{2} -m {0}.{1}".format(self.package, submodule, opts) | |
485 | 'Variable `x` is not loaded from module `{0}`.' |
|
485 | ) | |
486 | .format(submodule)) |
|
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 | def test_run_submodule_with_absolute_import(self): |
|
492 | def test_run_submodule_with_absolute_import(self): | |
489 | self.check_run_submodule('absolute') |
|
493 | self.check_run_submodule('absolute') | |
@@ -533,17 +537,17 b' def test_run__name__():' | |||||
533 | f.write("q = __name__") |
|
537 | f.write("q = __name__") | |
534 |
|
538 | |||
535 | _ip.user_ns.pop("q", None) |
|
539 | _ip.user_ns.pop("q", None) | |
536 | _ip.magic("run {}".format(path)) |
|
540 | _ip.run_line_magic("run", "{}".format(path)) | |
537 | assert _ip.user_ns.pop("q") == "__main__" |
|
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 | assert _ip.user_ns.pop("q") == "foo" |
|
544 | assert _ip.user_ns.pop("q") == "foo" | |
541 |
|
545 | |||
542 | try: |
|
546 | try: | |
543 | _ip.magic("run -i -n {}".format(path)) |
|
547 | _ip.run_line_magic("run", "-i -n {}".format(path)) | |
544 | assert _ip.user_ns.pop("q") == "foo" |
|
548 | assert _ip.user_ns.pop("q") == "foo" | |
545 | finally: |
|
549 | finally: | |
546 |
_ip.magic( |
|
550 | _ip.run_line_magic("reset", "-f") | |
547 |
|
551 | |||
548 |
|
552 | |||
549 | def test_run_tb(): |
|
553 | def test_run_tb(): | |
@@ -563,7 +567,7 b' def test_run_tb():' | |||||
563 | ) |
|
567 | ) | |
564 | ) |
|
568 | ) | |
565 | with capture_output() as io: |
|
569 | with capture_output() as io: | |
566 |
_ip.magic( |
|
570 | _ip.run_line_magic("run", "{}".format(path)) | |
567 | out = io.stdout |
|
571 | out = io.stdout | |
568 | assert "execfile" not in out |
|
572 | assert "execfile" not in out | |
569 | assert "RuntimeError" in out |
|
573 | assert "RuntimeError" in out |
@@ -610,6 +610,8 b' class VerboseTB(TBTools):' | |||||
610 | traceback, to be used with alternate interpreters (because their own code |
|
610 | traceback, to be used with alternate interpreters (because their own code | |
611 | would appear in the traceback).""" |
|
611 | would appear in the traceback).""" | |
612 |
|
612 | |||
|
613 | _tb_highlight = "bg:ansiyellow" | |||
|
614 | ||||
613 | def __init__( |
|
615 | def __init__( | |
614 | self, |
|
616 | self, | |
615 | color_scheme: str = "Linux", |
|
617 | color_scheme: str = "Linux", | |
@@ -642,10 +644,8 b' class VerboseTB(TBTools):' | |||||
642 | self.long_header = long_header |
|
644 | self.long_header = long_header | |
643 | self.include_vars = include_vars |
|
645 | self.include_vars = include_vars | |
644 | # By default we use linecache.checkcache, but the user can provide a |
|
646 | # By default we use linecache.checkcache, but the user can provide a | |
645 |
# different check_cache implementation. This |
|
647 | # different check_cache implementation. This was formerly used by the | |
646 |
# |
|
648 | # IPython kernel for interactive code, but is no longer necessary. | |
647 | # by a compiler instance that flushes the linecache but preserves its |
|
|||
648 | # own code cache. |
|
|||
649 | if check_cache is None: |
|
649 | if check_cache is None: | |
650 | check_cache = linecache.checkcache |
|
650 | check_cache = linecache.checkcache | |
651 | self.check_cache = check_cache |
|
651 | self.check_cache = check_cache | |
@@ -836,7 +836,7 b' class VerboseTB(TBTools):' | |||||
836 | before = context - after |
|
836 | before = context - after | |
837 | if self.has_colors: |
|
837 | if self.has_colors: | |
838 | style = get_style_by_name("default") |
|
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 | formatter = Terminal256Formatter(style=style) |
|
840 | formatter = Terminal256Formatter(style=style) | |
841 | else: |
|
841 | else: | |
842 | formatter = None |
|
842 | formatter = None |
@@ -107,6 +107,10 b' Some of the known remaining caveats are:' | |||||
107 | before it is reloaded are not upgraded. |
|
107 | before it is reloaded are not upgraded. | |
108 |
|
108 | |||
109 | - C extension modules cannot be reloaded, and so cannot be autoreloaded. |
|
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 | from IPython.core.magic import Magics, magics_class, line_magic |
|
116 | from IPython.core.magic import Magics, magics_class, line_magic |
@@ -1,11 +1,14 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | from IPython.testing import decorators as dec |
|
2 | from IPython.testing import decorators as dec | |
3 |
|
3 | |||
|
4 | ||||
4 | def test_import_backgroundjobs(): |
|
5 | def test_import_backgroundjobs(): | |
5 | from IPython.lib import backgroundjobs |
|
6 | from IPython.lib import backgroundjobs | |
6 |
|
7 | |||
|
8 | ||||
7 | def test_import_deepreload(): |
|
9 | def test_import_deepreload(): | |
8 | from IPython.lib import deepreload |
|
10 | from IPython.lib import deepreload | |
9 |
|
11 | |||
|
12 | ||||
10 | def test_import_demo(): |
|
13 | def test_import_demo(): | |
11 | from IPython.lib import demo |
|
14 | from IPython.lib import demo |
@@ -981,8 +981,9 b' class IPythonDirective(Directive):' | |||||
981 | self.shell.warning_is_error = warning_is_error |
|
981 | self.shell.warning_is_error = warning_is_error | |
982 |
|
982 | |||
983 | # setup bookmark for saving figures directory |
|
983 | # setup bookmark for saving figures directory | |
984 |
self.shell.process_input_line( |
|
984 | self.shell.process_input_line( | |
985 | store_history=False) |
|
985 | 'bookmark ipy_savedir "%s"' % savefig_dir, store_history=False | |
|
986 | ) | |||
986 | self.shell.clear_cout() |
|
987 | self.shell.clear_cout() | |
987 |
|
988 | |||
988 | return rgxin, rgxout, promptin, promptout |
|
989 | return rgxin, rgxout, promptin, promptout |
@@ -405,14 +405,14 b' class TerminalInteractiveShell(InteractiveShell):' | |||||
405 | @observe('term_title') |
|
405 | @observe('term_title') | |
406 | def init_term_title(self, change=None): |
|
406 | def init_term_title(self, change=None): | |
407 | # Enable or disable the terminal title. |
|
407 | # Enable or disable the terminal title. | |
408 | if self.term_title: |
|
408 | if self.term_title and _is_tty: | |
409 | toggle_set_term_title(True) |
|
409 | toggle_set_term_title(True) | |
410 | set_term_title(self.term_title_format.format(cwd=abbrev_cwd())) |
|
410 | set_term_title(self.term_title_format.format(cwd=abbrev_cwd())) | |
411 | else: |
|
411 | else: | |
412 | toggle_set_term_title(False) |
|
412 | toggle_set_term_title(False) | |
413 |
|
413 | |||
414 | def restore_term_title(self): |
|
414 | def restore_term_title(self): | |
415 | if self.term_title: |
|
415 | if self.term_title and _is_tty: | |
416 | restore_term_title() |
|
416 | restore_term_title() | |
417 |
|
417 | |||
418 | def init_display_formatter(self): |
|
418 | def init_display_formatter(self): | |
@@ -711,9 +711,8 b' class TerminalInteractiveShell(InteractiveShell):' | |||||
711 |
|
711 | |||
712 | active_eventloop = None |
|
712 | active_eventloop = None | |
713 | def enable_gui(self, gui=None): |
|
713 | def enable_gui(self, gui=None): | |
714 |
if gui and (gui |
|
714 | if gui and (gui not in {"inline", "webagg"}): | |
715 |
self.active_eventloop, self._inputhook = |
|
715 | self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui) | |
716 | get_inputhook_name_and_func(gui) |
|
|||
717 | else: |
|
716 | else: | |
718 | self.active_eventloop = self._inputhook = None |
|
717 | self.active_eventloop = self._inputhook = None | |
719 |
|
718 |
@@ -318,6 +318,7 b' class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):' | |||||
318 | self.shell.mainloop() |
|
318 | self.shell.mainloop() | |
319 | else: |
|
319 | else: | |
320 | self.log.debug("IPython not interactive...") |
|
320 | self.log.debug("IPython not interactive...") | |
|
321 | self.shell.restore_term_title() | |||
321 | if not self.shell.last_execution_succeeded: |
|
322 | if not self.shell.last_execution_succeeded: | |
322 | sys.exit(1) |
|
323 | sys.exit(1) | |
323 |
|
324 |
@@ -41,7 +41,7 b' class TerminalMagics(Magics):' | |||||
41 | def __init__(self, shell): |
|
41 | def __init__(self, shell): | |
42 | super(TerminalMagics, self).__init__(shell) |
|
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 | """ Execute a block, or store it in a variable, per the user's request. |
|
45 | """ Execute a block, or store it in a variable, per the user's request. | |
46 | """ |
|
46 | """ | |
47 | if name: |
|
47 | if name: | |
@@ -53,7 +53,7 b' class TerminalMagics(Magics):' | |||||
53 | self.shell.user_ns['pasted_block'] = b |
|
53 | self.shell.user_ns['pasted_block'] = b | |
54 | self.shell.using_paste_magics = True |
|
54 | self.shell.using_paste_magics = True | |
55 | try: |
|
55 | try: | |
56 |
self.shell.run_cell(b, store_history |
|
56 | self.shell.run_cell(b, store_history) | |
57 | finally: |
|
57 | finally: | |
58 | self.shell.using_paste_magics = False |
|
58 | self.shell.using_paste_magics = False | |
59 |
|
59 | |||
@@ -147,7 +147,7 b' class TerminalMagics(Magics):' | |||||
147 |
|
147 | |||
148 | sentinel = opts.get('s', u'--') |
|
148 | sentinel = opts.get('s', u'--') | |
149 | block = '\n'.join(get_pasted_lines(sentinel, quiet=quiet)) |
|
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 | @line_magic |
|
152 | @line_magic | |
153 | def paste(self, parameter_s=''): |
|
153 | def paste(self, parameter_s=''): | |
@@ -203,7 +203,7 b' class TerminalMagics(Magics):' | |||||
203 | sys.stdout.write("\n") |
|
203 | sys.stdout.write("\n") | |
204 | sys.stdout.write("## -- End pasted text --\n") |
|
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 | # Class-level: add a '%cls' magic only on Windows |
|
208 | # Class-level: add a '%cls' magic only on Windows | |
209 | if sys.platform == 'win32': |
|
209 | if sys.platform == 'win32': |
@@ -31,8 +31,7 b' from prompt_toolkit import __version__ as ptk_version' | |||||
31 |
|
31 | |||
32 | from IPython.core.async_helpers import get_asyncio_loop |
|
32 | from IPython.core.async_helpers import get_asyncio_loop | |
33 |
|
33 | |||
34 |
PTK3 = ptk_version.startswith( |
|
34 | PTK3 = ptk_version.startswith("3.") | |
35 |
|
||||
36 |
|
35 | |||
37 |
|
36 | |||
38 | def inputhook(context): |
|
37 | def inputhook(context): |
@@ -41,6 +41,7 b' import gtk, gobject' | |||||
41 | # Enable threading in GTK. (Otherwise, GTK will keep the GIL.) |
|
41 | # Enable threading in GTK. (Otherwise, GTK will keep the GIL.) | |
42 | gtk.gdk.threads_init() |
|
42 | gtk.gdk.threads_init() | |
43 |
|
43 | |||
|
44 | ||||
44 | def inputhook(context): |
|
45 | def inputhook(context): | |
45 | """ |
|
46 | """ | |
46 | When the eventloop of prompt-toolkit is idle, call this inputhook. |
|
47 | When the eventloop of prompt-toolkit is idle, call this inputhook. | |
@@ -50,6 +51,7 b' def inputhook(context):' | |||||
50 |
|
51 | |||
51 | :param context: An `InputHookContext` instance. |
|
52 | :param context: An `InputHookContext` instance. | |
52 | """ |
|
53 | """ | |
|
54 | ||||
53 | def _main_quit(*a, **kw): |
|
55 | def _main_quit(*a, **kw): | |
54 | gtk.main_quit() |
|
56 | gtk.main_quit() | |
55 | return False |
|
57 | return False |
@@ -3,10 +3,12 b'' | |||||
3 |
|
3 | |||
4 | from gi.repository import Gtk, GLib |
|
4 | from gi.repository import Gtk, GLib | |
5 |
|
5 | |||
|
6 | ||||
6 | def _main_quit(*args, **kwargs): |
|
7 | def _main_quit(*args, **kwargs): | |
7 | Gtk.main_quit() |
|
8 | Gtk.main_quit() | |
8 | return False |
|
9 | return False | |
9 |
|
10 | |||
|
11 | ||||
10 | def inputhook(context): |
|
12 | def inputhook(context): | |
11 | GLib.io_add_watch(context.fileno(), GLib.PRIORITY_DEFAULT, GLib.IO_IN, _main_quit) |
|
13 | GLib.io_add_watch(context.fileno(), GLib.PRIORITY_DEFAULT, GLib.IO_IN, _main_quit) | |
12 | Gtk.main() |
|
14 | Gtk.main() |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file |
@@ -2,7 +2,7 b'' | |||||
2 | """Decorators that don't go anywhere else. |
|
2 | """Decorators that don't go anywhere else. | |
3 |
|
3 | |||
4 | This module contains misc. decorators that don't really go with another module |
|
4 | This module contains misc. decorators that don't really go with another module | |
5 | in :mod:`IPython.utils`. Beore putting something here please see if it should |
|
5 | in :mod:`IPython.utils`. Before putting something here please see if it should | |
6 | go into another topical module in :mod:`IPython.utils`. |
|
6 | go into another topical module in :mod:`IPython.utils`. | |
7 | """ |
|
7 | """ | |
8 |
|
8 | |||
@@ -16,6 +16,10 b' go into another topical module in :mod:`IPython.utils`.' | |||||
16 | #----------------------------------------------------------------------------- |
|
16 | #----------------------------------------------------------------------------- | |
17 | # Imports |
|
17 | # Imports | |
18 | #----------------------------------------------------------------------------- |
|
18 | #----------------------------------------------------------------------------- | |
|
19 | from typing import Sequence | |||
|
20 | ||||
|
21 | from IPython.utils.docs import GENERATING_DOCUMENTATION | |||
|
22 | ||||
19 |
|
23 | |||
20 | #----------------------------------------------------------------------------- |
|
24 | #----------------------------------------------------------------------------- | |
21 | # Code |
|
25 | # Code | |
@@ -48,6 +52,7 b' def flag_calls(func):' | |||||
48 | wrapper.__doc__ = func.__doc__ |
|
52 | wrapper.__doc__ = func.__doc__ | |
49 | return wrapper |
|
53 | return wrapper | |
50 |
|
54 | |||
|
55 | ||||
51 | def undoc(func): |
|
56 | def undoc(func): | |
52 | """Mark a function or class as undocumented. |
|
57 | """Mark a function or class as undocumented. | |
53 |
|
58 | |||
@@ -56,3 +61,23 b' def undoc(func):' | |||||
56 | """ |
|
61 | """ | |
57 | return func |
|
62 | return func | |
58 |
|
63 | |||
|
64 | ||||
|
65 | def sphinx_options( | |||
|
66 | show_inheritance: bool = True, | |||
|
67 | show_inherited_members: bool = False, | |||
|
68 | exclude_inherited_from: Sequence[str] = tuple(), | |||
|
69 | ): | |||
|
70 | """Set sphinx options""" | |||
|
71 | ||||
|
72 | def wrapper(func): | |||
|
73 | if not GENERATING_DOCUMENTATION: | |||
|
74 | return func | |||
|
75 | ||||
|
76 | func._sphinx_options = dict( | |||
|
77 | show_inheritance=show_inheritance, | |||
|
78 | show_inherited_members=show_inherited_members, | |||
|
79 | exclude_inherited_from=exclude_inherited_from, | |||
|
80 | ) | |||
|
81 | return func | |||
|
82 | ||||
|
83 | return wrapper |
@@ -62,15 +62,27 b' def _restore_term_title():' | |||||
62 | pass |
|
62 | pass | |
63 |
|
63 | |||
64 |
|
64 | |||
|
65 | _xterm_term_title_saved = False | |||
|
66 | ||||
|
67 | ||||
65 | def _set_term_title_xterm(title): |
|
68 | def _set_term_title_xterm(title): | |
66 | """ Change virtual terminal title in xterm-workalikes """ |
|
69 | """ Change virtual terminal title in xterm-workalikes """ | |
|
70 | global _xterm_term_title_saved | |||
|
71 | # Only save the title the first time we set, otherwise restore will only | |||
|
72 | # go back one title (probably undoing a %cd title change). | |||
|
73 | if not _xterm_term_title_saved: | |||
67 | # save the current title to the xterm "stack" |
|
74 | # save the current title to the xterm "stack" | |
68 |
sys.stdout.write( |
|
75 | sys.stdout.write("\033[22;0t") | |
|
76 | _xterm_term_title_saved = True | |||
69 | sys.stdout.write('\033]0;%s\007' % title) |
|
77 | sys.stdout.write('\033]0;%s\007' % title) | |
70 |
|
78 | |||
71 |
|
79 | |||
72 | def _restore_term_title_xterm(): |
|
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 | sys.stdout.write('\033[23;0t') |
|
84 | sys.stdout.write('\033[23;0t') | |
|
85 | _xterm_term_title_saved = False | |||
74 |
|
86 | |||
75 |
|
87 | |||
76 | if os.name == 'posix': |
|
88 | if os.name == 'posix': |
@@ -19,7 +19,6 b' def test_base():' | |||||
19 |
|
19 | |||
20 |
|
20 | |||
21 | def test_SubClass(): |
|
21 | def test_SubClass(): | |
22 |
|
||||
23 | class SubClass(Base): |
|
22 | class SubClass(Base): | |
24 | y = 2 |
|
23 | y = 2 | |
25 |
|
24 | |||
@@ -53,7 +52,7 b' def test_misbehaving_object_without_trait_names():' | |||||
53 |
|
52 | |||
54 | class SillierWithDir(MisbehavingGetattr): |
|
53 | class SillierWithDir(MisbehavingGetattr): | |
55 | def __dir__(self): |
|
54 | def __dir__(self): | |
56 |
return [ |
|
55 | return ["some_method"] | |
57 |
|
56 | |||
58 | for bad_klass in (MisbehavingGetattr, SillierWithDir): |
|
57 | for bad_klass in (MisbehavingGetattr, SillierWithDir): | |
59 | obj = bad_klass() |
|
58 | obj = bad_klass() |
@@ -13,8 +13,8 b'' | |||||
13 | .. image:: https://raster.shields.io/badge/Follows-NEP29-brightgreen.png |
|
13 | .. image:: https://raster.shields.io/badge/Follows-NEP29-brightgreen.png | |
14 | :target: https://numpy.org/neps/nep-0029-deprecation_policy.html |
|
14 | :target: https://numpy.org/neps/nep-0029-deprecation_policy.html | |
15 |
|
15 | |||
16 |
.. image:: https://tidelift.com/ |
|
16 | .. image:: https://tidelift.com/badges/package/pypi/ipython?style=flat | |
17 |
:target: https://tidelift.com/ |
|
17 | :target: https://tidelift.com/subscription/pkg/pypi-ipython | |
18 |
|
18 | |||
19 |
|
19 | |||
20 | =========================================== |
|
20 | =========================================== |
@@ -41,6 +41,14 b' else:' | |||||
41 | html_theme = "sphinx_rtd_theme" |
|
41 | html_theme = "sphinx_rtd_theme" | |
42 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] |
|
42 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] | |
43 |
|
43 | |||
|
44 | # Allow Python scripts to change behaviour during sphinx run | |||
|
45 | os.environ["IN_SPHINX_RUN"] = "True" | |||
|
46 | ||||
|
47 | autodoc_type_aliases = { | |||
|
48 | "Matcher": " IPython.core.completer.Matcher", | |||
|
49 | "MatcherAPIv1": " IPython.core.completer.MatcherAPIv1", | |||
|
50 | } | |||
|
51 | ||||
44 | # If your extensions are in another directory, add it here. If the directory |
|
52 | # If your extensions are in another directory, add it here. If the directory | |
45 | # is relative to the documentation root, use os.path.abspath to make it |
|
53 | # is relative to the documentation root, use os.path.abspath to make it | |
46 | # absolute, like shown here. |
|
54 | # absolute, like shown here. |
@@ -69,7 +69,7 b' shell:' | |||||
69 |
|
69 | |||
70 | /home/bob >>> # it works |
|
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 | extensions to customise prompts. |
|
73 | extensions to customise prompts. | |
74 |
|
74 | |||
75 | Inside IPython or in a startup script, you can use a custom prompts class |
|
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 | A level above configuration are IPython extensions, Python modules which modify |
|
7 | A level above configuration are IPython extensions, Python modules which modify | |
8 | the behaviour of the shell. They are referred to by an importable module name, |
|
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 |
|
9 | and can be placed anywhere you'd normally import from. | |
10 | ``.ipython/extensions/``. |
|
|||
11 |
|
10 | |||
12 | Getting extensions |
|
11 | Getting extensions | |
13 | ================== |
|
12 | ================== | |
@@ -71,10 +70,7 b' Useful :class:`InteractiveShell` methods include :meth:`~IPython.core.interactiv' | |||||
71 | :ref:`defining_magics` |
|
70 | :ref:`defining_magics` | |
72 |
|
71 | |||
73 | You can put your extension modules anywhere you want, as long as they can be |
|
72 | You can put your extension modules anywhere you want, as long as they can be | |
74 |
imported by Python's standard import mechanism. |
|
73 | 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. |
|
|||
78 |
|
74 | |||
79 | When your extension is ready for general use, please add it to the `extensions |
|
75 | When your extension is ready for general use, please add it to the `extensions | |
80 | index <https://github.com/ipython/ipython/wiki/Extensions-Index>`_. We also |
|
76 | index <https://github.com/ipython/ipython/wiki/Extensions-Index>`_. We also |
@@ -2,6 +2,70 b'' | |||||
2 | 8.x Series |
|
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 | .. _version 8.5.0: |
|
69 | .. _version 8.5.0: | |
6 |
|
70 | |||
7 | IPython 8.5.0 |
|
71 | IPython 8.5.0 |
@@ -24,14 +24,9 b' import inspect' | |||||
24 | import os |
|
24 | import os | |
25 | import re |
|
25 | import re | |
26 | from importlib import import_module |
|
26 | from importlib import import_module | |
|
27 | from types import SimpleNamespace as Obj | |||
27 |
|
28 | |||
28 |
|
29 | |||
29 | class Obj(object): |
|
|||
30 | '''Namespace to hold arbitrary information.''' |
|
|||
31 | def __init__(self, **kwargs): |
|
|||
32 | for k, v in kwargs.items(): |
|
|||
33 | setattr(self, k, v) |
|
|||
34 |
|
||||
35 | class FuncClsScanner(ast.NodeVisitor): |
|
30 | class FuncClsScanner(ast.NodeVisitor): | |
36 | """Scan a module for top-level functions and classes. |
|
31 | """Scan a module for top-level functions and classes. | |
37 |
|
32 | |||
@@ -62,11 +57,15 b' class FuncClsScanner(ast.NodeVisitor):' | |||||
62 | self.functions.append(node.name) |
|
57 | self.functions.append(node.name) | |
63 |
|
58 | |||
64 | def visit_ClassDef(self, node): |
|
59 | def visit_ClassDef(self, node): | |
65 | if not (node.name.startswith('_') or self.has_undoc_decorator(node)) \ |
|
60 | if ( | |
66 | and node.name not in self.classes_seen: |
|
61 | not (node.name.startswith("_") or self.has_undoc_decorator(node)) | |
67 | cls = Obj(name=node.name) |
|
62 | and node.name not in self.classes_seen | |
68 | cls.has_init = any(isinstance(n, ast.FunctionDef) and \ |
|
63 | ): | |
69 | n.name=='__init__' for n in node.body) |
|
64 | cls = Obj(name=node.name, sphinx_options={}) | |
|
65 | cls.has_init = any( | |||
|
66 | isinstance(n, ast.FunctionDef) and n.name == "__init__" | |||
|
67 | for n in node.body | |||
|
68 | ) | |||
70 | self.classes.append(cls) |
|
69 | self.classes.append(cls) | |
71 | self.classes_seen.add(node.name) |
|
70 | self.classes_seen.add(node.name) | |
72 |
|
71 | |||
@@ -221,7 +220,11 b' class ApiDocWriter(object):' | |||||
221 | funcs, classes = [], [] |
|
220 | funcs, classes = [], [] | |
222 | for name, obj in ns.items(): |
|
221 | for name, obj in ns.items(): | |
223 | if inspect.isclass(obj): |
|
222 | if inspect.isclass(obj): | |
224 | cls = Obj(name=name, has_init='__init__' in obj.__dict__) |
|
223 | cls = Obj( | |
|
224 | name=name, | |||
|
225 | has_init="__init__" in obj.__dict__, | |||
|
226 | sphinx_options=getattr(obj, "_sphinx_options", {}), | |||
|
227 | ) | |||
225 | classes.append(cls) |
|
228 | classes.append(cls) | |
226 | elif inspect.isfunction(obj): |
|
229 | elif inspect.isfunction(obj): | |
227 | funcs.append(name) |
|
230 | funcs.append(name) | |
@@ -279,10 +282,18 b' class ApiDocWriter(object):' | |||||
279 | self.rst_section_levels[2] * len(subhead) + '\n' |
|
282 | self.rst_section_levels[2] * len(subhead) + '\n' | |
280 |
|
283 | |||
281 | for c in classes: |
|
284 | for c in classes: | |
282 | ad += '\n.. autoclass:: ' + c.name + '\n' |
|
285 | opts = c.sphinx_options | |
|
286 | ad += "\n.. autoclass:: " + c.name + "\n" | |||
283 | # must NOT exclude from index to keep cross-refs working |
|
287 | # must NOT exclude from index to keep cross-refs working | |
284 |
ad += |
|
288 | ad += " :members:\n" | |
285 |
|
|
289 | if opts.get("show_inheritance", True): | |
|
290 | ad += " :show-inheritance:\n" | |||
|
291 | if opts.get("show_inherited_members", False): | |||
|
292 | exclusions_list = opts.get("exclude_inherited_from", []) | |||
|
293 | exclusions = ( | |||
|
294 | (" " + " ".join(exclusions_list)) if exclusions_list else "" | |||
|
295 | ) | |||
|
296 | ad += f" :inherited-members:{exclusions}\n" | |||
286 | if c.has_init: |
|
297 | if c.has_init: | |
287 | ad += '\n .. automethod:: __init__\n' |
|
298 | ad += '\n .. automethod:: __init__\n' | |
288 |
|
299 |
@@ -54,6 +54,7 b' doc =' | |||||
54 | matplotlib |
|
54 | matplotlib | |
55 | stack_data |
|
55 | stack_data | |
56 | pytest<7 |
|
56 | pytest<7 | |
|
57 | typing_extensions | |||
57 | %(test)s |
|
58 | %(test)s | |
58 | kernel = |
|
59 | kernel = | |
59 | ipykernel |
|
60 | ipykernel | |
@@ -78,7 +79,7 b' test_extra =' | |||||
78 | curio |
|
79 | curio | |
79 | matplotlib!=3.2.0 |
|
80 | matplotlib!=3.2.0 | |
80 | nbformat |
|
81 | nbformat | |
81 |
numpy>=1. |
|
82 | numpy>=1.20 | |
82 | pandas |
|
83 | pandas | |
83 | trio |
|
84 | trio | |
84 | all = |
|
85 | all = |
@@ -211,14 +211,20 b' def find_entry_points():' | |||||
211 | use, our own build_scripts_entrypt class below parses these and builds |
|
211 | use, our own build_scripts_entrypt class below parses these and builds | |
212 | command line scripts. |
|
212 | command line scripts. | |
213 |
|
213 | |||
214 |
Each of our entry points gets |
|
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 |
|
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 | ep = [ |
|
218 | ep = [ | |
218 | 'ipython%s = IPython:start_ipython', |
|
219 | 'ipython%s = IPython:start_ipython', | |
219 | ] |
|
220 | ] | |
220 | suffix = str(sys.version_info[0]) |
|
221 | major_suffix = str(sys.version_info[0]) | |
221 | return [e % '' for e in ep] + [e % suffix for e in ep] |
|
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 | class install_lib_symlink(Command): |
|
229 | class install_lib_symlink(Command): | |
224 | user_options = [ |
|
230 | user_options = [ | |
@@ -340,7 +346,7 b' def git_prebuild(pkg_dir, build_cmd=build_py):' | |||||
340 | out_file.writelines( |
|
346 | out_file.writelines( | |
341 | [ |
|
347 | [ | |
342 | "# GENERATED BY setup.py\n", |
|
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