ipython 9.0.0b2__py3-none-any.whl → 9.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- IPython/core/debugger.py +15 -3
- IPython/core/inputtransformer2.py +3 -9
- IPython/core/interactiveshell.py +11 -8
- IPython/core/magic.py +3 -2
- IPython/core/magics/code.py +1 -1
- IPython/core/oinspect.py +7 -1
- IPython/core/release.py +3 -3
- IPython/core/tbtools.py +7 -1
- IPython/core/tips.py +9 -3
- IPython/extensions/deduperreload/__init__.py +0 -0
- IPython/extensions/deduperreload/deduperreload.py +608 -0
- IPython/extensions/deduperreload/deduperreload_patching.py +141 -0
- IPython/terminal/interactiveshell.py +6 -0
- IPython/terminal/pt_inputhooks/qt.py +3 -1
- IPython/terminal/ptutils.py +3 -2
- IPython/terminal/shortcuts/__init__.py +1 -1
- IPython/utils/PyColorize.py +8 -2
- IPython/utils/_sysinfo.py +1 -1
- IPython/utils/coloransi.py +9 -0
- {ipython-9.0.0b2.dist-info → ipython-9.0.2.dist-info}/METADATA +1 -1
- {ipython-9.0.0b2.dist-info → ipython-9.0.2.dist-info}/RECORD +27 -23
- {ipython-9.0.0b2.dist-info → ipython-9.0.2.dist-info}/WHEEL +1 -1
- {ipython-9.0.0b2.data → ipython-9.0.2.data}/data/share/man/man1/ipython.1 +0 -0
- {ipython-9.0.0b2.dist-info → ipython-9.0.2.dist-info}/COPYING.rst +0 -0
- {ipython-9.0.0b2.dist-info → ipython-9.0.2.dist-info}/LICENSE +0 -0
- {ipython-9.0.0b2.dist-info → ipython-9.0.2.dist-info}/entry_points.txt +0 -0
- {ipython-9.0.0b2.dist-info → ipython-9.0.2.dist-info}/top_level.txt +0 -0
IPython/core/debugger.py
CHANGED
|
@@ -228,7 +228,12 @@ class Pdb(OldPdb):
|
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
def __init__(
|
|
231
|
-
self,
|
|
231
|
+
self,
|
|
232
|
+
completekey=None,
|
|
233
|
+
stdin=None,
|
|
234
|
+
stdout=None,
|
|
235
|
+
context: int | None | str = 5,
|
|
236
|
+
**kwargs,
|
|
232
237
|
):
|
|
233
238
|
"""Create a new IPython debugger.
|
|
234
239
|
|
|
@@ -251,7 +256,11 @@ class Pdb(OldPdb):
|
|
|
251
256
|
The possibilities are python version dependent, see the python
|
|
252
257
|
docs for more info.
|
|
253
258
|
"""
|
|
254
|
-
|
|
259
|
+
# ipdb issue, see https://github.com/ipython/ipython/issues/14811
|
|
260
|
+
if context is None:
|
|
261
|
+
context = 5
|
|
262
|
+
if isinstance(context, str):
|
|
263
|
+
context = int(context)
|
|
255
264
|
self.context = context
|
|
256
265
|
|
|
257
266
|
# `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
|
|
@@ -298,7 +307,10 @@ class Pdb(OldPdb):
|
|
|
298
307
|
return self._context
|
|
299
308
|
|
|
300
309
|
@context.setter
|
|
301
|
-
def context(self, value: int) -> None:
|
|
310
|
+
def context(self, value: int | str) -> None:
|
|
311
|
+
# ipdb issue see https://github.com/ipython/ipython/issues/14811
|
|
312
|
+
if not isinstance(value, int):
|
|
313
|
+
value = int(value)
|
|
302
314
|
assert isinstance(value, int)
|
|
303
315
|
assert value >= 0
|
|
304
316
|
self._context = value
|
|
@@ -17,6 +17,7 @@ import sys
|
|
|
17
17
|
import tokenize
|
|
18
18
|
from typing import List, Tuple, Optional, Any
|
|
19
19
|
import warnings
|
|
20
|
+
from textwrap import dedent
|
|
20
21
|
|
|
21
22
|
from IPython.utils import tokenutil
|
|
22
23
|
|
|
@@ -38,18 +39,11 @@ def leading_empty_lines(lines):
|
|
|
38
39
|
def leading_indent(lines):
|
|
39
40
|
"""Remove leading indentation.
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
removed from each following line in the cell.
|
|
42
|
+
Removes the minimum common leading indentation from all lines.
|
|
43
43
|
"""
|
|
44
44
|
if not lines:
|
|
45
45
|
return lines
|
|
46
|
-
|
|
47
|
-
if not m or lines[0].strip().startswith("#"):
|
|
48
|
-
return lines
|
|
49
|
-
space = m.group(0)
|
|
50
|
-
n = len(space)
|
|
51
|
-
return [l[n:] if l.startswith(space) else l
|
|
52
|
-
for l in lines]
|
|
46
|
+
return dedent("".join(lines)).splitlines(keepends=True)
|
|
53
47
|
|
|
54
48
|
class PromptStripper:
|
|
55
49
|
"""Remove matching input prompts from a block of input.
|
IPython/core/interactiveshell.py
CHANGED
|
@@ -329,6 +329,7 @@ class InteractiveShell(SingletonConfigurable):
|
|
|
329
329
|
|
|
330
330
|
_instance = None
|
|
331
331
|
_user_ns: dict
|
|
332
|
+
_sys_modules_keys: set[str, AnyType]
|
|
332
333
|
|
|
333
334
|
inspector: oinspect.Inspector
|
|
334
335
|
|
|
@@ -764,7 +765,9 @@ class InteractiveShell(SingletonConfigurable):
|
|
|
764
765
|
if not new == new.lower():
|
|
765
766
|
warn(
|
|
766
767
|
f"`TerminalInteractiveShell.colors` is now lowercase: `{new.lower()}`,"
|
|
767
|
-
" non lowercase, may invalid in the future."
|
|
768
|
+
" non lowercase, may be invalid in the future.",
|
|
769
|
+
DeprecationWarning,
|
|
770
|
+
stacklevel=2,
|
|
768
771
|
)
|
|
769
772
|
return new.lower()
|
|
770
773
|
|
|
@@ -781,7 +784,8 @@ class InteractiveShell(SingletonConfigurable):
|
|
|
781
784
|
)
|
|
782
785
|
|
|
783
786
|
try:
|
|
784
|
-
|
|
787
|
+
# Deprecation in 9.0, colors should always be lower
|
|
788
|
+
self.inspector.set_theme_name(self.colors.lower())
|
|
785
789
|
except Exception:
|
|
786
790
|
warn(
|
|
787
791
|
"Error changing object inspector color schemes.\n%s"
|
|
@@ -804,20 +808,19 @@ class InteractiveShell(SingletonConfigurable):
|
|
|
804
808
|
|
|
805
809
|
self.dir_stack = []
|
|
806
810
|
|
|
807
|
-
def init_logger(self):
|
|
811
|
+
def init_logger(self) -> None:
|
|
808
812
|
self.logger = Logger(self.home_dir, logfname='ipython_log.py',
|
|
809
813
|
logmode='rotate')
|
|
810
814
|
|
|
811
|
-
def init_logstart(self):
|
|
815
|
+
def init_logstart(self) -> None:
|
|
812
816
|
"""Initialize logging in case it was requested at the command line.
|
|
813
817
|
"""
|
|
814
818
|
if self.logappend:
|
|
815
|
-
self.
|
|
819
|
+
self.run_line_magic("logstart", f"{self.logappend} append")
|
|
816
820
|
elif self.logfile:
|
|
817
|
-
self.
|
|
821
|
+
self.run_line_magic("logstart", self.logfile)
|
|
818
822
|
elif self.logstart:
|
|
819
|
-
self.
|
|
820
|
-
|
|
823
|
+
self.run_line_magic("logstart", "")
|
|
821
824
|
|
|
822
825
|
def init_builtins(self):
|
|
823
826
|
# A single, static flag that we set to True. Its presence indicates
|
IPython/core/magic.py
CHANGED
|
@@ -672,10 +672,11 @@ class Magics(Configurable):
|
|
|
672
672
|
argv = arg_split(arg_str, posix, strict)
|
|
673
673
|
# Do regular option processing
|
|
674
674
|
try:
|
|
675
|
-
opts,args = getopt(argv, opt_str, long_opts)
|
|
675
|
+
opts, args = getopt(argv, opt_str, long_opts)
|
|
676
676
|
except GetoptError as e:
|
|
677
677
|
raise UsageError(
|
|
678
|
-
'%s (
|
|
678
|
+
'%s (allowed: "%s"%s)'
|
|
679
|
+
% (e.msg, opt_str, " ".join(("",) + long_opts) if long_opts else "")
|
|
679
680
|
) from e
|
|
680
681
|
for o, a in opts:
|
|
681
682
|
if mode == "string" and preserve_non_opts:
|
IPython/core/magics/code.py
CHANGED
|
@@ -193,7 +193,7 @@ class CodeMagics(Magics):
|
|
|
193
193
|
|
|
194
194
|
-r: use 'raw' input. By default, the 'processed' history is used,
|
|
195
195
|
so that magics are loaded in their transformed version to valid
|
|
196
|
-
Python. If this option is given, the raw input as typed
|
|
196
|
+
Python. If this option is given, the raw input as typed at the
|
|
197
197
|
command line is used instead.
|
|
198
198
|
|
|
199
199
|
-f: force overwrite. If file exists, %save will prompt for overwrite
|
IPython/core/oinspect.py
CHANGED
|
@@ -401,7 +401,13 @@ class Inspector(Configurable):
|
|
|
401
401
|
parent=None,
|
|
402
402
|
config=None,
|
|
403
403
|
):
|
|
404
|
-
|
|
404
|
+
if theme_name in ["Linux", "LightBG", "Neutral", "NoColor"]:
|
|
405
|
+
warnings.warn(
|
|
406
|
+
f"Theme names and color schemes are lowercase in IPython 9.0 use {theme_name.lower()} instead",
|
|
407
|
+
DeprecationWarning,
|
|
408
|
+
stacklevel=2,
|
|
409
|
+
)
|
|
410
|
+
theme_name = theme_name.lower()
|
|
405
411
|
self._theme_name = theme_name
|
|
406
412
|
super(Inspector, self).__init__(parent=parent, config=config)
|
|
407
413
|
self.parser = PyColorize.Parser(out="str", theme_name=theme_name)
|
IPython/core/release.py
CHANGED
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
# version
|
|
18
18
|
_version_major = 9
|
|
19
19
|
_version_minor = 0
|
|
20
|
-
_version_patch =
|
|
20
|
+
_version_patch = 2
|
|
21
21
|
_version_extra = ".dev"
|
|
22
|
-
_version_extra = "b2"
|
|
23
|
-
|
|
22
|
+
# _version_extra = "b2"
|
|
23
|
+
_version_extra = "" # Uncomment this for full releases
|
|
24
24
|
|
|
25
25
|
# Construct full version string from these.
|
|
26
26
|
_ver = [_version_major, _version_minor, _version_patch]
|
IPython/core/tbtools.py
CHANGED
|
@@ -401,7 +401,13 @@ class TBTools:
|
|
|
401
401
|
stacklevel=2,
|
|
402
402
|
)
|
|
403
403
|
theme_name = color_scheme
|
|
404
|
-
|
|
404
|
+
if theme_name in ["Linux", "LightBG", "Neutral", "NoColor"]:
|
|
405
|
+
warnings.warn(
|
|
406
|
+
f"Theme names and color schemes are lowercase in IPython 9.0 use {theme_name.lower()} instead",
|
|
407
|
+
DeprecationWarning,
|
|
408
|
+
stacklevel=2,
|
|
409
|
+
)
|
|
410
|
+
theme_name = theme_name.lower()
|
|
405
411
|
# Whether to call the interactive pdb debugger after printing
|
|
406
412
|
# tracebacks or not
|
|
407
413
|
super().__init__()
|
IPython/core/tips.py
CHANGED
|
@@ -71,12 +71,12 @@ _tips: Any = {
|
|
|
71
71
|
"Run your doctests from within IPython for development and debugging. The special %doctest_mode command toggles a mode where the prompt, output and exceptions display matches as closely as possible that of the default Python interpreter.",
|
|
72
72
|
"You can use `files = !ls *.png`",
|
|
73
73
|
"Use the IPython.lib.demo.Demo class to load any Python script as an interactive demo.",
|
|
74
|
-
"Put a
|
|
74
|
+
"Put a ';' at the end of a line to suppress the printing of output.",
|
|
75
75
|
"You can use Ctrl-O to force a new line in terminal IPython",
|
|
76
76
|
"Use `object?` to see the help on `object`, `object??` to view it's source",
|
|
77
77
|
"`?` alone on a line will brings up IPython's help",
|
|
78
78
|
"You can use `%hist` to view history, see the options with `%history?`",
|
|
79
|
-
"You can change the
|
|
79
|
+
"You can change the editing mode of IPython to behave more like vi, or emacs.",
|
|
80
80
|
"IPython 9.0+ have hooks to integrate AI/LLM completions.",
|
|
81
81
|
"Use `%timeit` or `%%timeit`, and the `-r`, `-n`, and `-o` options to easily profile your code.",
|
|
82
82
|
"Use `ipython --help-all | less` to view all the IPython configurations options.",
|
|
@@ -91,11 +91,17 @@ if os.name == "nt":
|
|
|
91
91
|
"We can't show you all tips on windows as sometime Unicode characters crash windows console, please help us debug it."
|
|
92
92
|
]
|
|
93
93
|
)
|
|
94
|
+
# unicode may crash windows console, so we filter out tips with non-ASCII characters.
|
|
95
|
+
_tips["every_year"] = {
|
|
96
|
+
k: v
|
|
97
|
+
for k, v in _tips["every_year"].items()
|
|
98
|
+
if all(ord(char) < 128 for char in v)
|
|
99
|
+
}
|
|
94
100
|
else:
|
|
95
101
|
_tips["random"].extend(
|
|
96
102
|
[
|
|
97
103
|
"You can use latex or unicode completion, `\\alpha<tab>` will insert the α symbol.",
|
|
98
|
-
"You can find how to type a
|
|
104
|
+
"You can find how to type a latex symbol by back completing it `\\θ<tab>` will expand to `\\theta`.",
|
|
99
105
|
"You can find how to type a unicode symbol by back completing it `\\Ⅷ<tab>` will expand to `\\ROMAN NUMERAL EIGHT`.",
|
|
100
106
|
"IPython support combining unicode identifiers, F\\vec<tab> will become F⃗, usefull for physics equations. Play with \\dot \\ddot and others.",
|
|
101
107
|
]
|
|
File without changes
|
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import ast
|
|
3
|
+
import builtins
|
|
4
|
+
import contextlib
|
|
5
|
+
import itertools
|
|
6
|
+
import os
|
|
7
|
+
import platform
|
|
8
|
+
import sys
|
|
9
|
+
import textwrap
|
|
10
|
+
from types import ModuleType
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Generator, Iterable, NamedTuple, cast
|
|
12
|
+
|
|
13
|
+
from IPython.extensions.deduperreload.deduperreload_patching import (
|
|
14
|
+
DeduperReloaderPatchingMixin,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
TDefinitionAst = (
|
|
19
|
+
ast.FunctionDef
|
|
20
|
+
| ast.AsyncFunctionDef
|
|
21
|
+
| ast.Import
|
|
22
|
+
| ast.ImportFrom
|
|
23
|
+
| ast.Assign
|
|
24
|
+
| ast.AnnAssign
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_module_file_name(module: ModuleType | str) -> str:
|
|
29
|
+
"""Returns the module's file path, or the empty string if it's inaccessible"""
|
|
30
|
+
if (mod := sys.modules.get(module) if isinstance(module, str) else module) is None:
|
|
31
|
+
return ""
|
|
32
|
+
return getattr(mod, "__file__", "") or ""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def compare_ast(node1: ast.AST | list[ast.AST], node2: ast.AST | list[ast.AST]) -> bool:
|
|
36
|
+
"""Checks if node1 and node2 have identical AST structure/values, apart from some attributes"""
|
|
37
|
+
if type(node1) is not type(node2):
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
if isinstance(node1, ast.AST):
|
|
41
|
+
for k, v in node1.__dict__.items():
|
|
42
|
+
if k in (
|
|
43
|
+
"lineno",
|
|
44
|
+
"end_lineno",
|
|
45
|
+
"col_offset",
|
|
46
|
+
"end_col_offset",
|
|
47
|
+
"ctx",
|
|
48
|
+
"parent",
|
|
49
|
+
):
|
|
50
|
+
continue
|
|
51
|
+
if not hasattr(node2, k) or not compare_ast(v, getattr(node2, k)):
|
|
52
|
+
return False
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
elif isinstance(node1, list) and isinstance( # type:ignore [redundant-expr]
|
|
56
|
+
node2, list
|
|
57
|
+
):
|
|
58
|
+
return len(node1) == len(node2) and all(
|
|
59
|
+
compare_ast(n1, n2) for n1, n2 in zip(node1, node2)
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
return node1 == node2
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class DependencyNode(NamedTuple):
|
|
66
|
+
"""
|
|
67
|
+
Each node represents a function.
|
|
68
|
+
qualified_name: string which represents the namespace/name of the function
|
|
69
|
+
abstract_syntax_tree: subtree of the overall module which corresponds to this function
|
|
70
|
+
|
|
71
|
+
qualified_name is of the structure: (namespace1, namespace2, ..., name)
|
|
72
|
+
|
|
73
|
+
For example, foo() in the following would be represented as (A, B, foo):
|
|
74
|
+
|
|
75
|
+
class A:
|
|
76
|
+
class B:
|
|
77
|
+
def foo():
|
|
78
|
+
pass
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
qualified_name: tuple[str, ...]
|
|
82
|
+
abstract_syntax_tree: ast.AST
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class GatherResult(NamedTuple):
|
|
86
|
+
import_defs: list[tuple[tuple[str, ...], ast.Import | ast.ImportFrom]] = []
|
|
87
|
+
assign_defs: list[tuple[tuple[str, ...], ast.Assign | ast.AnnAssign]] = []
|
|
88
|
+
function_defs: list[
|
|
89
|
+
tuple[tuple[str, ...], ast.FunctionDef | ast.AsyncFunctionDef]
|
|
90
|
+
] = []
|
|
91
|
+
classes: dict[str, ast.ClassDef] = {}
|
|
92
|
+
unfixable: list[ast.AST] = []
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def create(cls) -> GatherResult:
|
|
96
|
+
return cls([], [], [], {}, [])
|
|
97
|
+
|
|
98
|
+
def all_defs(self) -> Iterable[tuple[tuple[str, ...], TDefinitionAst]]:
|
|
99
|
+
return itertools.chain(self.import_defs, self.assign_defs, self.function_defs)
|
|
100
|
+
|
|
101
|
+
def inplace_merge(self, other: GatherResult) -> None:
|
|
102
|
+
self.import_defs.extend(other.import_defs)
|
|
103
|
+
self.assign_defs.extend(other.assign_defs)
|
|
104
|
+
self.function_defs.extend(other.function_defs)
|
|
105
|
+
self.classes.update(other.classes)
|
|
106
|
+
self.unfixable.extend(other.unfixable)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ConstexprDetector(ast.NodeVisitor):
|
|
110
|
+
def __init__(self) -> None:
|
|
111
|
+
self.is_constexpr = True
|
|
112
|
+
self._allow_builtins_exceptions = True
|
|
113
|
+
|
|
114
|
+
@contextlib.contextmanager
|
|
115
|
+
def disallow_builtins_exceptions(self) -> Generator[None, None, None]:
|
|
116
|
+
prev_allow = self._allow_builtins_exceptions
|
|
117
|
+
self._allow_builtins_exceptions = False
|
|
118
|
+
try:
|
|
119
|
+
yield
|
|
120
|
+
finally:
|
|
121
|
+
self._allow_builtins_exceptions = prev_allow
|
|
122
|
+
|
|
123
|
+
def visit_Attribute(self, node: ast.Attribute) -> None:
|
|
124
|
+
with self.disallow_builtins_exceptions():
|
|
125
|
+
self.visit(node.value)
|
|
126
|
+
|
|
127
|
+
def visit_Name(self, node: ast.Name) -> None:
|
|
128
|
+
if self._allow_builtins_exceptions and hasattr(builtins, node.id):
|
|
129
|
+
return
|
|
130
|
+
self.is_constexpr = False
|
|
131
|
+
|
|
132
|
+
def visit(self, node: ast.AST) -> None:
|
|
133
|
+
if not self.is_constexpr:
|
|
134
|
+
# can short-circuit if we've already detected that it's not a constexpr
|
|
135
|
+
return
|
|
136
|
+
super().visit(node)
|
|
137
|
+
|
|
138
|
+
def __call__(self, node: ast.AST) -> bool:
|
|
139
|
+
self.is_constexpr = True
|
|
140
|
+
self.visit(node)
|
|
141
|
+
return self.is_constexpr
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class AutoreloadTree:
|
|
145
|
+
"""
|
|
146
|
+
Recursive data structure to keep track of reloadable functions/methods. Each object corresponds to a specific scope level.
|
|
147
|
+
children: classes inside given scope, maps class name to autoreload tree for that class's scope
|
|
148
|
+
funcs_to_autoreload: list of function names that can be autoreloaded in given scope.
|
|
149
|
+
new_nested_classes: Classes getting added in new autoreload cycle
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
def __init__(self) -> None:
|
|
153
|
+
self.children: dict[str, AutoreloadTree] = {}
|
|
154
|
+
self.defs_to_reload: list[tuple[tuple[str, ...], ast.AST]] = []
|
|
155
|
+
self.defs_to_delete: set[str] = set()
|
|
156
|
+
self.new_nested_classes: dict[str, ast.AST] = {}
|
|
157
|
+
|
|
158
|
+
def traverse_prefixes(self, prefixes: list[str]) -> AutoreloadTree:
|
|
159
|
+
"""
|
|
160
|
+
Return ref to the AutoreloadTree at the namespace specified by prefixes
|
|
161
|
+
"""
|
|
162
|
+
cur = self
|
|
163
|
+
for prefix in prefixes:
|
|
164
|
+
if prefix not in cur.children:
|
|
165
|
+
cur.children[prefix] = AutoreloadTree()
|
|
166
|
+
cur = cur.children[prefix]
|
|
167
|
+
return cur
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class DeduperReloader(DeduperReloaderPatchingMixin):
|
|
171
|
+
"""
|
|
172
|
+
This version of autoreload detects when we can leverage targeted recompilation of a subset of a module and patching
|
|
173
|
+
existing function/method objects to reflect these changes.
|
|
174
|
+
|
|
175
|
+
Detects what functions/methods can be reloaded by recursively comparing the old/new AST of module-level classes,
|
|
176
|
+
module-level classes' methods, recursing through nested classes' methods. If other changes are made, original
|
|
177
|
+
autoreload algorithm is called directly.
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
def __init__(self) -> None:
|
|
181
|
+
self._to_autoreload: AutoreloadTree = AutoreloadTree()
|
|
182
|
+
self.source_by_modname: dict[str, str] = {}
|
|
183
|
+
self.dependency_graph: dict[tuple[str, ...], list[DependencyNode]] = {}
|
|
184
|
+
self._enabled = True
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def enabled(self) -> bool:
|
|
188
|
+
return self._enabled and platform.python_implementation() == "CPython"
|
|
189
|
+
|
|
190
|
+
@enabled.setter
|
|
191
|
+
def enabled(self, value: bool) -> None:
|
|
192
|
+
self._enabled = value
|
|
193
|
+
|
|
194
|
+
def update_sources(self) -> None:
|
|
195
|
+
"""
|
|
196
|
+
Update dictionary source_by_modname with current modules' source codes.
|
|
197
|
+
"""
|
|
198
|
+
if not self.enabled:
|
|
199
|
+
return
|
|
200
|
+
for new_modname in sys.modules.keys() - self.source_by_modname.keys():
|
|
201
|
+
new_module = sys.modules[new_modname]
|
|
202
|
+
if (
|
|
203
|
+
(fname := get_module_file_name(new_module))
|
|
204
|
+
is None # type:ignore [redundant-expr]
|
|
205
|
+
or "site-packages" in fname
|
|
206
|
+
or "dist-packages" in fname
|
|
207
|
+
or not os.access(fname, os.R_OK)
|
|
208
|
+
):
|
|
209
|
+
self.source_by_modname[new_modname] = ""
|
|
210
|
+
continue
|
|
211
|
+
with open(fname, "r") as f:
|
|
212
|
+
try:
|
|
213
|
+
self.source_by_modname[new_modname] = f.read()
|
|
214
|
+
except Exception:
|
|
215
|
+
self.source_by_modname[new_modname] = ""
|
|
216
|
+
|
|
217
|
+
constexpr_detector = ConstexprDetector()
|
|
218
|
+
|
|
219
|
+
@staticmethod
|
|
220
|
+
def is_enum_subclass(node: ast.Module | ast.ClassDef) -> bool:
|
|
221
|
+
if isinstance(node, ast.Module):
|
|
222
|
+
return False
|
|
223
|
+
for base in node.bases:
|
|
224
|
+
if isinstance(base, ast.Name) and base.id == "Enum":
|
|
225
|
+
return True
|
|
226
|
+
elif (
|
|
227
|
+
isinstance(base, ast.Attribute)
|
|
228
|
+
and base.attr == "Enum"
|
|
229
|
+
and isinstance(base.value, ast.Name)
|
|
230
|
+
and base.value.id == "enum"
|
|
231
|
+
):
|
|
232
|
+
return True
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
@classmethod
|
|
236
|
+
def is_constexpr_assign(
|
|
237
|
+
cls, node: ast.AST, parent_node: ast.Module | ast.ClassDef
|
|
238
|
+
) -> bool:
|
|
239
|
+
if not isinstance(node, (ast.Assign, ast.AnnAssign)) or node.value is None:
|
|
240
|
+
return False
|
|
241
|
+
if cls.is_enum_subclass(parent_node):
|
|
242
|
+
return False
|
|
243
|
+
for target in node.targets if isinstance(node, ast.Assign) else [node.target]:
|
|
244
|
+
if not isinstance(target, ast.Name):
|
|
245
|
+
return False
|
|
246
|
+
return cls.constexpr_detector(node.value)
|
|
247
|
+
|
|
248
|
+
@classmethod
|
|
249
|
+
def _gather_children(
|
|
250
|
+
cls, body: list[ast.stmt], parent_node: ast.Module | ast.ClassDef
|
|
251
|
+
) -> GatherResult:
|
|
252
|
+
"""
|
|
253
|
+
Given list of ast elements, return:
|
|
254
|
+
1. dict mapping function names to their ASTs.
|
|
255
|
+
2. dict mapping class names to their ASTs.
|
|
256
|
+
3. list of any other ASTs.
|
|
257
|
+
"""
|
|
258
|
+
result = GatherResult.create()
|
|
259
|
+
for ast_node in body:
|
|
260
|
+
ast_elt: ast.expr | ast.stmt = ast_node
|
|
261
|
+
while isinstance(ast_elt, ast.Expr):
|
|
262
|
+
ast_elt = ast_elt.value
|
|
263
|
+
if isinstance(ast_elt, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
264
|
+
result.function_defs.append(((ast_elt.name,), ast_elt))
|
|
265
|
+
elif isinstance(ast_elt, (ast.Import, ast.ImportFrom)):
|
|
266
|
+
result.import_defs.append(
|
|
267
|
+
(tuple(name.asname or name.name for name in ast_elt.names), ast_elt)
|
|
268
|
+
)
|
|
269
|
+
elif isinstance(ast_elt, ast.ClassDef):
|
|
270
|
+
result.classes[ast_elt.name] = ast_elt
|
|
271
|
+
elif isinstance(ast_elt, ast.If):
|
|
272
|
+
result.unfixable.append(ast_elt.test)
|
|
273
|
+
result.inplace_merge(cls._gather_children(ast_elt.body, parent_node))
|
|
274
|
+
result.inplace_merge(cls._gather_children(ast_elt.orelse, parent_node))
|
|
275
|
+
elif isinstance(ast_elt, (ast.AsyncWith, ast.With)):
|
|
276
|
+
result.unfixable.extend(ast_elt.items)
|
|
277
|
+
result.inplace_merge(cls._gather_children(ast_elt.body, parent_node))
|
|
278
|
+
elif isinstance(ast_elt, ast.Try):
|
|
279
|
+
result.inplace_merge(cls._gather_children(ast_elt.body, parent_node))
|
|
280
|
+
result.inplace_merge(cls._gather_children(ast_elt.orelse, parent_node))
|
|
281
|
+
result.inplace_merge(
|
|
282
|
+
cls._gather_children(ast_elt.finalbody, parent_node)
|
|
283
|
+
)
|
|
284
|
+
for handler in ast_elt.handlers:
|
|
285
|
+
if handler.type is not None:
|
|
286
|
+
result.unfixable.append(handler.type)
|
|
287
|
+
result.inplace_merge(
|
|
288
|
+
cls._gather_children(handler.body, parent_node)
|
|
289
|
+
)
|
|
290
|
+
elif not isinstance(ast_elt, (ast.Ellipsis, ast.Pass)):
|
|
291
|
+
if cls.is_constexpr_assign(ast_elt, parent_node):
|
|
292
|
+
assert isinstance(ast_elt, (ast.Assign, ast.AnnAssign))
|
|
293
|
+
targets = (
|
|
294
|
+
ast_elt.targets
|
|
295
|
+
if isinstance(ast_elt, ast.Assign)
|
|
296
|
+
else [ast_elt.target]
|
|
297
|
+
)
|
|
298
|
+
result.assign_defs.append(
|
|
299
|
+
(
|
|
300
|
+
tuple(cast(ast.Name, target).id for target in targets),
|
|
301
|
+
ast_elt,
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
else:
|
|
305
|
+
result.unfixable.append(ast_elt)
|
|
306
|
+
return result
|
|
307
|
+
|
|
308
|
+
def detect_autoreload(
|
|
309
|
+
self,
|
|
310
|
+
old_node: ast.Module | ast.ClassDef,
|
|
311
|
+
new_node: ast.Module | ast.ClassDef,
|
|
312
|
+
prefixes: list[str] | None = None,
|
|
313
|
+
) -> bool:
|
|
314
|
+
"""
|
|
315
|
+
Returns
|
|
316
|
+
-------
|
|
317
|
+
`True` if we can run our targeted autoreload algorithm safely.
|
|
318
|
+
`False` if we should instead use IPython's original autoreload implementation.
|
|
319
|
+
"""
|
|
320
|
+
if not self.enabled:
|
|
321
|
+
return False
|
|
322
|
+
prefixes = prefixes or []
|
|
323
|
+
|
|
324
|
+
old_result = self._gather_children(old_node.body, old_node)
|
|
325
|
+
new_result = self._gather_children(new_node.body, new_node)
|
|
326
|
+
old_defs_by_name: dict[str, ast.AST] = {
|
|
327
|
+
name: ast_def for names, ast_def in old_result.all_defs() for name in names
|
|
328
|
+
}
|
|
329
|
+
new_defs_by_name: dict[str, ast.AST] = {
|
|
330
|
+
name: ast_def for names, ast_def in new_result.all_defs() for name in names
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if not compare_ast(old_result.unfixable, new_result.unfixable):
|
|
334
|
+
return False
|
|
335
|
+
|
|
336
|
+
cur = self._to_autoreload.traverse_prefixes(prefixes)
|
|
337
|
+
for names, new_ast_def in new_result.all_defs():
|
|
338
|
+
names_to_reload = []
|
|
339
|
+
for name in names:
|
|
340
|
+
if new_defs_by_name[name] is not new_ast_def:
|
|
341
|
+
continue
|
|
342
|
+
if name not in old_defs_by_name or not compare_ast(
|
|
343
|
+
new_ast_def, old_defs_by_name[name]
|
|
344
|
+
):
|
|
345
|
+
names_to_reload.append(name)
|
|
346
|
+
if names_to_reload:
|
|
347
|
+
cur.defs_to_reload.append((tuple(names), new_ast_def))
|
|
348
|
+
cur.defs_to_delete |= set(old_defs_by_name.keys()) - set(
|
|
349
|
+
new_defs_by_name.keys()
|
|
350
|
+
)
|
|
351
|
+
for name, new_ast_def_class in new_result.classes.items():
|
|
352
|
+
if name not in old_result.classes:
|
|
353
|
+
cur.new_nested_classes[name] = new_ast_def_class
|
|
354
|
+
elif not compare_ast(
|
|
355
|
+
new_ast_def_class, old_result.classes[name]
|
|
356
|
+
) and not self.detect_autoreload(
|
|
357
|
+
old_result.classes[name], new_ast_def_class, prefixes + [name]
|
|
358
|
+
):
|
|
359
|
+
return False
|
|
360
|
+
return True
|
|
361
|
+
|
|
362
|
+
def _check_dependents(self) -> bool:
|
|
363
|
+
"""
|
|
364
|
+
If a decorator function is modified, we should similarly reload the functions which are decorated by this
|
|
365
|
+
decorator. Iterate through the Dependency Graph to find such cases in the given AutoreloadTree.
|
|
366
|
+
"""
|
|
367
|
+
for node in self._check_dependents_inner():
|
|
368
|
+
self._add_node_to_autoreload_tree(node)
|
|
369
|
+
return True
|
|
370
|
+
|
|
371
|
+
def _add_node_to_autoreload_tree(self, node: DependencyNode) -> None:
|
|
372
|
+
"""
|
|
373
|
+
Given a node of the dependency graph, add decorator dependencies to the autoreload tree.
|
|
374
|
+
"""
|
|
375
|
+
if len(node.qualified_name) == 0:
|
|
376
|
+
return
|
|
377
|
+
cur = self._to_autoreload.traverse_prefixes(list(node.qualified_name[:-1]))
|
|
378
|
+
if node.abstract_syntax_tree is not None:
|
|
379
|
+
cur.defs_to_reload.append(
|
|
380
|
+
((node.qualified_name[-1],), node.abstract_syntax_tree)
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
def _check_dependents_inner(
|
|
384
|
+
self, prefixes: list[str] | None = None
|
|
385
|
+
) -> list[DependencyNode]:
|
|
386
|
+
prefixes = prefixes or []
|
|
387
|
+
cur = self._to_autoreload.traverse_prefixes(prefixes)
|
|
388
|
+
ans = []
|
|
389
|
+
for (func_name, *_), _ in cur.defs_to_reload:
|
|
390
|
+
node = tuple(prefixes + [func_name])
|
|
391
|
+
ans.extend(self._gen_dependents(node))
|
|
392
|
+
for class_name in cur.new_nested_classes:
|
|
393
|
+
ans.extend(self._check_dependents_inner(prefixes + [class_name]))
|
|
394
|
+
return ans
|
|
395
|
+
|
|
396
|
+
def _gen_dependents(self, qualname: tuple[str, ...]) -> list[DependencyNode]:
|
|
397
|
+
ans = []
|
|
398
|
+
if qualname not in self.dependency_graph:
|
|
399
|
+
return []
|
|
400
|
+
for elt in self.dependency_graph[qualname]:
|
|
401
|
+
ans.extend(self._gen_dependents(elt.qualified_name))
|
|
402
|
+
ans.append(elt)
|
|
403
|
+
return ans
|
|
404
|
+
|
|
405
|
+
def _patch_namespace_inner(
|
|
406
|
+
self, ns: ModuleType | type, prefixes: list[str] | None = None
|
|
407
|
+
) -> bool:
|
|
408
|
+
"""
|
|
409
|
+
This function patches module functions and methods. Specifically, only objects with their name in
|
|
410
|
+
self.to_autoreload will be considered for patching. If an object has been marked to be autoreloaded,
|
|
411
|
+
new_source_code gets executed in the old version's global environment. Then, replace the old function's
|
|
412
|
+
attributes with the new function's attributes.
|
|
413
|
+
"""
|
|
414
|
+
prefixes = prefixes or []
|
|
415
|
+
cur = self._to_autoreload.traverse_prefixes(prefixes)
|
|
416
|
+
namespace_to_check = ns
|
|
417
|
+
for prefix in prefixes:
|
|
418
|
+
namespace_to_check = namespace_to_check.__dict__[prefix]
|
|
419
|
+
for names, new_ast_def in cur.defs_to_reload:
|
|
420
|
+
local_env: dict[str, Any] = {}
|
|
421
|
+
if (
|
|
422
|
+
isinstance(new_ast_def, (ast.FunctionDef, ast.AsyncFunctionDef))
|
|
423
|
+
and (name := names[0]) in namespace_to_check.__dict__
|
|
424
|
+
):
|
|
425
|
+
assert len(names) == 1
|
|
426
|
+
to_patch_to = namespace_to_check.__dict__[name]
|
|
427
|
+
if isinstance(to_patch_to, (staticmethod, classmethod)):
|
|
428
|
+
to_patch_to = to_patch_to.__func__
|
|
429
|
+
# exec new source code using old function's (obj) globals environment.
|
|
430
|
+
func_code = textwrap.dedent(ast.unparse(new_ast_def))
|
|
431
|
+
if is_method := (len(prefixes) > 0):
|
|
432
|
+
func_code = "class __autoreload_class__:\n" + textwrap.indent(
|
|
433
|
+
func_code, " "
|
|
434
|
+
)
|
|
435
|
+
global_env = namespace_to_check.__dict__
|
|
436
|
+
if hasattr(to_patch_to, "__globals__"):
|
|
437
|
+
global_env = to_patch_to.__globals__
|
|
438
|
+
elif isinstance(to_patch_to, property):
|
|
439
|
+
if to_patch_to.fget is not None:
|
|
440
|
+
global_env = to_patch_to.fget.__globals__
|
|
441
|
+
elif to_patch_to.fset is not None:
|
|
442
|
+
global_env = to_patch_to.fset.__globals__
|
|
443
|
+
elif to_patch_to.fdel is not None:
|
|
444
|
+
global_env = to_patch_to.fdel.__globals__
|
|
445
|
+
if not isinstance(global_env, dict):
|
|
446
|
+
global_env = dict(global_env)
|
|
447
|
+
exec(func_code, global_env, local_env) # type: ignore[arg-type]
|
|
448
|
+
# local_env contains the function exec'd from new version of function
|
|
449
|
+
if is_method:
|
|
450
|
+
to_patch_from = getattr(local_env["__autoreload_class__"], name)
|
|
451
|
+
else:
|
|
452
|
+
to_patch_from = local_env[name]
|
|
453
|
+
if isinstance(to_patch_from, (staticmethod, classmethod)):
|
|
454
|
+
to_patch_from = to_patch_from.__func__
|
|
455
|
+
if isinstance(to_patch_to, property) and isinstance(
|
|
456
|
+
to_patch_from, property
|
|
457
|
+
):
|
|
458
|
+
for attr in ("fget", "fset", "fdel"):
|
|
459
|
+
if (
|
|
460
|
+
getattr(to_patch_to, attr) is None
|
|
461
|
+
or getattr(to_patch_from, attr) is None
|
|
462
|
+
):
|
|
463
|
+
self.try_patch_attr(to_patch_to, to_patch_from, attr)
|
|
464
|
+
else:
|
|
465
|
+
self.patch_function(
|
|
466
|
+
getattr(to_patch_to, attr),
|
|
467
|
+
getattr(to_patch_from, attr),
|
|
468
|
+
is_method,
|
|
469
|
+
)
|
|
470
|
+
elif not isinstance(to_patch_to, property) and not isinstance(
|
|
471
|
+
to_patch_from, property
|
|
472
|
+
):
|
|
473
|
+
self.patch_function(to_patch_to, to_patch_from, is_method)
|
|
474
|
+
else:
|
|
475
|
+
raise ValueError(
|
|
476
|
+
"adding or removing property decorations not supported"
|
|
477
|
+
)
|
|
478
|
+
else:
|
|
479
|
+
exec(
|
|
480
|
+
ast.unparse(new_ast_def),
|
|
481
|
+
ns.__dict__ | namespace_to_check.__dict__,
|
|
482
|
+
local_env,
|
|
483
|
+
)
|
|
484
|
+
for name in names:
|
|
485
|
+
setattr(namespace_to_check, name, local_env[name])
|
|
486
|
+
cur.defs_to_reload.clear()
|
|
487
|
+
for name in cur.defs_to_delete:
|
|
488
|
+
try:
|
|
489
|
+
delattr(namespace_to_check, name)
|
|
490
|
+
except (AttributeError, TypeError, ValueError):
|
|
491
|
+
# give up on deleting the attribute, let the stale one dangle
|
|
492
|
+
pass
|
|
493
|
+
cur.defs_to_delete.clear()
|
|
494
|
+
for class_name, class_ast_node in cur.new_nested_classes.items():
|
|
495
|
+
local_env_class: dict[str, Any] = {}
|
|
496
|
+
exec(
|
|
497
|
+
ast.unparse(class_ast_node),
|
|
498
|
+
ns.__dict__ | namespace_to_check.__dict__,
|
|
499
|
+
local_env_class,
|
|
500
|
+
)
|
|
501
|
+
setattr(namespace_to_check, class_name, local_env_class[class_name])
|
|
502
|
+
cur.new_nested_classes.clear()
|
|
503
|
+
for class_name in cur.children.keys():
|
|
504
|
+
if not self._patch_namespace(ns, prefixes + [class_name]):
|
|
505
|
+
return False
|
|
506
|
+
cur.children.clear()
|
|
507
|
+
return True
|
|
508
|
+
|
|
509
|
+
def _patch_namespace(
|
|
510
|
+
self, ns: ModuleType | type, prefixes: list[str] | None = None
|
|
511
|
+
) -> bool:
|
|
512
|
+
"""
|
|
513
|
+
Wrapper for patching all elements in a namespace as specified by the to_autoreload member variable.
|
|
514
|
+
Returns `true` if patching was successful, and `false` if unsuccessful.
|
|
515
|
+
"""
|
|
516
|
+
try:
|
|
517
|
+
return self._patch_namespace_inner(ns, prefixes=prefixes)
|
|
518
|
+
except Exception:
|
|
519
|
+
return False
|
|
520
|
+
|
|
521
|
+
def maybe_reload_module(self, module: ModuleType) -> bool:
|
|
522
|
+
"""
|
|
523
|
+
Uses Deduperreload to try to update a module.
|
|
524
|
+
Returns `true` on success and `false` on failure.
|
|
525
|
+
"""
|
|
526
|
+
if not self.enabled:
|
|
527
|
+
return False
|
|
528
|
+
if not (modname := getattr(module, "__name__", None)):
|
|
529
|
+
return False
|
|
530
|
+
if (fname := get_module_file_name(module)) is None:
|
|
531
|
+
return False
|
|
532
|
+
with open(fname, "r") as f:
|
|
533
|
+
new_source_code = f.read()
|
|
534
|
+
patched_flag = False
|
|
535
|
+
if old_source_code := self.source_by_modname.get(modname):
|
|
536
|
+
# get old/new module ast
|
|
537
|
+
try:
|
|
538
|
+
old_module_ast = ast.parse(old_source_code)
|
|
539
|
+
new_module_ast = ast.parse(new_source_code)
|
|
540
|
+
except Exception:
|
|
541
|
+
return False
|
|
542
|
+
# detect if we are able to use our autoreload algorithm
|
|
543
|
+
ctx = contextlib.suppress()
|
|
544
|
+
with ctx:
|
|
545
|
+
self._build_dependency_graph(new_module_ast)
|
|
546
|
+
if (
|
|
547
|
+
self.detect_autoreload(old_module_ast, new_module_ast)
|
|
548
|
+
and self._check_dependents()
|
|
549
|
+
and self._patch_namespace(module)
|
|
550
|
+
):
|
|
551
|
+
patched_flag = True
|
|
552
|
+
|
|
553
|
+
self.source_by_modname[modname] = new_source_code
|
|
554
|
+
self._to_autoreload = AutoreloadTree()
|
|
555
|
+
return patched_flag
|
|
556
|
+
|
|
557
|
+
def _separate_name(
|
|
558
|
+
self,
|
|
559
|
+
decorator: ast.Attribute | ast.Name | ast.Call | ast.expr,
|
|
560
|
+
accept_calls: bool,
|
|
561
|
+
) -> list[str] | None:
|
|
562
|
+
"""
|
|
563
|
+
Generates a qualified name for a given decorator by finding its relative namespace.
|
|
564
|
+
"""
|
|
565
|
+
if isinstance(decorator, ast.Name):
|
|
566
|
+
return [decorator.id]
|
|
567
|
+
elif isinstance(decorator, ast.Call):
|
|
568
|
+
if accept_calls:
|
|
569
|
+
return self._separate_name(decorator.func, False)
|
|
570
|
+
else:
|
|
571
|
+
return None
|
|
572
|
+
if not isinstance(decorator, ast.Attribute):
|
|
573
|
+
return None
|
|
574
|
+
if pref := self._separate_name(decorator.value, False):
|
|
575
|
+
return pref + [decorator.attr]
|
|
576
|
+
else:
|
|
577
|
+
return None
|
|
578
|
+
|
|
579
|
+
def _gather_dependents(
|
|
580
|
+
self, body: list[ast.stmt], body_prefixes: list[str] | None = None
|
|
581
|
+
) -> bool:
|
|
582
|
+
body_prefixes = body_prefixes or []
|
|
583
|
+
for ast_node in body:
|
|
584
|
+
ast_elt: ast.expr | ast.stmt = ast_node
|
|
585
|
+
if isinstance(ast_elt, ast.ClassDef):
|
|
586
|
+
self._gather_dependents(ast_elt.body, body_prefixes + [ast_elt.name])
|
|
587
|
+
continue
|
|
588
|
+
if not isinstance(ast_elt, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
589
|
+
continue
|
|
590
|
+
qualified_name = tuple(body_prefixes + [ast_elt.name])
|
|
591
|
+
cur_dependency_node = DependencyNode(qualified_name, ast_elt)
|
|
592
|
+
for decorator in ast_elt.decorator_list:
|
|
593
|
+
decorator_path = self._separate_name(decorator, True)
|
|
594
|
+
if not decorator_path:
|
|
595
|
+
continue
|
|
596
|
+
decorator_path_tuple = tuple(decorator_path)
|
|
597
|
+
self.dependency_graph.setdefault(decorator_path_tuple, []).append(
|
|
598
|
+
cur_dependency_node
|
|
599
|
+
)
|
|
600
|
+
return True
|
|
601
|
+
|
|
602
|
+
def _build_dependency_graph(self, new_ast: ast.Module | ast.ClassDef) -> bool:
|
|
603
|
+
"""
|
|
604
|
+
Wrapper function for generating dependency graph given some AST.
|
|
605
|
+
Returns `true` on success. Returns `false` on failure.
|
|
606
|
+
Currently, only returns `true` as we do not block on failure to build this graph.
|
|
607
|
+
"""
|
|
608
|
+
return self._gather_dependents(new_ast.body)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import ctypes
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
NOT_FOUND: object = object()
|
|
7
|
+
_MAX_FIELD_SEARCH_OFFSET = 50
|
|
8
|
+
|
|
9
|
+
if sys.maxsize > 2**32:
|
|
10
|
+
WORD_TYPE: type[ctypes.c_int32] | type[ctypes.c_int64] = ctypes.c_int64
|
|
11
|
+
WORD_N_BYTES = 8
|
|
12
|
+
else:
|
|
13
|
+
WORD_TYPE = ctypes.c_int32
|
|
14
|
+
WORD_N_BYTES = 4
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DeduperReloaderPatchingMixin:
|
|
18
|
+
@staticmethod
|
|
19
|
+
def infer_field_offset(
|
|
20
|
+
obj: object,
|
|
21
|
+
field: str,
|
|
22
|
+
) -> int:
|
|
23
|
+
field_value = getattr(obj, field, NOT_FOUND)
|
|
24
|
+
if field_value is NOT_FOUND:
|
|
25
|
+
return -1
|
|
26
|
+
obj_addr = ctypes.c_void_p.from_buffer(ctypes.py_object(obj)).value
|
|
27
|
+
field_addr = ctypes.c_void_p.from_buffer(ctypes.py_object(field_value)).value
|
|
28
|
+
if obj_addr is None or field_addr is None:
|
|
29
|
+
return -1
|
|
30
|
+
ret = -1
|
|
31
|
+
for offset in range(1, _MAX_FIELD_SEARCH_OFFSET):
|
|
32
|
+
if (
|
|
33
|
+
ctypes.cast(
|
|
34
|
+
obj_addr + WORD_N_BYTES * offset, ctypes.POINTER(WORD_TYPE)
|
|
35
|
+
).contents.value
|
|
36
|
+
== field_addr
|
|
37
|
+
):
|
|
38
|
+
ret = offset
|
|
39
|
+
break
|
|
40
|
+
return ret
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def try_write_readonly_attr(
|
|
44
|
+
cls,
|
|
45
|
+
obj: object,
|
|
46
|
+
field: str,
|
|
47
|
+
new_value: object,
|
|
48
|
+
offset: int | None = None,
|
|
49
|
+
) -> None:
|
|
50
|
+
prev_value = getattr(obj, field, NOT_FOUND)
|
|
51
|
+
if prev_value is NOT_FOUND:
|
|
52
|
+
return
|
|
53
|
+
if offset is None:
|
|
54
|
+
offset = cls.infer_field_offset(obj, field)
|
|
55
|
+
if offset == -1:
|
|
56
|
+
return
|
|
57
|
+
obj_addr = ctypes.c_void_p.from_buffer(ctypes.py_object(obj)).value
|
|
58
|
+
new_value_addr = ctypes.c_void_p.from_buffer(ctypes.py_object(new_value)).value
|
|
59
|
+
if obj_addr is None or new_value_addr is None:
|
|
60
|
+
return
|
|
61
|
+
if prev_value is not None:
|
|
62
|
+
ctypes.pythonapi.Py_DecRef(ctypes.py_object(prev_value))
|
|
63
|
+
if new_value is not None:
|
|
64
|
+
ctypes.pythonapi.Py_IncRef(ctypes.py_object(new_value))
|
|
65
|
+
ctypes.cast(
|
|
66
|
+
obj_addr + WORD_N_BYTES * offset, ctypes.POINTER(WORD_TYPE)
|
|
67
|
+
).contents.value = new_value_addr
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def try_patch_readonly_attr(
|
|
71
|
+
cls,
|
|
72
|
+
old: object,
|
|
73
|
+
new: object,
|
|
74
|
+
field: str,
|
|
75
|
+
new_is_value: bool = False,
|
|
76
|
+
offset: int = -1,
|
|
77
|
+
) -> None:
|
|
78
|
+
|
|
79
|
+
old_value = getattr(old, field, NOT_FOUND)
|
|
80
|
+
new_value = new if new_is_value else getattr(new, field, NOT_FOUND)
|
|
81
|
+
if old_value is NOT_FOUND or new_value is NOT_FOUND:
|
|
82
|
+
return
|
|
83
|
+
elif old_value is new_value:
|
|
84
|
+
return
|
|
85
|
+
elif old_value is not None and offset < 0:
|
|
86
|
+
offset = cls.infer_field_offset(old, field)
|
|
87
|
+
elif offset < 0:
|
|
88
|
+
assert not new_is_value
|
|
89
|
+
assert new_value is not None
|
|
90
|
+
offset = cls.infer_field_offset(new, field)
|
|
91
|
+
cls.try_write_readonly_attr(old, field, new_value, offset=offset)
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def try_patch_attr(
|
|
95
|
+
cls,
|
|
96
|
+
old: object,
|
|
97
|
+
new: object,
|
|
98
|
+
field: str,
|
|
99
|
+
new_is_value: bool = False,
|
|
100
|
+
offset: int = -1,
|
|
101
|
+
) -> None:
|
|
102
|
+
try:
|
|
103
|
+
setattr(old, field, new if new_is_value else getattr(new, field))
|
|
104
|
+
except (AttributeError, TypeError, ValueError):
|
|
105
|
+
cls.try_patch_readonly_attr(old, new, field, new_is_value, offset)
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def patch_function(
|
|
109
|
+
cls, to_patch_to: Any, to_patch_from: Any, is_method: bool
|
|
110
|
+
) -> None:
|
|
111
|
+
new_freevars = []
|
|
112
|
+
new_closure = []
|
|
113
|
+
for i, v in enumerate(to_patch_to.__code__.co_freevars):
|
|
114
|
+
if v not in to_patch_from.__code__.co_freevars or v == "__class__":
|
|
115
|
+
new_freevars.append(v)
|
|
116
|
+
new_closure.append(to_patch_to.__closure__[i])
|
|
117
|
+
for i, v in enumerate(to_patch_from.__code__.co_freevars):
|
|
118
|
+
if v not in new_freevars:
|
|
119
|
+
new_freevars.append(v)
|
|
120
|
+
new_closure.append(to_patch_from.__closure__[i])
|
|
121
|
+
code_with_new_freevars = to_patch_from.__code__.replace(
|
|
122
|
+
co_freevars=tuple(new_freevars)
|
|
123
|
+
)
|
|
124
|
+
# lambdas may complain if there is more than one freevar
|
|
125
|
+
cls.try_patch_attr(
|
|
126
|
+
to_patch_to, code_with_new_freevars, "__code__", new_is_value=True
|
|
127
|
+
)
|
|
128
|
+
offset = -1
|
|
129
|
+
if to_patch_to.__closure__ is None and to_patch_from.__closure__ is not None:
|
|
130
|
+
offset = cls.infer_field_offset(to_patch_from, "__closure__")
|
|
131
|
+
cls.try_patch_readonly_attr(
|
|
132
|
+
to_patch_to,
|
|
133
|
+
tuple(new_closure) or None,
|
|
134
|
+
"__closure__",
|
|
135
|
+
new_is_value=True,
|
|
136
|
+
offset=offset,
|
|
137
|
+
)
|
|
138
|
+
for attr in ("__defaults__", "__kwdefaults__", "__doc__", "__dict__"):
|
|
139
|
+
cls.try_patch_attr(to_patch_to, to_patch_from, attr)
|
|
140
|
+
if is_method:
|
|
141
|
+
cls.try_patch_readonly_attr(to_patch_to, to_patch_from, "__self__")
|
|
@@ -1030,6 +1030,12 @@ class TerminalInteractiveShell(InteractiveShell):
|
|
|
1030
1030
|
|
|
1031
1031
|
_inputhook = None
|
|
1032
1032
|
def inputhook(self, context):
|
|
1033
|
+
warn(
|
|
1034
|
+
"inputkook seem unused, and marked for deprecation/Removal as of IPython 9.0. "
|
|
1035
|
+
"Please open an issue if you are using it.",
|
|
1036
|
+
category=PendingDeprecationWarning,
|
|
1037
|
+
stacklevel=2,
|
|
1038
|
+
)
|
|
1033
1039
|
if self._inputhook is not None:
|
|
1034
1040
|
self._inputhook(context)
|
|
1035
1041
|
|
|
@@ -50,7 +50,6 @@ def inputhook(context):
|
|
|
50
50
|
except AttributeError: # Only for Qt>=5.14.
|
|
51
51
|
pass
|
|
52
52
|
_appref = app = QtGui.QApplication([" "])
|
|
53
|
-
_eventloop = QtCore.QEventLoop(app)
|
|
54
53
|
|
|
55
54
|
# "reclaim" IPython sys.excepthook after event loop starts
|
|
56
55
|
# without this, it defaults back to BaseIPythonApplication.excepthook
|
|
@@ -58,6 +57,9 @@ def inputhook(context):
|
|
|
58
57
|
# formatting and look like "bug in IPython".
|
|
59
58
|
QtCore.QTimer.singleShot(0, _reclaim_excepthook)
|
|
60
59
|
|
|
60
|
+
if _eventloop is None:
|
|
61
|
+
_eventloop = QtCore.QEventLoop(app)
|
|
62
|
+
|
|
61
63
|
if sys.platform == 'win32':
|
|
62
64
|
# The QSocketNotifier method doesn't appear to work on Windows.
|
|
63
65
|
# Use polling instead.
|
IPython/terminal/ptutils.py
CHANGED
|
@@ -169,6 +169,7 @@ class IPythonPTCompleter(Completer):
|
|
|
169
169
|
adjusted_text = _adjust_completion_text_based_on_context(
|
|
170
170
|
c.text, body, offset
|
|
171
171
|
)
|
|
172
|
+
min_elide = 30 if self.shell is None else self.shell.min_elide
|
|
172
173
|
if c.type == "function":
|
|
173
174
|
yield Completion(
|
|
174
175
|
adjusted_text,
|
|
@@ -176,7 +177,7 @@ class IPythonPTCompleter(Completer):
|
|
|
176
177
|
display=_elide(
|
|
177
178
|
display_text + "()",
|
|
178
179
|
body[c.start : c.end],
|
|
179
|
-
min_elide=
|
|
180
|
+
min_elide=min_elide,
|
|
180
181
|
),
|
|
181
182
|
display_meta=c.type + c.signature,
|
|
182
183
|
)
|
|
@@ -187,7 +188,7 @@ class IPythonPTCompleter(Completer):
|
|
|
187
188
|
display=_elide(
|
|
188
189
|
display_text,
|
|
189
190
|
body[c.start : c.end],
|
|
190
|
-
min_elide=
|
|
191
|
+
min_elide=min_elide,
|
|
191
192
|
),
|
|
192
193
|
display_meta=c.type,
|
|
193
194
|
)
|
|
@@ -203,7 +203,7 @@ AUTO_SUGGEST_BINDINGS = [
|
|
|
203
203
|
Binding(
|
|
204
204
|
auto_suggest.accept,
|
|
205
205
|
["right"],
|
|
206
|
-
"has_suggestion & default_buffer_focused & emacs_like_insert_mode",
|
|
206
|
+
"has_suggestion & default_buffer_focused & emacs_like_insert_mode & is_cursor_at_the_end_of_line",
|
|
207
207
|
),
|
|
208
208
|
Binding(
|
|
209
209
|
auto_suggest.accept_word,
|
IPython/utils/PyColorize.py
CHANGED
|
@@ -3,6 +3,7 @@ import os
|
|
|
3
3
|
import sys
|
|
4
4
|
import token
|
|
5
5
|
import tokenize
|
|
6
|
+
import warnings
|
|
6
7
|
from io import StringIO
|
|
7
8
|
from typing import TypeAlias
|
|
8
9
|
|
|
@@ -353,7 +354,6 @@ class Parser:
|
|
|
353
354
|
Call format() to process code.
|
|
354
355
|
"""
|
|
355
356
|
|
|
356
|
-
|
|
357
357
|
assert theme_name is not None
|
|
358
358
|
|
|
359
359
|
self.out = out
|
|
@@ -361,7 +361,13 @@ class Parser:
|
|
|
361
361
|
self.lines = None
|
|
362
362
|
self.raw = None
|
|
363
363
|
if theme_name is not None:
|
|
364
|
-
|
|
364
|
+
if theme_name in ["Linux", "LightBG", "Neutral", "NoColor"]:
|
|
365
|
+
warnings.warn(
|
|
366
|
+
f"Theme names and color schemes are lowercase in IPython 9.0 use {theme_name.lower()} instead",
|
|
367
|
+
DeprecationWarning,
|
|
368
|
+
stacklevel=2,
|
|
369
|
+
)
|
|
370
|
+
theme_name = theme_name.lower()
|
|
365
371
|
if not theme_name:
|
|
366
372
|
self.theme_name = "nocolor"
|
|
367
373
|
else:
|
IPython/utils/_sysinfo.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# GENERATED BY setup.py
|
|
2
|
-
commit = "
|
|
2
|
+
commit = "9970f5e4b"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Deprecated/should be removed, but we break older version of ipyparallel
|
|
2
|
+
# https://github.com/ipython/ipyparallel/pull/924
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# minimal subset of TermColors, removed from IPython
|
|
6
|
+
# not for public consumption, beyond ipyparallel.
|
|
7
|
+
class TermColors:
|
|
8
|
+
Normal = "\033[0m"
|
|
9
|
+
Red = "\033[0;31m"
|
|
@@ -13,7 +13,7 @@ IPython/core/compilerop.py,sha256=tA8xHh10gp85brI2OYmvl7kW0TgDghdKbzmZE7nS4sw,69
|
|
|
13
13
|
IPython/core/completer.py,sha256=grT260S45DxAthhOeZZfp1YmZRhCKwvXstvxRnxMytQ,120548
|
|
14
14
|
IPython/core/completerlib.py,sha256=C_1uFwR4eiqIsemMRbluMQV1WJ3qSfnGxO01PsGSpr8,12641
|
|
15
15
|
IPython/core/crashhandler.py,sha256=8-kyI6aNkqbaB_lBlbNKAOFv34HDBCpLggMfiu4oIDg,8747
|
|
16
|
-
IPython/core/debugger.py,sha256=
|
|
16
|
+
IPython/core/debugger.py,sha256=I3LA4HQFEP5cy-kbbRvJqlLlSgVHSWBbp0cbPs3nU8M,41806
|
|
17
17
|
IPython/core/display.py,sha256=wQgVFY_U1O-a-jJSLb00nJ9m2w9-NsBhEBGnQUcWUd0,41129
|
|
18
18
|
IPython/core/display_functions.py,sha256=hlj1gXXrcIQU_ita03dHFesltOCViP1N3RcoLtLuyFI,12407
|
|
19
19
|
IPython/core/display_trap.py,sha256=31jM26B3PhNSLJhpyRHuTWscObwVMOlH9PDC2v8HmsU,2098
|
|
@@ -29,14 +29,14 @@ IPython/core/guarded_eval.py,sha256=dQXW-e3wM6z9WGcszdJLhqiK0UCii303GOOXd3z3ze8,
|
|
|
29
29
|
IPython/core/history.py,sha256=aTBJxCtZ_gPPJbzhAj6bOqII8AJClVWTH7xZX44m5jk,40602
|
|
30
30
|
IPython/core/historyapp.py,sha256=xVVF2UmWH7UUYg6L4bmpMnSgC1C7QXwNEDbQ1Waedkc,5871
|
|
31
31
|
IPython/core/hooks.py,sha256=xBWTZqycxZi97yj01IFc-SoJBzV5B73IoDHbAAlKUpQ,5193
|
|
32
|
-
IPython/core/inputtransformer2.py,sha256=
|
|
33
|
-
IPython/core/interactiveshell.py,sha256=
|
|
32
|
+
IPython/core/inputtransformer2.py,sha256=7sRleytrcAbp5PZMOrDw59MjUAGXg5BbaRbPkSW83-I,28909
|
|
33
|
+
IPython/core/interactiveshell.py,sha256=uAGkI0JslwYzc2WEhl3R38gqLG2w9xQpAJjhLuCVZ0g,153787
|
|
34
34
|
IPython/core/latex_symbols.py,sha256=DzFecvqWVSsdN7vWAsp0mlYAHRDQKfZGAmvuDUh0M-s,30127
|
|
35
35
|
IPython/core/logger.py,sha256=Iwe4xKMmxEdvSwHYPMfsTWkmdaqVCgvZT3R3I3qTmrU,8436
|
|
36
36
|
IPython/core/macro.py,sha256=OhvXWNhLe393rI2wTpMgbUVHWSnmC_ycHiYqzqSHXZU,1726
|
|
37
|
-
IPython/core/magic.py,sha256=
|
|
37
|
+
IPython/core/magic.py,sha256=sUPVeVSk70aGVaPXncrSJxYJU8u_6YGCnR3folx8wys,28967
|
|
38
38
|
IPython/core/magic_arguments.py,sha256=utkhQzGmn9Uw-ye33VO9P8ryP1L6ghkPcCdmhjri8K0,9701
|
|
39
|
-
IPython/core/oinspect.py,sha256=
|
|
39
|
+
IPython/core/oinspect.py,sha256=_C6tUncGVHmaU89_chHRknQJ264mDYxzsNxeHNYFOao,40280
|
|
40
40
|
IPython/core/page.py,sha256=P8Dmo0MjUq71DEnIPM_kaSGZoKTaPkrk4sUuM4I6HDA,11788
|
|
41
41
|
IPython/core/payload.py,sha256=uHcwG5Ahm3fnz2dsIKbzYK_lHOilqfen0IhQffOUQbE,1763
|
|
42
42
|
IPython/core/payloadpage.py,sha256=xGz4Ov82-0lmhKSIlM-kyIa50COk-ojB7tXLkGIRnPw,1177
|
|
@@ -44,18 +44,18 @@ IPython/core/prefilter.py,sha256=JHQ3feaD4bhoBDqZcEgmlDjQ2sfRXC1DNjgJhpaMU7E,257
|
|
|
44
44
|
IPython/core/profileapp.py,sha256=bFMFIyehxeF9pDUtxw_6D3b0nxeqsupKTe7XhH7GMkc,10711
|
|
45
45
|
IPython/core/profiledir.py,sha256=-vjOa1I_UajMZJblJRYXh16Y0RaAUn5a2swQBsw2qEU,8459
|
|
46
46
|
IPython/core/pylabtools.py,sha256=LfNV9xCJ3flCfJXmv1NaCRYj9jZDtHAQ5oSEHWo3Gmg,17376
|
|
47
|
-
IPython/core/release.py,sha256=
|
|
47
|
+
IPython/core/release.py,sha256=Xh10_c3N-D5TcMiswCtqiR5brz6KpxliDhScbJLcRuI,1505
|
|
48
48
|
IPython/core/shellapp.py,sha256=oZIzj_sqIXrN3qyyhinZ1gLXvFviKYHkmS4H3wVEb74,19307
|
|
49
49
|
IPython/core/splitinput.py,sha256=bAX1puQjvYB-otJyqiqeOhWj6dooWuQeNVx2YdaKQs8,5006
|
|
50
|
-
IPython/core/tbtools.py,sha256=
|
|
51
|
-
IPython/core/tips.py,sha256=
|
|
50
|
+
IPython/core/tbtools.py,sha256=X4iB5zKAT2y4TK1R9l3d3kiW5htrzKn3qxalFFe2xzI,16880
|
|
51
|
+
IPython/core/tips.py,sha256=EBvLHsSwa5EwXjIJDotgm_t5r_dWWvnkJy-e1WvphoU,6164
|
|
52
52
|
IPython/core/ultratb.py,sha256=ItaUt56wPnptv6f5ecbcBR3FtHljTDoIRHKsU-ZWGqI,46265
|
|
53
53
|
IPython/core/usage.py,sha256=agrZE5eZIvJnXoqI8VV9e-oWZx5LbLxUq9MdQpEyts4,13542
|
|
54
54
|
IPython/core/magics/__init__.py,sha256=pkd-UfzjDGp5UHuFKjw192vZnigpTP9ftXzG3oLdiS8,1619
|
|
55
55
|
IPython/core/magics/ast_mod.py,sha256=06OoRO7Z7Jzfc-cflf8Z3wyqF17fkYv6fJ_Nw4d7eQE,10295
|
|
56
56
|
IPython/core/magics/auto.py,sha256=yEouIjsQ6LmfSEfNvkZbNmNDFl19KLRnaJciYdR7a1A,4816
|
|
57
57
|
IPython/core/magics/basic.py,sha256=hvqMBRbn4rwvjDhjuZkZotMV__oSRpmKc08zQy81QhM,22356
|
|
58
|
-
IPython/core/magics/code.py,sha256=
|
|
58
|
+
IPython/core/magics/code.py,sha256=h_dho9niPvtf_IpoOZf5GAD6CYbT0EQGsfLfutyX-7I,28144
|
|
59
59
|
IPython/core/magics/config.py,sha256=QBL5uY7m-Q7C46mO3q1Yio9s73w1TnI9y__j5E-j44Y,4881
|
|
60
60
|
IPython/core/magics/display.py,sha256=STRq66GlZwcvFyBxbkqslclpP_s9LnqD0ew9Z3S4-Jo,3130
|
|
61
61
|
IPython/core/magics/execution.py,sha256=KHggud3yvtJ-w7PX7qXRF7Y9IsBqS8sZmzgzdmoP1YI,61879
|
|
@@ -71,6 +71,9 @@ IPython/core/profile/README_STARTUP,sha256=Y47GtYQkWy6pdzkqylUNL6eBSZMUIRGwTxXPU
|
|
|
71
71
|
IPython/extensions/__init__.py,sha256=V4vSllFd18CVUvDTbXLYqfwTFmPXUiQZGCs5LX2IliE,78
|
|
72
72
|
IPython/extensions/autoreload.py,sha256=_qU6PNtAm93J2UqtD3TMA68rj9wUWJPXfqb_VidrOCM,24571
|
|
73
73
|
IPython/extensions/storemagic.py,sha256=ths8PLtGmYZAYaibRuS1QetLm1Xu1soyGwehBjayByg,8168
|
|
74
|
+
IPython/extensions/deduperreload/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
75
|
+
IPython/extensions/deduperreload/deduperreload.py,sha256=jh4sa6b9w2zO4PQRUtPwAFtVUlSJwPKRAQg4q_csh9s,24680
|
|
76
|
+
IPython/extensions/deduperreload/deduperreload_patching.py,sha256=xOaws3UV5KmaAK8yZ3qtQO45w_5Ntkt_qZTcZ1psszY,4934
|
|
74
77
|
IPython/external/__init__.py,sha256=-EQHbuUnBe1RS1_CwaLGzNSZQsCJsrxHW_r15smvVW0,126
|
|
75
78
|
IPython/external/pickleshare.py,sha256=Zs0Hq8IXbf51RNFCr1AFxZKtlhfPJMM3b1WD4sNB670,9974
|
|
76
79
|
IPython/external/qt_for_kernel.py,sha256=ROLfN-NcA_h-ZgZsm9pBW1y57I_gxnJmOdpRwvpw8DA,3442
|
|
@@ -93,11 +96,11 @@ IPython/sphinxext/ipython_directive.py,sha256=hOQCdWx2N7WzBJVHMw7qVvAwLJxBZJxFCE
|
|
|
93
96
|
IPython/terminal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
94
97
|
IPython/terminal/debugger.py,sha256=0LdBmCKVHKNGpmBKdb4bBgKlyuTVyfz2Dg2Y8wE4FTM,6937
|
|
95
98
|
IPython/terminal/embed.py,sha256=9PnL6nL6mOklg77Zrn6bqw9TUswjH2d7VNRtcuAi0FI,16122
|
|
96
|
-
IPython/terminal/interactiveshell.py,sha256=
|
|
99
|
+
IPython/terminal/interactiveshell.py,sha256=yd-33C6tvN0Oc-_ftLbhPMmq6HufyNUo7fygO753p5k,39931
|
|
97
100
|
IPython/terminal/ipapp.py,sha256=d2Rog4DRkVv0_fReqZOuorKMXAtEqsCskwPejtjj0Lg,12512
|
|
98
101
|
IPython/terminal/magics.py,sha256=49ZVJzbAUkG_EFpebxIBEqm3tEClvqefoeM6QnxGrrk,7705
|
|
99
102
|
IPython/terminal/prompts.py,sha256=5IoXb-pXA4MWu3gAfEuyIwZUbDg6mxxJuMkOqRBmYa0,4555
|
|
100
|
-
IPython/terminal/ptutils.py,sha256=
|
|
103
|
+
IPython/terminal/ptutils.py,sha256=6PWDVPuBzpMxLdl_jqK7QtdnYVoVIjUP4Vl1JpFZFoM,8067
|
|
101
104
|
IPython/terminal/pt_inputhooks/__init__.py,sha256=jB7MOn9ZtC5qcq9RnCu9kbxjysP5YrN9KbXeYIx79Q4,3606
|
|
102
105
|
IPython/terminal/pt_inputhooks/asyncio.py,sha256=ffxVjt0RIsh0yYzcPDpSTrhWv5JDNRBTof0OlJgksFU,1271
|
|
103
106
|
IPython/terminal/pt_inputhooks/glut.py,sha256=CchyzpkfAqVytLzuKv_XWsU4rmAJXCgxBksACM2bk2s,4999
|
|
@@ -106,10 +109,10 @@ IPython/terminal/pt_inputhooks/gtk3.py,sha256=_MuZIjZuLsW2i4vvUjNQh9u4xIU8kcITyJ
|
|
|
106
109
|
IPython/terminal/pt_inputhooks/gtk4.py,sha256=r_MxCT7a0nTHZtqyuZpPgCW2Cl7iuomC0PjgFteSL9c,557
|
|
107
110
|
IPython/terminal/pt_inputhooks/osx.py,sha256=TnndyR_SPbmWC_A--0ORB06rhH7IS2_7kjphfPcRqXo,4448
|
|
108
111
|
IPython/terminal/pt_inputhooks/pyglet.py,sha256=wDDQDgilvK9uzwGB3XyAqcf8LfKr-Lc_NMAzunZMu_Y,2371
|
|
109
|
-
IPython/terminal/pt_inputhooks/qt.py,sha256=
|
|
112
|
+
IPython/terminal/pt_inputhooks/qt.py,sha256=ivVQu3UzzfGGeuowh6lNeVtuOJOL7g_VTKcgMG8JHpM,3479
|
|
110
113
|
IPython/terminal/pt_inputhooks/tk.py,sha256=FjejvtwbvpeBZLoBCci1RDo_jWD5qElMy7PP-atd8j4,3651
|
|
111
114
|
IPython/terminal/pt_inputhooks/wx.py,sha256=9yI52lDSZ3O_5Gww_3IeenEk_3PepLIME3Onh4X3kW0,7126
|
|
112
|
-
IPython/terminal/shortcuts/__init__.py,sha256=
|
|
115
|
+
IPython/terminal/shortcuts/__init__.py,sha256=irSX9mwHzeLDQ95fXRGdZxduRknwATtESvm2NIov7KU,18563
|
|
113
116
|
IPython/terminal/shortcuts/auto_match.py,sha256=9uT1fDb-c4Ew7TSIs_zET1jSxDlbfWGluxfW_pj39tk,3066
|
|
114
117
|
IPython/terminal/shortcuts/auto_suggest.py,sha256=dJGZGymi8xEmCurPYsqYeWHMP5kfqcnCcezDdBlv9wk,23177
|
|
115
118
|
IPython/terminal/shortcuts/filters.py,sha256=MgRTQWq8YfIyWvMASuQ9BGKq5RQwiEY5trSyMnMtJAo,10998
|
|
@@ -131,7 +134,7 @@ IPython/testing/plugin/test_example.txt,sha256=CGM8aZIYHlePDdAnR1yX3MfDGu0OceZpU
|
|
|
131
134
|
IPython/testing/plugin/test_exampleip.txt,sha256=5gLcj8iCk-WCOGz0ObpQpuZMhGwS1jUMyH3mouGxQJI,814
|
|
132
135
|
IPython/testing/plugin/test_ipdoctest.py,sha256=Lc3qQdZ3amXf9EKA7JlXf30b3BzP8RwdNS9-SMRe2P0,1907
|
|
133
136
|
IPython/testing/plugin/test_refs.py,sha256=y-Y2Q8niRIbaanbwpIzvEwwaHkJfAq10HYfb4bAXHBc,715
|
|
134
|
-
IPython/utils/PyColorize.py,sha256=
|
|
137
|
+
IPython/utils/PyColorize.py,sha256=VyNAE6pgw8sCrS2-1v4p30TZIHe6SFz_0YqLUdsOc4k,15553
|
|
135
138
|
IPython/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
136
139
|
IPython/utils/_process_cli.py,sha256=tJWYMEgNYgeMx9v-n3YLbKW0tPbFtpczfDH28RC3n4A,2020
|
|
137
140
|
IPython/utils/_process_common.py,sha256=hMFRGOJh-n-uBcSAOnr2qetQXAthMVpS8mwRcZuQqlo,7306
|
|
@@ -139,8 +142,9 @@ IPython/utils/_process_emscripten.py,sha256=lGLQb2IgmanNtb502KflfuKIhgOF119Ji3cw
|
|
|
139
142
|
IPython/utils/_process_posix.py,sha256=aOEtguhS3vdWngBpws1XQURO8Ozqd5gRiCk9VLky6tA,7502
|
|
140
143
|
IPython/utils/_process_win32.py,sha256=Pcf6ZiqMbqDT79edzegE_AX3D367UtE8bbhT41no54A,6775
|
|
141
144
|
IPython/utils/_process_win32_controller.py,sha256=hi2eR7mLbl3TTMCVbgps85GppxdtYbhOYK_l13WvYaM,21343
|
|
142
|
-
IPython/utils/_sysinfo.py,sha256=
|
|
145
|
+
IPython/utils/_sysinfo.py,sha256=k_tFtBwenx5P8zcV-6FTx5UbGvbQ9A6h5D7WevWmHoI,45
|
|
143
146
|
IPython/utils/capture.py,sha256=h5yL5Lxq8bgO1SFpoNDYjEi6mh1IW_2X9CE7vOsUxE4,5137
|
|
147
|
+
IPython/utils/coloransi.py,sha256=CML-SkzLa7oaIK1qypb3uwcfPXDeKHxZQiMJ0IWvUY0,293
|
|
144
148
|
IPython/utils/contexts.py,sha256=w5_uXc0WTU3KKV1kcCW9A0_Mz5mGRoeGWMq_P_eo-Dg,1610
|
|
145
149
|
IPython/utils/data.py,sha256=36VVGY1b0JG7_zSdbVSy8IzLqM0uT-uB12TBYWgd1lI,1015
|
|
146
150
|
IPython/utils/decorators.py,sha256=qsYLskFlT2bB_Q-87ttBA56lAf-knWLOe5WiOwPdXFE,2680
|
|
@@ -170,11 +174,11 @@ IPython/utils/text.py,sha256=6s-y4KvDmnJxLs0urf5D-1auZGSnj2xmXgQ-9jVu0N8,18788
|
|
|
170
174
|
IPython/utils/timing.py,sha256=nND-ZUBkHWfYevvbRG-YfOSIFczz_epzMqWK5PH6nqA,4275
|
|
171
175
|
IPython/utils/tokenutil.py,sha256=x6KQ6ZCGOY7j5GQcr7byJRZSBFgyBcfkTiLtjxkl9f8,6552
|
|
172
176
|
IPython/utils/wildcard.py,sha256=6EEc3OEYp-IuSoidL6nwpaHg--GxnzbAJTmFiz77CNE,4612
|
|
173
|
-
ipython-9.0.
|
|
174
|
-
ipython-9.0.
|
|
175
|
-
ipython-9.0.
|
|
176
|
-
ipython-9.0.
|
|
177
|
-
ipython-9.0.
|
|
178
|
-
ipython-9.0.
|
|
179
|
-
ipython-9.0.
|
|
180
|
-
ipython-9.0.
|
|
177
|
+
ipython-9.0.2.data/data/share/man/man1/ipython.1,sha256=PVdQP2hHmHyUEwzLOPcgavnCe9jTDVrM1jKZt4cnF_Q,2058
|
|
178
|
+
ipython-9.0.2.dist-info/COPYING.rst,sha256=NBr8vXKYh7cEb-e5j8T07f867Y048G7v2bMGcPBD3xc,1639
|
|
179
|
+
ipython-9.0.2.dist-info/LICENSE,sha256=4OOQdI7UQKuJPKHxNaiKkgqvVAnbuQpbQnx1xeUSaPs,1720
|
|
180
|
+
ipython-9.0.2.dist-info/METADATA,sha256=7S5uSfcEcaNZ9JJsCMTJ_lBitQ-QnvsIZjPLk6DZc6Q,4254
|
|
181
|
+
ipython-9.0.2.dist-info/WHEEL,sha256=RnOlZk4WRTvz4xhLM2LyBNUiU6tTosLZam4r642KGVM,90
|
|
182
|
+
ipython-9.0.2.dist-info/entry_points.txt,sha256=z5BEEohWgg0SHdgdeNABf4T3fu-lr9W6F_bWOQHLdVs,83
|
|
183
|
+
ipython-9.0.2.dist-info/top_level.txt,sha256=PKjvHtNCBZ9EHTmd2mwJ1J_k3j0F6D1lTFzIcJFFPEU,8
|
|
184
|
+
ipython-9.0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|