pyflyby 1.10.1__cp311-cp311-manylinux_2_24_x86_64.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.
Potentially problematic release.
This version of pyflyby might be problematic. Click here for more details.
- pyflyby/__init__.py +61 -0
- pyflyby/__main__.py +9 -0
- pyflyby/_autoimp.py +2229 -0
- pyflyby/_cmdline.py +548 -0
- pyflyby/_comms.py +221 -0
- pyflyby/_dbg.py +1367 -0
- pyflyby/_docxref.py +379 -0
- pyflyby/_dynimp.py +154 -0
- pyflyby/_fast_iter_modules.cpython-311-x86_64-linux-gnu.so +0 -0
- pyflyby/_file.py +771 -0
- pyflyby/_flags.py +230 -0
- pyflyby/_format.py +186 -0
- pyflyby/_idents.py +227 -0
- pyflyby/_import_sorting.py +165 -0
- pyflyby/_importclns.py +658 -0
- pyflyby/_importdb.py +680 -0
- pyflyby/_imports2s.py +643 -0
- pyflyby/_importstmt.py +723 -0
- pyflyby/_interactive.py +2113 -0
- pyflyby/_livepatch.py +793 -0
- pyflyby/_log.py +104 -0
- pyflyby/_modules.py +641 -0
- pyflyby/_parse.py +1381 -0
- pyflyby/_py.py +2166 -0
- pyflyby/_saveframe.py +1145 -0
- pyflyby/_saveframe_reader.py +471 -0
- pyflyby/_util.py +458 -0
- pyflyby/_version.py +7 -0
- pyflyby/autoimport.py +20 -0
- pyflyby/etc/pyflyby/canonical.py +10 -0
- pyflyby/etc/pyflyby/common.py +27 -0
- pyflyby/etc/pyflyby/forget.py +10 -0
- pyflyby/etc/pyflyby/mandatory.py +10 -0
- pyflyby/etc/pyflyby/numpy.py +156 -0
- pyflyby/etc/pyflyby/std.py +335 -0
- pyflyby/importdb.py +19 -0
- pyflyby/libexec/pyflyby/colordiff +34 -0
- pyflyby/libexec/pyflyby/diff-colorize +148 -0
- pyflyby/share/emacs/site-lisp/pyflyby.el +108 -0
- pyflyby-1.10.1.data/scripts/collect-exports +76 -0
- pyflyby-1.10.1.data/scripts/collect-imports +58 -0
- pyflyby-1.10.1.data/scripts/find-import +38 -0
- pyflyby-1.10.1.data/scripts/list-bad-xrefs +34 -0
- pyflyby-1.10.1.data/scripts/prune-broken-imports +34 -0
- pyflyby-1.10.1.data/scripts/pyflyby-diff +34 -0
- pyflyby-1.10.1.data/scripts/reformat-imports +27 -0
- pyflyby-1.10.1.data/scripts/replace-star-imports +37 -0
- pyflyby-1.10.1.data/scripts/saveframe +299 -0
- pyflyby-1.10.1.data/scripts/tidy-imports +163 -0
- pyflyby-1.10.1.data/scripts/transform-imports +47 -0
- pyflyby-1.10.1.dist-info/METADATA +591 -0
- pyflyby-1.10.1.dist-info/RECORD +55 -0
- pyflyby-1.10.1.dist-info/WHEEL +5 -0
- pyflyby-1.10.1.dist-info/entry_points.txt +4 -0
- pyflyby-1.10.1.dist-info/licenses/LICENSE.txt +23 -0
pyflyby/_docxref.py
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
# pyflyby/_docxref.py.
|
|
2
|
+
|
|
3
|
+
# Module for checking Epydoc cross-references.
|
|
4
|
+
|
|
5
|
+
# Portions of the code below are derived from Epydoc, which is distributed
|
|
6
|
+
# under the MIT license:
|
|
7
|
+
#
|
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
9
|
+
# copy of this software and any associated documentation files (the
|
|
10
|
+
# "Software"), to deal in the Software without restriction, including
|
|
11
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
|
12
|
+
# distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
13
|
+
# persons to whom the Software is furnished to do so, subject to the
|
|
14
|
+
# following conditions:
|
|
15
|
+
#
|
|
16
|
+
# The above copyright notice and this permission notice shall be included in
|
|
17
|
+
# all copies or substantial portions of the Software.
|
|
18
|
+
#
|
|
19
|
+
# The software is provided "as is", without warranty of any kind, express or
|
|
20
|
+
# implied, including but not limited to the warranties of merchantability,
|
|
21
|
+
# fitness for a particular purpose and noninfringement. In no event shall
|
|
22
|
+
# the authors or copyright holders be liable for any claim, damages or other
|
|
23
|
+
# liability, whether in an action of contract, tort or otherwise, arising
|
|
24
|
+
# from, out of or in connection with the software or the use or other
|
|
25
|
+
# dealings in the software.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
import re
|
|
29
|
+
import builtins
|
|
30
|
+
from textwrap import dedent
|
|
31
|
+
|
|
32
|
+
from epydoc.apidoc import (ClassDoc, ModuleDoc, PropertyDoc,
|
|
33
|
+
RoutineDoc, UNKNOWN, VariableDoc)
|
|
34
|
+
from epydoc.docbuilder import build_doc_index
|
|
35
|
+
from epydoc.markup.plaintext import ParsedPlaintextDocstring
|
|
36
|
+
|
|
37
|
+
from pyflyby._file import Filename
|
|
38
|
+
from pyflyby._idents import DottedIdentifier
|
|
39
|
+
from pyflyby._log import logger
|
|
40
|
+
from pyflyby._modules import ModuleHandle
|
|
41
|
+
from pyflyby._util import cached_attribute, memoize, prefixes
|
|
42
|
+
|
|
43
|
+
# If someone references numpy.*, just assume it's OK - it's not worth
|
|
44
|
+
# following into numpy because it's too slow.
|
|
45
|
+
ASSUME_MODULES_OK = set(['numpy'])
|
|
46
|
+
|
|
47
|
+
@memoize
|
|
48
|
+
def map_strings_to_line_numbers(module):
|
|
49
|
+
"""
|
|
50
|
+
Walk ``module.ast``, looking at all string literals. Return a map from
|
|
51
|
+
string literals to line numbers (1-index).
|
|
52
|
+
|
|
53
|
+
:rtype:
|
|
54
|
+
``dict`` from ``str`` to (``int``, ``str``)
|
|
55
|
+
"""
|
|
56
|
+
d = {}
|
|
57
|
+
for field in module.block.string_literals():
|
|
58
|
+
# Dedent because epydoc dedents strings and we need to look up by
|
|
59
|
+
# those. But keep track of original version because we need to count
|
|
60
|
+
# exact line numbers.
|
|
61
|
+
s = dedent(field.s).strip()
|
|
62
|
+
start_lineno = field.startpos.lineno
|
|
63
|
+
d[s] = (start_lineno, field.s)
|
|
64
|
+
return d
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_string_linenos(module, searchstring, within_string):
|
|
68
|
+
"""
|
|
69
|
+
Return the line numbers (1-indexed) within ``filename`` that contain
|
|
70
|
+
``searchstring``. Only consider string literals (i.e. not comments).
|
|
71
|
+
First look for exact matches of ``within_string`` (modulo indenting) and
|
|
72
|
+
then search within that. Only if the ``within_string`` is not found,
|
|
73
|
+
search the entire file.
|
|
74
|
+
|
|
75
|
+
[If there's a comment on the same line as a string that also contains the
|
|
76
|
+
searchstring, we'll get confused.]
|
|
77
|
+
"""
|
|
78
|
+
module = ModuleHandle(module)
|
|
79
|
+
regexp = re.compile(searchstring)
|
|
80
|
+
map = map_strings_to_line_numbers(module)
|
|
81
|
+
results = []
|
|
82
|
+
def scan_within_string(results, start_lineno, orig_full_string):
|
|
83
|
+
for i, line in enumerate(orig_full_string.splitlines()):
|
|
84
|
+
if regexp.search(line):
|
|
85
|
+
results.append( start_lineno + i )
|
|
86
|
+
try:
|
|
87
|
+
lineno, orig_full_string = map[within_string.strip()]
|
|
88
|
+
except KeyError:
|
|
89
|
+
pass
|
|
90
|
+
else:
|
|
91
|
+
# We found the larger string exactly within the ast.
|
|
92
|
+
scan_within_string(results, lineno, orig_full_string)
|
|
93
|
+
if results:
|
|
94
|
+
return tuple(results)
|
|
95
|
+
# We could continue down if this ever happened.
|
|
96
|
+
raise Exception(
|
|
97
|
+
"Found superstring in %r but not substring %r within superstring"
|
|
98
|
+
% (module.filename, searchstring))
|
|
99
|
+
# Try a full text search.
|
|
100
|
+
for lineno, orig_full_string in map.values():
|
|
101
|
+
scan_within_string(results, lineno, orig_full_string)
|
|
102
|
+
if results:
|
|
103
|
+
return tuple(sorted(results))
|
|
104
|
+
raise Exception(
|
|
105
|
+
"Could not find %r anywhere in %r" % (searchstring, module.filename))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def describe_xref(identifier, container):
|
|
109
|
+
module = ModuleHandle(str(container.defining_module.canonical_name))
|
|
110
|
+
assert module.filename == Filename(container.defining_module.filename)
|
|
111
|
+
linenos = get_string_linenos(
|
|
112
|
+
module,
|
|
113
|
+
"(L{|<)%s" % (identifier,),
|
|
114
|
+
container.docstring)
|
|
115
|
+
return (module, linenos, str(container.canonical_name), identifier)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def safe_build_doc_index(modules):
|
|
120
|
+
# build_doc_index isn't re-entrant due to crappy caching! >:(
|
|
121
|
+
from epydoc.docintrospecter import clear_cache
|
|
122
|
+
clear_cache()
|
|
123
|
+
from epydoc.docparser import _moduledoc_cache
|
|
124
|
+
_moduledoc_cache.clear()
|
|
125
|
+
# Build a new DocIndex. It swallows exceptions and returns None on error!
|
|
126
|
+
# >:(
|
|
127
|
+
result = build_doc_index(modules)
|
|
128
|
+
if result is None:
|
|
129
|
+
raise Exception("Failed to build doc index on %r" % (modules,))
|
|
130
|
+
return result
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class ExpandedDocIndex(object):
|
|
134
|
+
"""
|
|
135
|
+
A wrapper around DocIndex that automatically expands with more modules as
|
|
136
|
+
needed.
|
|
137
|
+
"""
|
|
138
|
+
# TODO: this is kludgy and inefficient since it re-reads modules.
|
|
139
|
+
def __init__(self, modules):
|
|
140
|
+
self.modules = set([ModuleHandle(m) for m in modules])
|
|
141
|
+
|
|
142
|
+
def add_module(self, module):
|
|
143
|
+
"""
|
|
144
|
+
Adds ``module`` and recreates the DocIndex with the updated set of
|
|
145
|
+
modules.
|
|
146
|
+
|
|
147
|
+
:return:
|
|
148
|
+
Whether anything was added.
|
|
149
|
+
"""
|
|
150
|
+
module = ModuleHandle(module)
|
|
151
|
+
for prefix in module.ancestors:
|
|
152
|
+
if prefix in self.modules:
|
|
153
|
+
# The module, or a prefix of it, was already added.
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
for existing_module in sorted(self.modules):
|
|
157
|
+
if existing_module.startswith(module):
|
|
158
|
+
# This supersedes an existing module.
|
|
159
|
+
assert existing_module != module
|
|
160
|
+
self.modules.remove(existing_module)
|
|
161
|
+
|
|
162
|
+
logger.debug("Expanding docindex to include %r", module)
|
|
163
|
+
self.modules.add(module)
|
|
164
|
+
del self.docindex
|
|
165
|
+
return True
|
|
166
|
+
|
|
167
|
+
def find(self, a, b):
|
|
168
|
+
return self.docindex.find(a, b)
|
|
169
|
+
|
|
170
|
+
def get_vardoc(self, a):
|
|
171
|
+
return self.docindex.get_vardoc(a)
|
|
172
|
+
|
|
173
|
+
@cached_attribute
|
|
174
|
+
def docindex(self):
|
|
175
|
+
return safe_build_doc_index(
|
|
176
|
+
[str(m.name) for m in sorted(self.modules)])
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def remove_epydoc_sym_suffix(s):
|
|
180
|
+
"""
|
|
181
|
+
Remove trailing "'" that Epydoc annoyingly adds to 'shadowed' names.
|
|
182
|
+
|
|
183
|
+
>>> remove_epydoc_sym_suffix("a.b'.c'.d")
|
|
184
|
+
'a.b.c.d'
|
|
185
|
+
|
|
186
|
+
"""
|
|
187
|
+
return re.sub(r"'([.]|$)", r'\1', s)
|
|
188
|
+
|
|
189
|
+
class XrefScanner(object):
|
|
190
|
+
|
|
191
|
+
def __init__(self, modules):
|
|
192
|
+
self.modules = modules
|
|
193
|
+
self.docindex = safe_build_doc_index(modules)
|
|
194
|
+
|
|
195
|
+
@cached_attribute
|
|
196
|
+
def expanded_docindex(self):
|
|
197
|
+
return ExpandedDocIndex(self.modules)
|
|
198
|
+
|
|
199
|
+
def scan(self):
|
|
200
|
+
self._failed_xrefs = []
|
|
201
|
+
valdocs = sorted(self.docindex.reachable_valdocs(
|
|
202
|
+
imports=False, packages=False, bases=False, submodules=False,
|
|
203
|
+
subclasses=False, private=True
|
|
204
|
+
))
|
|
205
|
+
for doc in valdocs:
|
|
206
|
+
if isinstance(doc, ClassDoc):
|
|
207
|
+
self.scan_class(doc)
|
|
208
|
+
elif isinstance(doc, ModuleDoc):
|
|
209
|
+
self.scan_module(doc)
|
|
210
|
+
return tuple(sorted(self._failed_xrefs))
|
|
211
|
+
|
|
212
|
+
def scan_module(self, doc):
|
|
213
|
+
self.descr(doc)
|
|
214
|
+
if doc.is_package is True:
|
|
215
|
+
for submodule in doc.submodules:
|
|
216
|
+
self.scan_module(submodule)
|
|
217
|
+
# self.scan_module_list(doc)
|
|
218
|
+
self.scan_details_list(doc, "function")
|
|
219
|
+
self.scan_details_list(doc, "other")
|
|
220
|
+
|
|
221
|
+
def scan_class(self, doc):
|
|
222
|
+
self.descr(doc)
|
|
223
|
+
self.scan_details_list(doc, "method")
|
|
224
|
+
self.scan_details_list(doc, "classvariable")
|
|
225
|
+
self.scan_details_list(doc, "instancevariable")
|
|
226
|
+
self.scan_details_list(doc, "property")
|
|
227
|
+
|
|
228
|
+
def scan_details_list(self, doc, value_type):
|
|
229
|
+
detailed = True
|
|
230
|
+
if isinstance(doc, ClassDoc):
|
|
231
|
+
var_docs = doc.select_variables(value_type=value_type,
|
|
232
|
+
imported=False, inherited=False,
|
|
233
|
+
public=None,
|
|
234
|
+
detailed=detailed)
|
|
235
|
+
else:
|
|
236
|
+
var_docs = doc.select_variables(value_type=value_type,
|
|
237
|
+
imported=False,
|
|
238
|
+
public=None,
|
|
239
|
+
detailed=detailed)
|
|
240
|
+
for var_doc in var_docs:
|
|
241
|
+
self.scan_details(var_doc)
|
|
242
|
+
|
|
243
|
+
def scan_details(self, var_doc):
|
|
244
|
+
self.descr(var_doc)
|
|
245
|
+
if isinstance(var_doc.value, RoutineDoc):
|
|
246
|
+
self.return_type(var_doc)
|
|
247
|
+
self.return_descr(var_doc)
|
|
248
|
+
for (arg_names, arg_descr) in var_doc.value.arg_descrs:
|
|
249
|
+
self.scan_docstring(arg_descr, var_doc.value)
|
|
250
|
+
for arg in var_doc.value.arg_types:
|
|
251
|
+
self.scan_docstring(
|
|
252
|
+
var_doc.value.arg_types[arg], var_doc.value)
|
|
253
|
+
elif isinstance(var_doc.value, PropertyDoc):
|
|
254
|
+
prop_doc = var_doc.value
|
|
255
|
+
self.return_type(prop_doc.fget)
|
|
256
|
+
self.return_type(prop_doc.fset)
|
|
257
|
+
self.return_type(prop_doc.fdel)
|
|
258
|
+
else:
|
|
259
|
+
self.type_descr(var_doc)
|
|
260
|
+
|
|
261
|
+
def _scan_attr(self, attr, api_doc):
|
|
262
|
+
if api_doc in (None, UNKNOWN):
|
|
263
|
+
return ''
|
|
264
|
+
pds = getattr(api_doc, attr, None) # pds = ParsedDocstring.
|
|
265
|
+
if pds not in (None, UNKNOWN):
|
|
266
|
+
self.scan_docstring(pds, api_doc)
|
|
267
|
+
elif isinstance(api_doc, VariableDoc):
|
|
268
|
+
self._scan_attr(attr, api_doc.value)
|
|
269
|
+
|
|
270
|
+
def summary(self, api_doc):
|
|
271
|
+
self._scan_attr('summary', api_doc)
|
|
272
|
+
|
|
273
|
+
def descr(self, api_doc):
|
|
274
|
+
self._scan_attr('descr', api_doc)
|
|
275
|
+
|
|
276
|
+
def type_descr(self, api_doc):
|
|
277
|
+
self._scan_attr('type_descr', api_doc)
|
|
278
|
+
|
|
279
|
+
def return_type(self, api_doc):
|
|
280
|
+
self._scan_attr('return_type', api_doc)
|
|
281
|
+
|
|
282
|
+
def return_descr(self, api_doc):
|
|
283
|
+
self._scan_attr('return_descr', api_doc)
|
|
284
|
+
|
|
285
|
+
def check_xref(self, identifier, container):
|
|
286
|
+
"""
|
|
287
|
+
Check that ``identifier`` cross-references a proper symbol.
|
|
288
|
+
|
|
289
|
+
Look in modules that we weren't explicitly asked to look in, if
|
|
290
|
+
needed.
|
|
291
|
+
"""
|
|
292
|
+
if identifier in builtins.__dict__:
|
|
293
|
+
return True
|
|
294
|
+
def check_container():
|
|
295
|
+
if self.expanded_docindex.find(identifier, container) is not None:
|
|
296
|
+
return True
|
|
297
|
+
if isinstance(container, RoutineDoc):
|
|
298
|
+
tcontainer = self.expanded_docindex.get_vardoc(
|
|
299
|
+
container.canonical_name)
|
|
300
|
+
doc = self.expanded_docindex.find(identifier, tcontainer)
|
|
301
|
+
while (doc is not None and tcontainer not in (None, UNKNOWN)
|
|
302
|
+
and tcontainer.overrides not in (None, UNKNOWN)):
|
|
303
|
+
tcontainer = tcontainer.overrides
|
|
304
|
+
doc = self.expanded_docindex.find(identifier, tcontainer)
|
|
305
|
+
return doc is not None
|
|
306
|
+
return False
|
|
307
|
+
def check_defining_module(x):
|
|
308
|
+
if x is None:
|
|
309
|
+
return False
|
|
310
|
+
defining_module_name = remove_epydoc_sym_suffix(str(
|
|
311
|
+
x.defining_module.canonical_name))
|
|
312
|
+
if defining_module_name in ASSUME_MODULES_OK:
|
|
313
|
+
return True
|
|
314
|
+
if self.expanded_docindex.add_module(defining_module_name):
|
|
315
|
+
if check_container():
|
|
316
|
+
return True
|
|
317
|
+
return False
|
|
318
|
+
if check_container():
|
|
319
|
+
return True
|
|
320
|
+
if (isinstance(container, RoutineDoc) and
|
|
321
|
+
identifier in container.all_args()):
|
|
322
|
+
return True
|
|
323
|
+
if check_defining_module(container):
|
|
324
|
+
return True
|
|
325
|
+
# If the user has imported foo.bar.baz as baz and now uses
|
|
326
|
+
# ``baz.quux``, we need to add the module foo.bar.baz.
|
|
327
|
+
for prefix in reversed(list(prefixes(
|
|
328
|
+
DottedIdentifier(remove_epydoc_sym_suffix(identifier))))):
|
|
329
|
+
if check_defining_module(
|
|
330
|
+
self.docindex.find(str(prefix), container)):
|
|
331
|
+
return True
|
|
332
|
+
try:
|
|
333
|
+
module = ModuleHandle.containing(identifier)
|
|
334
|
+
except ImportError:
|
|
335
|
+
pass
|
|
336
|
+
else:
|
|
337
|
+
if str(module.name) in ASSUME_MODULES_OK:
|
|
338
|
+
return True
|
|
339
|
+
if self.expanded_docindex.add_module(module):
|
|
340
|
+
if check_container():
|
|
341
|
+
return True
|
|
342
|
+
return False
|
|
343
|
+
|
|
344
|
+
def scan_docstring(self, parsed_docstring, container):
|
|
345
|
+
if parsed_docstring in (None, UNKNOWN): return ''
|
|
346
|
+
if isinstance(parsed_docstring, ParsedPlaintextDocstring):
|
|
347
|
+
return ''
|
|
348
|
+
|
|
349
|
+
def scan_tree(tree):
|
|
350
|
+
if isinstance(tree, str):
|
|
351
|
+
return tree
|
|
352
|
+
variables = [scan_tree(child) for child in tree.children]
|
|
353
|
+
if tree.tag == 'link':
|
|
354
|
+
identifier = variables[1]
|
|
355
|
+
if not self.check_xref(identifier, container):
|
|
356
|
+
self._failed_xrefs.append(
|
|
357
|
+
describe_xref(identifier, container) )
|
|
358
|
+
return '?'
|
|
359
|
+
elif tree.tag == 'indexed':
|
|
360
|
+
return '?'
|
|
361
|
+
elif tree.tag in ('epytext', 'section', 'tag', 'arg',
|
|
362
|
+
'name', 'target', 'html', 'para'):
|
|
363
|
+
return ''.join(variables)
|
|
364
|
+
return '?'
|
|
365
|
+
|
|
366
|
+
scan_tree(parsed_docstring._tree)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def find_bad_doc_cross_references(names):
|
|
370
|
+
"""
|
|
371
|
+
Find docstring cross references that fail to resolve.
|
|
372
|
+
|
|
373
|
+
:type names:
|
|
374
|
+
Sequence of module names or filenames.
|
|
375
|
+
:return:
|
|
376
|
+
Sequence of ``(module, linenos, container_name, identifier)`` tuples.
|
|
377
|
+
"""
|
|
378
|
+
xrs = XrefScanner(names)
|
|
379
|
+
return xrs.scan()
|
pyflyby/_dynimp.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Virtual module to create dynamic import at runtime.
|
|
3
|
+
|
|
4
|
+
It is sometime desirable to have auto import which are define only during
|
|
5
|
+
a session and never exist on a on-disk file.
|
|
6
|
+
|
|
7
|
+
This is injects a Dict module loader as well as a dictionary registry of in
|
|
8
|
+
memory module.
|
|
9
|
+
|
|
10
|
+
This is mostly use in IPython for lazy variable initialisation without having
|
|
11
|
+
to use proxy objects.
|
|
12
|
+
|
|
13
|
+
To use, put the following in your IPython startup files
|
|
14
|
+
(``~/.ipython/profile_default/startup/autoimp.py`), or in your IPython
|
|
15
|
+
configuration file:
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
.. code:: python
|
|
19
|
+
|
|
20
|
+
from pyflyby._dynimp import add_import
|
|
21
|
+
|
|
22
|
+
add_import("foo", "foo = 1")
|
|
23
|
+
|
|
24
|
+
add_import(
|
|
25
|
+
"df, data",
|
|
26
|
+
'''
|
|
27
|
+
import pandas as pd
|
|
28
|
+
data = [1,2,3]
|
|
29
|
+
df = pd.DataFrame(data)
|
|
30
|
+
''',
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
Now at the IPython prompt, if the pyflyby extension is loaded (either because
|
|
34
|
+
you started using the ``py`` cli, or some configuration options like ``ipython
|
|
35
|
+
--TerminalIPythonApp.extra_extensions=pyflyby``. When trying to use an undefined
|
|
36
|
+
variable like ``foo``, ``df`` or ``data``, the corresponding module will be
|
|
37
|
+
executed and the relevant variable imported.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
import importlib.abc
|
|
42
|
+
import importlib.util
|
|
43
|
+
import sys
|
|
44
|
+
|
|
45
|
+
from textwrap import dedent
|
|
46
|
+
from typing import FrozenSet
|
|
47
|
+
|
|
48
|
+
from pyflyby._importclns import ImportSet, Import
|
|
49
|
+
|
|
50
|
+
module_dict = {}
|
|
51
|
+
|
|
52
|
+
PYFLYBY_LAZY_LOAD_PREFIX = "from pyflyby_autoimport_"
|
|
53
|
+
|
|
54
|
+
def add_import(names: str, code: str, *, strict: bool = True):
|
|
55
|
+
"""
|
|
56
|
+
Add a runtime generated import module
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
names: str
|
|
61
|
+
name, or comma separated list variable names that should be created by
|
|
62
|
+
executing and importing `code`.
|
|
63
|
+
code: str
|
|
64
|
+
potentially multiline string that will be turned into a module,
|
|
65
|
+
executed and from which variables listed in names can be imported.
|
|
66
|
+
strict: bool
|
|
67
|
+
Raise in case of problem loading IPython of if pyflyby extension not installed.
|
|
68
|
+
otherwise just ignore error
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
Examples
|
|
73
|
+
--------
|
|
74
|
+
|
|
75
|
+
>>> add_import('pd, df', '''
|
|
76
|
+
... import pandas a pd
|
|
77
|
+
...
|
|
78
|
+
... df = pd.DataFrame([[1,2], [3,4]])
|
|
79
|
+
... ''', strict=False) # don't fail doctest
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
try:
|
|
83
|
+
ip = _raise_if_problem()
|
|
84
|
+
except Exception:
|
|
85
|
+
if strict:
|
|
86
|
+
raise
|
|
87
|
+
else:
|
|
88
|
+
return
|
|
89
|
+
return _add_import(ip, names, code)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _raise_if_problem():
|
|
93
|
+
try:
|
|
94
|
+
import IPython
|
|
95
|
+
except ModuleNotFoundError as e:
|
|
96
|
+
raise ImportError("Dynamic autoimport requires IPython to be installed") from e
|
|
97
|
+
|
|
98
|
+
ip = IPython.get_ipython()
|
|
99
|
+
if ip is None:
|
|
100
|
+
raise ImportError("Dynamic autoimport only work from within IPython")
|
|
101
|
+
|
|
102
|
+
if not hasattr(ip, "_auto_importer"):
|
|
103
|
+
raise ValueError(
|
|
104
|
+
"IPython needs to be loaded with pyflyby extension for lazy variable to work"
|
|
105
|
+
)
|
|
106
|
+
return ip
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _add_import(ip, names: str, code: str) -> None:
|
|
110
|
+
"""
|
|
111
|
+
private version of add_import
|
|
112
|
+
"""
|
|
113
|
+
assert ip is not None
|
|
114
|
+
module = PYFLYBY_LAZY_LOAD_PREFIX.split()[1]
|
|
115
|
+
mang = module + names.replace(",", "_").replace(" ", "_")
|
|
116
|
+
a: FrozenSet[Import] = ImportSet(f"from {mang} import {names}")._importset
|
|
117
|
+
b: FrozenSet[Import] = ip._auto_importer.db.known_imports._importset
|
|
118
|
+
s_import: FrozenSet[Import] = a | b
|
|
119
|
+
|
|
120
|
+
ip._auto_importer.db.known_imports = ImportSet._from_imports(list(s_import))
|
|
121
|
+
module_dict[mang] = dedent(code)
|
|
122
|
+
|
|
123
|
+
class DictLoader(importlib.abc.Loader):
|
|
124
|
+
"""
|
|
125
|
+
A dict based loader for in-memory module definition.
|
|
126
|
+
"""
|
|
127
|
+
def __init__(self, module_name, module_code):
|
|
128
|
+
self.module_name = module_name
|
|
129
|
+
self.module_code = module_code
|
|
130
|
+
|
|
131
|
+
def create_module(self, spec):
|
|
132
|
+
return None # Use default module creation semantics
|
|
133
|
+
|
|
134
|
+
def exec_module(self, module):
|
|
135
|
+
"""
|
|
136
|
+
we exec module code directly in memory
|
|
137
|
+
"""
|
|
138
|
+
exec(self.module_code, module.__dict__)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class DictFinder(importlib.abc.MetaPathFinder):
|
|
142
|
+
"""
|
|
143
|
+
A meta path finder for abode DictLoader
|
|
144
|
+
"""
|
|
145
|
+
def find_spec(self, fullname, path, target=None):
|
|
146
|
+
if fullname in module_dict:
|
|
147
|
+
module_code = module_dict[fullname]
|
|
148
|
+
loader = DictLoader(fullname, module_code)
|
|
149
|
+
return importlib.util.spec_from_loader(fullname, loader)
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def inject():
|
|
154
|
+
sys.meta_path.insert(0, DictFinder())
|
|
Binary file
|