pyflyby 1.10.4__cp311-cp311-macosx_11_0_arm64.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.
Files changed (53) hide show
  1. pyflyby/__init__.py +61 -0
  2. pyflyby/__main__.py +9 -0
  3. pyflyby/_autoimp.py +2228 -0
  4. pyflyby/_cmdline.py +591 -0
  5. pyflyby/_comms.py +221 -0
  6. pyflyby/_dbg.py +1383 -0
  7. pyflyby/_dynimp.py +154 -0
  8. pyflyby/_fast_iter_modules.cpython-311-darwin.so +0 -0
  9. pyflyby/_file.py +771 -0
  10. pyflyby/_flags.py +230 -0
  11. pyflyby/_format.py +186 -0
  12. pyflyby/_idents.py +227 -0
  13. pyflyby/_import_sorting.py +165 -0
  14. pyflyby/_importclns.py +658 -0
  15. pyflyby/_importdb.py +535 -0
  16. pyflyby/_imports2s.py +643 -0
  17. pyflyby/_importstmt.py +723 -0
  18. pyflyby/_interactive.py +2113 -0
  19. pyflyby/_livepatch.py +793 -0
  20. pyflyby/_log.py +107 -0
  21. pyflyby/_modules.py +646 -0
  22. pyflyby/_parse.py +1396 -0
  23. pyflyby/_py.py +2165 -0
  24. pyflyby/_saveframe.py +1145 -0
  25. pyflyby/_saveframe_reader.py +471 -0
  26. pyflyby/_util.py +458 -0
  27. pyflyby/_version.py +8 -0
  28. pyflyby/autoimport.py +20 -0
  29. pyflyby/etc/pyflyby/canonical.py +10 -0
  30. pyflyby/etc/pyflyby/common.py +27 -0
  31. pyflyby/etc/pyflyby/forget.py +10 -0
  32. pyflyby/etc/pyflyby/mandatory.py +10 -0
  33. pyflyby/etc/pyflyby/numpy.py +156 -0
  34. pyflyby/etc/pyflyby/std.py +335 -0
  35. pyflyby/importdb.py +19 -0
  36. pyflyby/libexec/pyflyby/colordiff +34 -0
  37. pyflyby/libexec/pyflyby/diff-colorize +148 -0
  38. pyflyby/share/emacs/site-lisp/pyflyby.el +112 -0
  39. pyflyby-1.10.4.data/scripts/collect-exports +76 -0
  40. pyflyby-1.10.4.data/scripts/collect-imports +58 -0
  41. pyflyby-1.10.4.data/scripts/find-import +38 -0
  42. pyflyby-1.10.4.data/scripts/prune-broken-imports +34 -0
  43. pyflyby-1.10.4.data/scripts/pyflyby-diff +34 -0
  44. pyflyby-1.10.4.data/scripts/reformat-imports +27 -0
  45. pyflyby-1.10.4.data/scripts/replace-star-imports +37 -0
  46. pyflyby-1.10.4.data/scripts/saveframe +299 -0
  47. pyflyby-1.10.4.data/scripts/tidy-imports +170 -0
  48. pyflyby-1.10.4.data/scripts/transform-imports +47 -0
  49. pyflyby-1.10.4.dist-info/METADATA +605 -0
  50. pyflyby-1.10.4.dist-info/RECORD +53 -0
  51. pyflyby-1.10.4.dist-info/WHEEL +6 -0
  52. pyflyby-1.10.4.dist-info/entry_points.txt +4 -0
  53. pyflyby-1.10.4.dist-info/licenses/LICENSE.txt +19 -0
pyflyby/_py.py ADDED
@@ -0,0 +1,2165 @@
1
+ # pyflyby/_py.py
2
+ # Copyright (C) 2014, 2015, 2018, 2019 Karl Chen.
3
+ # License: MIT http://opensource.org/licenses/MIT
4
+
5
+ r"""
6
+ The `py` program (part of the pyflyby project) is a command-line multitool for
7
+ running python code, with heuristic intention guessing, automatic importing,
8
+ and debugging support.
9
+
10
+ Invocation summary
11
+ ==================
12
+
13
+ .. code::
14
+
15
+ py [--file] filename.py arg1 arg2 Execute a file
16
+ py [--eval] 'function(arg1, arg2)' Evaluate an expression/statement
17
+ py [--apply] function arg1 arg2 Call function(arg1, arg2)
18
+ py [--module] modname arg1 arg2 Run a module
19
+
20
+ py --map function arg1 arg2 Call function(arg1); function(arg2)
21
+
22
+ py -i 'function(arg1, arg2)' Run file/code/etc, then run IPython
23
+ py --debug 'function(arg1, arg2)' Debug file/code/etc
24
+ py --debug PID Attach debugger to PID
25
+
26
+ py function? Get help for a function or module
27
+ py function?? Get source of a function or module
28
+
29
+ py Start IPython with autoimporter
30
+ py nb Start IPython Notebook with autoimporter
31
+
32
+
33
+ py [--add-deprecated-builtins] Inject "breakpoint", "debug_exception",
34
+ "debug_statement", "waitpoint" into
35
+ builtins. This is deprecated, and
36
+ present for backward compatibilty
37
+ but will be removed in the future.
38
+
39
+ Features
40
+ ========
41
+
42
+ * Heuristic action mode guessing: If none of --file, --eval, --apply,
43
+ --module, or --map is specified, then guess what to do, choosing one of
44
+ these actions:
45
+
46
+ * Execute (run) a file
47
+ * Evaluate concatenated arguments
48
+ * Run a module
49
+ * Call (apply) a function
50
+ * Evaluate first argument
51
+
52
+ * Automatic importing: All action modes (except run_module) automatically
53
+ import as needed.
54
+
55
+ * Heuristic argument evaluation: By default, `py --eval`, `py --apply`, and
56
+ `py --map` guess whether the arguments should be interpreted as
57
+ expressions or literal strings. A "--" by itself will designate subsequent
58
+ args as strings. A "-" by itself will be replaced by the contents of
59
+ stdin as a string.
60
+
61
+ * Merged eval/exec: Code is eval()ed or exec()ed as appropriate.
62
+
63
+ * Result printing: By default, results are pretty-printed if not None.
64
+
65
+ * Heuristic flags: "print" can be used as a function or a statement.
66
+
67
+ * Matplotlib/pylab integration: show() is called if appropriate to block on
68
+ plots.
69
+
70
+ * Enter debugger upon unhandled exception. (This functionality is enabled
71
+ by default when stdout is a tty. Use --postmortem=no to never use the
72
+ postmortem debugger. Use --postmortem=yes enable even if stdout is not a
73
+ tty. If the postmortem debugger is enabled but /dev/tty is not available,
74
+ then if an exception occurs, py will email instructions for attaching a
75
+ debugger.)
76
+
77
+ * Control-\\ (SIGQUIT) enters debugger while running (and allows continuing).
78
+
79
+ * New builtin functions such as "debugger()".
80
+
81
+ Warning
82
+ =======
83
+ `py` is intended as an interactive tool. When writing shell aliases for
84
+ interactive use, the `--safe` option can be useful. When writing scripts,
85
+ it's better to avoid all heuristic guessing; use regular `python -c ...`, or
86
+ better yet, a full-fledged python program (and run tidy-imports).
87
+
88
+
89
+ Options
90
+ =======
91
+
92
+ .. code::
93
+
94
+ Global options valid before code argument:
95
+
96
+ --args=string Interpret all arguments as literal strings.
97
+ (The "--" argument also specifies remaining arguments to be
98
+ literal strings.)
99
+ --args=eval Evaluate all arguments as expressions.
100
+ --args=auto (Default) Heuristically guess whether to evaluate arguments
101
+ as literal strings or expressions.
102
+ --output=silent Don't print the result of evaluation.
103
+ --output=str Print str(result).
104
+ --output=repr Print repr(result).
105
+ --output=pprint Print pprint.pformat(result).
106
+ --output=repr-if-not-none
107
+ Print repr(result), but only if result is not None.
108
+ --output=pprint-if-not-none
109
+ Print pprint.pformat(result), but only if result is not None.
110
+ --output=interactive
111
+ (Default) Print result.__interactive_display__() if defined,
112
+ else pprint if result is not None.
113
+ --output=exit Raise SystemExit(result).
114
+ --safe Equivalent to --args=strings and PYFLYBY_PATH=EMPTY.
115
+ --quiet, --q Log only error messages to stderr; omit info and warnings.
116
+ --interactive, --i
117
+ Run an IPython shell after completion
118
+ --debug, --d Run the target code file etc under the debugger. If a PID is
119
+ given, then instead attach a debugger to the target PID.
120
+ --verbose Turn on verbose messages from pyflyby.
121
+
122
+ Pseudo-actions valid before, after, or without code argument:
123
+
124
+ --version Print pyflyby version or version of a module.
125
+ --help, --h, --? Print this help or help for a function or module.
126
+ --source, --?? Print source code for a function or module.
127
+
128
+
129
+ Examples
130
+ ========
131
+
132
+ Start IPython with pyflyby autoimporter enabled::
133
+
134
+ $ py
135
+
136
+ Start IPython/Jupyter Notebook with pyflyby autoimporter enabled::
137
+
138
+ $ py nb
139
+
140
+ Find the ASCII value of the letter "j" (apply builtin function)::
141
+
142
+ $ py ord j
143
+ [PYFLYBY] ord('j')
144
+ 106
145
+
146
+ Decode a base64-encoded string (apply autoimported function)::
147
+
148
+ $ py b64decode aGVsbG8=
149
+ [PYFLYBY] from base64 import b64decode
150
+ [PYFLYBY] b64decode('aGVsbG8=', altchars=None)
151
+ b'hello'
152
+
153
+ Find the day of the week of some date (apply function in module)::
154
+
155
+ $ py calendar.weekday 2014 7 18
156
+ [PYFLYBY] import calendar
157
+ [PYFLYBY] calendar.weekday(2014, 7, 18)
158
+ 4
159
+
160
+ Using named arguments::
161
+
162
+ $ py calendar.weekday --day=16 --month=7 --year=2014
163
+ [PYFLYBY] import calendar
164
+ [PYFLYBY] calendar.weekday(2014, 7, 16)
165
+ 2
166
+
167
+ Using short named arguments::
168
+
169
+ $ py calendar.weekday -m 7 -d 15 -y 2014
170
+ [PYFLYBY] import calendar
171
+ [PYFLYBY] calendar.weekday(2014, 7, 15)
172
+ 1
173
+
174
+ Invert a matrix (evaluate expression, with autoimporting)::
175
+
176
+ $ py 'matrix("1 3 3; 1 4 3; 1 3 4").I'
177
+ [PYFLYBY] from numpy import matrix
178
+ [PYFLYBY] matrix("1 3 3; 1 4 3; 1 3 4").I
179
+ matrix([[ 7., -3., -3.],
180
+ [-1., 1., 0.],
181
+ [-1., 0., 1.]])
182
+
183
+ Plot cosine (evaluate expression, with autoimporting)::
184
+
185
+ $ py 'plot(cos(arange(30)))'
186
+ [PYFLYBY] from numpy import arange
187
+ [PYFLYBY] from numpy import cos
188
+ [PYFLYBY] from matplotlib.pyplot import plot
189
+ [PYFLYBY] plot(cos(arange(30)))
190
+ <plot>
191
+
192
+ Command-line calculator (multiple arguments)::
193
+
194
+ $ py 3 / 4
195
+ 0.75
196
+
197
+ Command-line calculator (single arguments)::
198
+
199
+ $ py '(5+7j) \** 12'
200
+ (65602966976-150532462080j)
201
+
202
+ Rationalize a decimal (apply bound method)::
203
+
204
+ $ py 2.5.as_integer_ratio
205
+ [PYFLYBY] 2.5.as_integer_ratio()
206
+ (5, 2)
207
+
208
+ Rationalize a decimal (apply unbound method)::
209
+
210
+ $ py float.as_integer_ratio 2.5
211
+ [PYFLYBY] float.as_integer_ratio(2.5)
212
+ (5, 2)
213
+
214
+ Rationalize decimals (map/apply)::
215
+
216
+ $ py --map float.as_integer_ratio 2.5 3.5
217
+ [PYFLYBY] float.as_integer_ratio(2.5)
218
+ (5, 2)
219
+ [PYFLYBY] float.as_integer_ratio(3.5)
220
+ (7, 2)
221
+
222
+ Square numbers (map lambda)::
223
+
224
+ $ py --map 'lambda x: x \**2' 3 4 5
225
+ [PYFLYBY] (lambda x: x \**2)(3)
226
+ 9
227
+ [PYFLYBY] (lambda x: x \**2)(4)
228
+ 16
229
+ [PYFLYBY] (lambda x: x \**2)(5)
230
+ 25
231
+
232
+ Find length of string (using "-" for stdin)::
233
+
234
+ $ echo hello | py len -
235
+ [PYFLYBY] len('hello\\n')
236
+ 6
237
+
238
+ Run stdin as code::
239
+
240
+ $ echo 'print(sys.argv[1:])' | py - hello world
241
+ [PYFLYBY] import sys
242
+ ['hello', 'world']
243
+
244
+ Run libc functions::
245
+
246
+ $ py --quiet --output=none 'CDLL("libc.so.6").printf' %03d 7
247
+ 007
248
+
249
+ Download web page::
250
+
251
+ $ py --print 'requests.get(sys.argv[1]).text' http://example.com
252
+
253
+ Get function help::
254
+
255
+ $ py b64decode?
256
+ [PYFLYBY] from base64 import b64decode
257
+ Python signature::
258
+
259
+ >> b64decode(s, altchars=None, validate=False)
260
+
261
+ Command-line signature::
262
+
263
+ $ py b64decode s [altchars [validate]]
264
+ $ py b64decode --s=... [--altchars=...] [--validate=...]
265
+ ...
266
+
267
+ Get module help::
268
+
269
+ $ py pandas?
270
+ [PYFLYBY] import pandas
271
+ Version:
272
+ 0.13.1
273
+ Filename:
274
+ /usr/local/lib/python2.7/site-packages/pandas/__init__.pyc
275
+ Docstring:
276
+ pandas - a powerful data analysis and manipulation library for Python
277
+ ...
278
+
279
+ """
280
+
281
+ from contextlib import contextmanager
282
+ from functools import total_ordering
283
+ from pathlib import Path
284
+ from shlex import quote as shquote
285
+ from types import FunctionType, MethodType, ModuleType
286
+ from typing import Any
287
+ import ast
288
+ import builtins
289
+ import inspect
290
+ import os
291
+ import re
292
+ import sys
293
+ import types
294
+ import warnings
295
+
296
+ from pyflyby._autoimp import auto_import, find_missing_imports
297
+ from pyflyby._cmdline import print_version_and_exit, syntax
298
+ from pyflyby._dbg import (add_debug_functions_to_builtins,
299
+ attach_debugger, debug_statement,
300
+ debugger, enable_faulthandler,
301
+ enable_signal_handler_debugger,
302
+ enable_sigterm_handler,
303
+ remote_print_stack)
304
+ from pyflyby._file import Filename, UnsafeFilenameError, which
305
+ from pyflyby._flags import CompilerFlags
306
+ from pyflyby._idents import is_identifier
307
+ from pyflyby._interactive import (get_ipython_terminal_app_with_autoimporter,
308
+ run_ipython_line_magic,
309
+ start_ipython_with_autoimporter)
310
+ from pyflyby._log import logger
311
+ from pyflyby._modules import ModuleHandle
312
+ from pyflyby._parse import PythonBlock
313
+ from pyflyby._util import indent, prefixes, cmp
314
+
315
+ # TODO: add --tidy-imports, etc
316
+
317
+ # TODO: new --action="concat_eval eval apply" etc. specifying multiple
318
+ # actions means try each of them in that order. then --safe can exclude
319
+ # concat-eval, and users can customize which action modes are included.
320
+ # --apply would be equivalent to --action=apply.
321
+
322
+ # TODO: plug-in system. 'py foo' should attempt something that the
323
+ # user/vendor can add to the system. leading candidate: use entry_point
324
+ # system (http://stackoverflow.com/a/774859). other candidates: maybe use
325
+ # python namespace like pyflyby.vendor.foo or pyflyby.commands.foo or
326
+ # pyflyby.magics.foo, or maybe a config file.
327
+ # TODO: note additional features in documentation feature list
328
+
329
+ # TODO: somehow do the right thing with glob.glob vs glob, pprint.pprint vs
330
+ # pprint, etc. Ideas:
331
+ # - make --apply special case detect modules and take module.module
332
+ # - enhance auto_import() to keep track of the context while finding missing imports
333
+ # - enhance auto_import() to scan for calls after importing
334
+
335
+ # TODO: pipe help/source output (all output?) through $PYFLYBY_PAGER (default "less -FRX").
336
+
337
+ # TODO: unparse ast node for info logging
338
+ # https://hg.python.org/cpython/log/tip/Tools/parser/unparse.py
339
+
340
+ # TODO: run_module should detect if the module doesn't check __name__ and
341
+ # therefore is unlikely to be meaningful to use with run_module.
342
+
343
+ # TODO: make sure run_modules etc work correctly with modules under namespace
344
+ # packages.
345
+
346
+ # TODO: detect deeper ImportError, e.g. suppose user accesses module1; module1
347
+ # imports badmodule, which can't be imported successfully and raises
348
+ # ImportError; we should get that ImportError instead of trying other things
349
+ # or turning it into a string. probably do this by changing the place where
350
+ # we import modules to first get the loader, then if import fails, raise a
351
+ # subclass of ImportError.
352
+
353
+ # TODO: provide a way to omit newline in output. maybe --output=write.
354
+
355
+ # TODO: make sure 'py -c' matches behavior of 'python -c' w.r.t. sys.modules["__main__"]
356
+ # $ py -c 'x=3;import sys; print sys.modules["__main__"].__dict__["x"]'
357
+ # mimic runpy.{_TempModule,_run_code}
358
+
359
+ # TODO: refactor this module - maybe to _heuristic.py
360
+
361
+ # TODO: add --profile, --runsnake
362
+ usage = """
363
+ py --- command-line python multitool with automatic importing
364
+
365
+ $ py [--file] filename.py arg1 arg2 Execute file
366
+ $ py [--apply] function arg1 arg2 Call function
367
+ $ py [--eval] 'function(arg1, arg2)' Evaluate code
368
+ $ py [--module] modname arg1 arg2 Run a module
369
+
370
+ $ py --debug file/code... args... Debug code
371
+ $ py --debug PID Attach debugger to PID
372
+
373
+ $ py IPython shell
374
+ """.strip()
375
+
376
+ # Default compiler flags (feature flags) used for all user code. We include
377
+ # "print_function" here, but we also use auto_flags=True, which means
378
+ # print_function may be flipped off if the code contains print statements.
379
+ FLAGS = CompilerFlags(["absolute_import", "with_statement", "division",
380
+ "print_function"])
381
+
382
+
383
+ def _get_argspec(arg:Any) -> inspect.FullArgSpec:
384
+ from inspect import getfullargspec as getargspec, FullArgSpec as ArgSpec
385
+ if isinstance(arg, FunctionType):
386
+ return getargspec(arg)
387
+ elif isinstance(arg, MethodType):
388
+ argspec = getargspec(arg)
389
+ if arg.__self__ is not None:
390
+ # For bound methods, ignore the "self" argument.
391
+ return ArgSpec(argspec.args[1:], *argspec[1:])
392
+ return argspec
393
+ elif isinstance(arg, type):
394
+ if arg.__new__ is not object.__new__:
395
+ argspec = _get_argspec(arg.__new__)
396
+ return ArgSpec(argspec.args[1:], *argspec[1:])
397
+ else:
398
+ argspec = _get_argspec(arg.__init__)
399
+ return ArgSpec(argspec.args[1:], *argspec[1:])
400
+ elif callable(arg):
401
+ # Unknown, probably a built-in method.
402
+ return ArgSpec([], "args", "kwargs", None, [], None, {})
403
+ raise TypeError(
404
+ "_get_argspec: unexpected %s" % (type(arg).__name__,))
405
+
406
+
407
+ def _requires_parens_as_function(function_name:str) -> bool:
408
+ """
409
+ Returns whether the given string of a callable would require parentheses
410
+ around it to call it.
411
+
412
+ >>> _requires_parens_as_function("foo.bar[4]")
413
+ False
414
+
415
+ >>> _requires_parens_as_function("foo+bar")
416
+ True
417
+
418
+ >>> _requires_parens_as_function("(foo+bar)()")
419
+ False
420
+
421
+ >>> _requires_parens_as_function("(foo+bar)")
422
+ False
423
+
424
+ >>> _requires_parens_as_function("(foo)+(bar)")
425
+ True
426
+
427
+ :type function_name:
428
+ ``str``
429
+ :rtype:
430
+ ``bool``
431
+ """
432
+ # TODO: this might be obsolete if we use unparse instead of keeping original
433
+ # user formatting (or alternatively, unparse should use something like this).
434
+
435
+ assert isinstance(function_name, str)
436
+
437
+ block = PythonBlock(function_name, flags=FLAGS)
438
+ node = block.expression_ast_node
439
+ if not node:
440
+ # Couldn't parse? Just assume we do need parens for now. Or should
441
+ # we raise an exception here?
442
+ return True
443
+ body = node.body
444
+ # Is it something that doesn't need parentheses?
445
+ if isinstance(body, (ast.Name, ast.Attribute, ast.Call, ast.Subscript)):
446
+ return False
447
+ # Does it already have parentheses?
448
+ n = str(function_name)
449
+ if n.startswith("(") and n.endswith(")"):
450
+ # It has parentheses, superficially. Make sure it's not something
451
+ # like "(foo)+(bar)".
452
+ flags = int(FLAGS) | ast.PyCF_ONLY_AST
453
+ try:
454
+ tnode = compile(n[1:-1], "<unknown>", "eval", flags)
455
+ except SyntaxError:
456
+ return True
457
+ if ast.dump(tnode) == ast.dump(node):
458
+ return False
459
+ else:
460
+ return True
461
+ return True
462
+
463
+
464
+ def _format_call_spec(function_name:str, obj:Any)-> str:
465
+ # using signature() is not strictly identical
466
+ # as it might look at __text_signature__ and/or respect possitional only
467
+ # forward slash:
468
+ # >>> def foo(a, /, b)
469
+ # whcih formatargspec did not do.
470
+ # argspec = _get_argspec(obj)
471
+ # old_callspect = inspect.formatargspec(*argspec)
472
+ # assert old_callspect == callspec , f"{old_callspect} !={callspec}"
473
+ callspec = str(inspect.signature(obj))
474
+ if _requires_parens_as_function(function_name):
475
+ return "(%s)%s" % (function_name, callspec)
476
+ else:
477
+ return "%s%s" % (function_name, callspec)
478
+
479
+
480
+
481
+
482
+ def _build_function_usage_string(function_name:str, obj:Any, prefix:str):
483
+ argspec = _get_argspec(obj)
484
+
485
+ usage = []
486
+ # TODO: colorize
487
+ usage.append("Python signature:")
488
+ usage.append(" >"+">> " + _format_call_spec(function_name, obj))
489
+ usage.append("")
490
+ usage.append("Command-line signature:")
491
+ keywords = argspec.varkw
492
+ if not argspec.args and argspec.varargs and keywords:
493
+ # We have no information about the arguments. It's probably a
494
+ # built-in where getargspec failed.
495
+ usage.append(" $ %s%s ...\n" % (prefix, function_name))
496
+ return "\n".join(usage)
497
+ defaults = argspec.defaults or ()
498
+ first_with_default = len(argspec.args) - len(defaults)
499
+ # Show first alternative of command-line syntax.
500
+ syntax1 = " $ %s%s" % (prefix, shquote(function_name),)
501
+ for i, arg in enumerate(argspec.args):
502
+ if i >= first_with_default:
503
+ syntax1 += " [%s" % (arg,)
504
+ else:
505
+ syntax1 += " %s" % (arg,)
506
+ if argspec.varargs:
507
+ syntax1 += " %s..." % argspec.varargs
508
+ syntax1 += "]" * len(defaults)
509
+ for arg in argspec.kwonlyargs:
510
+ if argspec.kwonlydefaults and arg in argspec.kwonlydefaults:
511
+ syntax1 += " [--%s=...]" % (arg,)
512
+ else:
513
+ syntax1 += " --%s=..." % (arg,)
514
+ if keywords:
515
+ syntax1 += " [--...]"
516
+ usage.append(syntax1)
517
+ # usage.append("or:")
518
+ syntax2 = " $ %s%s" % (prefix, shquote(function_name),)
519
+ for i, arg in enumerate(argspec.args):
520
+ if i >= first_with_default:
521
+ syntax2 += " [--%s=...]" % (arg,)
522
+ else:
523
+ syntax2 += " --%s=..." % (arg,)
524
+ for arg in argspec.kwonlyargs:
525
+ if argspec.kwonlydefaults and arg in argspec.kwonlydefaults:
526
+ syntax2 += " [--%s=...]" % (arg,)
527
+ else:
528
+ syntax2 += " --%s=..." % (arg,)
529
+ if argspec.varargs:
530
+ syntax2 += " %s..." % argspec.varargs
531
+ if keywords:
532
+ syntax2 += " [--...]"
533
+ usage.append(syntax2)
534
+ usage.append("")
535
+ return "\n".join(usage)
536
+
537
+
538
+ class ParseError(Exception):
539
+ pass
540
+
541
+
542
+ class _ParseInterruptedWantHelp(Exception):
543
+ pass
544
+
545
+
546
+ class _ParseInterruptedWantSource(Exception):
547
+ pass
548
+
549
+
550
+ class UserExpr:
551
+ """
552
+ An expression from user input, and its evaluated value.
553
+
554
+ The expression can come from a string literal or other raw value, or a
555
+ string that is evaluated as an expression, or heuristically chosen.
556
+
557
+ >>> ns = _Namespace()
558
+
559
+ Heuristic auto-evaluation::
560
+
561
+ >>> UserExpr('5+2', ns, "auto").value
562
+ 7
563
+
564
+ >>> UserExpr('5j+2', ns, "auto").value
565
+ (2+5j)
566
+
567
+ >>> UserExpr('base64.b64decode("SGFsbG93ZWVu")', ns, "auto").value
568
+ [PYFLYBY] import base64
569
+ b'Halloween'
570
+
571
+ Returning an unparsable argument as a string::
572
+
573
+ >>> UserExpr('Victory Loop', ns, "auto").value
574
+ 'Victory Loop'
575
+
576
+ Returning an undefined (and not auto-importable) argument as a string::
577
+
578
+ >>> UserExpr('Willowbrook29817621+5', ns, "auto").value
579
+ 'Willowbrook29817621+5'
580
+
581
+ Explicit literal string::
582
+
583
+ >>> UserExpr("2+3", ns, "raw_value").value
584
+ '2+3'
585
+
586
+ >>> UserExpr("'2+3'", ns, "raw_value").value
587
+ "'2+3'"
588
+
589
+ Other raw values::
590
+
591
+ >>> UserExpr(sys.exit, ns, "raw_value").value
592
+ <built-in function exit>
593
+ """
594
+
595
+ def __init__(self, arg, namespace, arg_mode, source=None):
596
+ """
597
+ Construct a new UserExpr.
598
+
599
+ :type arg:
600
+ ``str`` if ``arg_mode`` is "eval" or "auto"; anything if ``arg_mode``
601
+ is "raw_value"
602
+ :param arg:
603
+ Input user argument.
604
+ :type namespace:
605
+ `_Namespace`
606
+ :type arg_mode:
607
+ ``str``
608
+ :param arg_mode:
609
+ If ``"raw_value"``, then return ``arg`` unchanged. If ``"eval"``, then
610
+ always evaluate ``arg``. If ``"auto"``, then heuristically evaluate
611
+ if appropriate.
612
+ """
613
+ if arg_mode == "string":
614
+ arg_mode = "raw_value"
615
+ self._namespace = namespace
616
+ self._original_arg_mode = arg_mode
617
+ self._original_arg = arg
618
+ if arg_mode == "raw_value":
619
+ # self.inferred_arg_mode = "raw_value"
620
+ # self.original_source = None
621
+ if source is None:
622
+ source = PythonBlock(repr(self._original_arg))
623
+ else:
624
+ source = PythonBlock(source)
625
+ self.source = source
626
+ self.value = self._original_arg
627
+ elif arg_mode == "eval":
628
+ if source is not None:
629
+ raise ValueError(
630
+ "UserExpr(): source argument not allowed for eval")
631
+ # self.inferred_arg_mode = "eval"
632
+ self._original_arg_as_source = PythonBlock(arg, flags=FLAGS)
633
+ # self.original_source = self._original_arg_as_source
634
+ elif arg_mode == "auto":
635
+ if source is not None:
636
+ raise ValueError(
637
+ "UserExpr(): source argument not allowed for auto")
638
+ if not isinstance(arg, str):
639
+ raise ValueError(
640
+ "UserExpr(): arg must be a string if arg_mode='auto'")
641
+ self._original_arg_as_source = PythonBlock(arg, flags=FLAGS)
642
+ else:
643
+ raise ValueError("UserExpr(): bad arg_mode=%r" % (arg_mode,))
644
+
645
+ def _infer_and_evaluate(self) -> None:
646
+ if self._original_arg_mode == "raw_value":
647
+ pass
648
+ elif self._original_arg_mode == "eval":
649
+ block = self._original_arg_as_source
650
+ if not (str(block).strip()):
651
+ raise ValueError("empty input")
652
+ self.value = self._namespace.auto_eval(block)
653
+ self.source = self._original_arg_as_source #.pretty_print() # TODO
654
+ elif self._original_arg_mode == "auto":
655
+ block = self._original_arg_as_source
656
+ ERROR = object()
657
+ if not (str(block).strip()):
658
+ value = ERROR
659
+ elif not block.parsable_as_expression:
660
+ value = ERROR
661
+ else:
662
+ try:
663
+ value = self._namespace.auto_eval(block)
664
+ except UnimportableNameError:
665
+ value = ERROR
666
+ if value is ERROR:
667
+ # self.inferred_arg_mode = "raw_value"
668
+ self.value = self._original_arg
669
+ # self.original_source = None
670
+ self.source = PythonBlock(repr(self.value))
671
+ else:
672
+ # self.inferred_arg_mode = "eval"
673
+ self.value = value
674
+ # self.original_source = block
675
+ self.source = block #.pretty_print() # TODO
676
+ else:
677
+ raise AssertionError("internal error")
678
+ self._infer_and_evaluate = lambda: None
679
+
680
+ def __getattr__(self, k):
681
+ self._infer_and_evaluate()
682
+ return object.__getattribute__(self, k)
683
+
684
+ def __str__(self):
685
+ return str(self._original_arg)
686
+
687
+
688
+ def _parse_auto_apply_args(argspec, commandline_args, namespace, arg_mode="auto"):
689
+ """
690
+ Parse command-line arguments heuristically. Arguments that can be
691
+ evaluated are evaluated; otherwise they are treated as strings.
692
+
693
+ :returns:
694
+ ``args``, ``kwargs``
695
+ """
696
+ # This is implemented manually instead of using optparse or argparse. We
697
+ # do so because neither supports dynamic keyword arguments well. Optparse
698
+ # doesn't support parsing known arguments only, and argparse doesn't
699
+ # support turning off interspersed positional arguments.
700
+ def make_expr(arg, arg_mode=arg_mode):
701
+ return UserExpr(arg, namespace, arg_mode)
702
+
703
+ # Create a map from argname to default value.
704
+ defaults = argspec.defaults or ()
705
+ argname2default = {}
706
+ for argname, default in zip(argspec.args[len(argspec.args)-len(defaults):],
707
+ defaults):
708
+ argname2default[argname] = make_expr(default, "raw_value")
709
+ if argspec.kwonlydefaults:
710
+ for argname, default in argspec.kwonlydefaults.items():
711
+ argname2default[argname] = make_expr(default, "raw_value")
712
+ # Create a map from prefix to arguments with that prefix. E.g. {"foo":
713
+ # ["foobar", "foobaz"]}
714
+ prefix2argname = {}
715
+ for argname in argspec.args:
716
+ for prefix in prefixes(argname):
717
+ prefix2argname.setdefault(prefix, []).append(argname)
718
+ for argname in argspec.kwonlyargs:
719
+ for prefix in prefixes(argname):
720
+ prefix2argname.setdefault(prefix, []).append(argname)
721
+ # Enumerate over input arguments.
722
+ got_pos_args = []
723
+ got_keyword_args = {}
724
+ args = list(commandline_args)
725
+ while args:
726
+ arg = args.pop(0)
727
+ if arg in ["--?", "-?", "?"]:
728
+ raise _ParseInterruptedWantHelp
729
+ elif arg in ["--??", "-??", "??"]:
730
+ raise _ParseInterruptedWantSource
731
+ elif arg.startswith("-"):
732
+ if arg == "-":
733
+ # Read from stdin and stuff into next argument as a string.
734
+ data = sys.stdin.read()
735
+ got_pos_args.append(make_expr(data, "string"))
736
+ continue
737
+ elif arg == "--":
738
+ # Treat remaining arguments as strings.
739
+ got_pos_args.extend([make_expr(x, "string") for x in args])
740
+ del args[:]
741
+ continue
742
+ elif arg.startswith("--"):
743
+ argname = arg[2:]
744
+ else:
745
+ argname = arg[1:]
746
+ argname, equalsign, value = argname.partition("=")
747
+ argname = argname.replace("-", "_")
748
+ if not is_identifier(argname):
749
+ raise ParseError("Invalid option name %s" % (argname,))
750
+ matched_argnames = prefix2argname.get(argname, [])
751
+ if len(matched_argnames) == 1:
752
+ argname, = matched_argnames
753
+ elif len(matched_argnames) == 0:
754
+ if equalsign == "":
755
+ if argname in ["help", "h"]:
756
+ raise _ParseInterruptedWantHelp
757
+ if argname in ["source"]:
758
+ raise _ParseInterruptedWantSource
759
+ if not argspec.varkw:
760
+ raise ParseError("Unknown option name %s" %
761
+ (argname,))
762
+
763
+ elif len(matched_argnames) > 1:
764
+ raise ParseError(
765
+ "Ambiguous %s: could mean one of: %s"
766
+ % (argname,
767
+ ", ".join("--%s"%s for s in matched_argnames)))
768
+ else:
769
+ raise AssertionError
770
+ if not value:
771
+ try:
772
+ value = args.pop(0)
773
+ except IndexError:
774
+ raise ParseError("Missing argument to %s" % (arg,))
775
+ if value.startswith("--"):
776
+ raise ParseError(
777
+ "Missing argument to %s. "
778
+ "If you really want to use %r as the argument to %s, "
779
+ "then use %s=%s."
780
+ % (arg, value, arg, arg, value))
781
+ got_keyword_args[argname] = make_expr(value)
782
+ else:
783
+ got_pos_args.append(make_expr(arg))
784
+
785
+ parsed_args = []
786
+ parsed_kwargs = {}
787
+ for i, argname in enumerate(argspec.args):
788
+ if i < len(got_pos_args):
789
+ if argname in got_keyword_args:
790
+ raise ParseError(
791
+ "%s specified both as positional argument (%s) "
792
+ "and keyword argument (%s)"
793
+ % (argname, got_pos_args[i], got_keyword_args[argname]))
794
+ expr = got_pos_args[i]
795
+ else:
796
+ try:
797
+ expr = got_keyword_args.pop(argname)
798
+ except KeyError:
799
+ try:
800
+ expr = argname2default[argname]
801
+ except KeyError:
802
+ raise ParseError(
803
+ "missing required argument %s" % (argname,))
804
+ try:
805
+ value = expr.value
806
+ except Exception as e:
807
+ raise ParseError(
808
+ "Error parsing value for --%s=%s: %s: %s"
809
+ % (argname, expr, type(e).__name__, e))
810
+ parsed_args.append(value)
811
+
812
+ for argname in argspec.kwonlyargs:
813
+ try:
814
+ expr = got_keyword_args.pop(argname)
815
+ except KeyError:
816
+ try:
817
+ expr = argname2default[argname]
818
+ except KeyError:
819
+ raise ParseError(
820
+ "missing required keyword argument %s" % (argname,))
821
+
822
+ try:
823
+ value = expr.value
824
+ except Exception as e:
825
+ raise ParseError(
826
+ "Error parsing value for --%s=%s: %s: %s"
827
+ % (argname, expr, type(e).__name__, e))
828
+ parsed_kwargs[argname] = value
829
+
830
+ if len(got_pos_args) > len(argspec.args):
831
+ if argspec.varargs:
832
+ for expr in got_pos_args[len(argspec.args):]:
833
+ try:
834
+ value = expr.value
835
+ except Exception as e:
836
+ raise ParseError(
837
+ "Error parsing value for *%s: %s: %s: %s"
838
+ % (argspec.varargs, expr, type(e).__name__, e))
839
+ parsed_args.append(value)
840
+ else:
841
+ max_nargs = len(argspec.args)
842
+ if argspec.defaults:
843
+ expected = "%d-%d" % (max_nargs-len(argspec.defaults),max_nargs)
844
+ else:
845
+ expected = "%d" % (max_nargs,)
846
+ raise ParseError(
847
+ "Too many positional arguments. "
848
+ "Expected %s positional argument(s): %s. Got %d args: %s"
849
+ % (expected, ", ".join(argspec.args),
850
+ len(got_pos_args), " ".join(map(str, got_pos_args))))
851
+
852
+ for argname, expr in sorted(got_keyword_args.items()):
853
+ try:
854
+ parsed_kwargs[argname] = expr.value
855
+ except Exception as e:
856
+ raise ParseError(
857
+ "Error parsing value for --%s=%s: %s: %s"
858
+ % (argname, expr, type(e).__name__, e))
859
+
860
+ return parsed_args, parsed_kwargs
861
+
862
+
863
+ def _format_call(function_name:str, argspec, args, kwargs):
864
+ # TODO: print original unparsed arg strings
865
+ defaults = argspec.defaults or ()
866
+ first_with_default = len(argspec.args) - len(defaults)
867
+ argparts = []
868
+ for i in range(max(len(args), len(argspec.args))):
869
+ if i >= first_with_default and len(args) <= len(argspec.args):
870
+ argparts.append("%s=%r" % (argspec.args[i], args[i]))
871
+ else:
872
+ argparts.append(repr(args[i]))
873
+ for k, v in sorted(kwargs.items()):
874
+ argparts.append("%s=%r" % (k, v))
875
+ if _requires_parens_as_function(function_name):
876
+ function_name = "(%s)" % (function_name,)
877
+ r = "%s(%s)" % (function_name, ", ".join(argparts))
878
+ return r
879
+
880
+
881
+ class UnimportableNameError(NameError):
882
+ pass
883
+
884
+
885
+ class NotAFunctionError(Exception):
886
+ pass
887
+
888
+
889
+ def _get_help(expr:UserExpr, verbosity:int=1) -> str:
890
+ """
891
+ Construct a help string.
892
+
893
+ :type expr:
894
+ `UserExpr`
895
+ :param expr:
896
+ Object to generate help for.
897
+ :rtype:
898
+ ``str``
899
+ """
900
+ # TODO: colorize headers
901
+ result = ""
902
+ obj = expr.value
903
+ name:str = str(expr.source)
904
+ if callable(obj):
905
+ prefix = os.path.basename(sys.orig_argv[0]) + " "
906
+ result += _build_function_usage_string(name, obj, prefix)
907
+ if verbosity == 0:
908
+ include_filename = False
909
+ include_doc = False
910
+ include_source = False
911
+ elif verbosity == 1:
912
+ include_filename = True
913
+ include_doc = True
914
+ include_source = False
915
+ elif verbosity == 2:
916
+ include_filename = True
917
+ include_doc = False
918
+ include_source = True
919
+ else:
920
+ raise ValueError("invalid verbosity=%r" % (verbosity,))
921
+ try:
922
+ version = obj.__version__
923
+ except Exception:
924
+ pass
925
+ else:
926
+ result += "\nVersion:\n %s\n" % (version,)
927
+ if include_filename:
928
+ try:
929
+ filename = inspect.getfile(obj)
930
+ except Exception:
931
+ pass
932
+ else:
933
+ result += "\nFilename:\n %s\n" % (filename,)
934
+ if include_source:
935
+ try:
936
+ source = inspect.getsource(obj)
937
+ except Exception:
938
+ source = ""
939
+ if source:
940
+ # TODO: colorize source
941
+ result += "\nSource:\n%s\n" % (indent(source, " "))
942
+ else:
943
+ source = "(Not available)"
944
+ include_doc = True
945
+ if include_doc:
946
+ doc = (inspect.getdoc(obj) or "").strip() or "(No docstring)"
947
+ result += "\nDocstring:\n%s" % (indent(doc, " "))
948
+ return result
949
+
950
+
951
+ _enable_postmortem_debugger = None
952
+
953
+ def _handle_user_exception(exc_info=None):
954
+ """
955
+ Handle an exception in user code.
956
+ """
957
+ # TODO: Make tracebacks show user code being executed. IPython does that
958
+ # by stuffing linecache.cache, and also advising linecache.checkcache. We
959
+ # can either advise it ourselves also, or re-use IPython.core.compilerop.
960
+ # Probably better to advise ourselves. Add "<pyflyby-input-md5[:12]>" to
961
+ # linecache. Perhaps do it in a context manager that removes from
962
+ # linecache when done. Advise checkcache to protect any "<pyflyby-*>".
963
+ # Do it for all code compiled from here, including args, debug_statement,
964
+ # etc.
965
+ if exc_info is None:
966
+ exc_info = sys.exc_info()
967
+ if exc_info[2].tb_next:
968
+ exc_info = (exc_info[0], exc_info[1],
969
+ exc_info[2].tb_next) # skip this traceback
970
+ # If ``_enable_postmortem_debugger`` is enabled, then debug the exception.
971
+ # By default, this is enabled run running in a tty.
972
+ # We check isatty(1) here because we want 'py ... | cat' to never go into
973
+ # the debugger. Note that debugger() also checks whether /dev/tty is
974
+ # usable (and if not, waits for attach).
975
+ if _enable_postmortem_debugger:
976
+ # *** Run debugger. ***
977
+ debugger(exc_info)
978
+ # TODO: consider using print_verbose_tb(*exc_info)
979
+ import traceback
980
+ traceback.print_exception(*exc_info)
981
+ raise SystemExit(1)
982
+
983
+
984
+ def auto_apply(function, commandline_args, namespace, arg_mode=None,
985
+ debug=False):
986
+ """
987
+ Call ``function`` on command-line arguments. Arguments can be positional
988
+ or keyword arguments like "--foo=bar". Arguments are by default
989
+ heuristically evaluated.
990
+
991
+ :type function:
992
+ ``UserExpr``
993
+ :param function:
994
+ Function to apply.
995
+ :type commandline_args:
996
+ ``list`` of ``str``
997
+ :param commandline_args:
998
+ Arguments to ``function`` as strings.
999
+ :param arg_mode:
1000
+ How to interpret ``commandline_args``. If ``"string"``, then treat them
1001
+ as literal strings. If ``"eval"``, then evaluate all arguments as
1002
+ expressions. If ``"auto"`` (the default), then heuristically decide
1003
+ whether to treat as expressions or strings.
1004
+ """
1005
+ if not isinstance(function, UserExpr):
1006
+ raise TypeError
1007
+ if not callable(function.value):
1008
+ raise NotAFunctionError("Not a function", function.value)
1009
+ arg_mode = _interpret_arg_mode(arg_mode, default="auto")
1010
+ # Parse command-line arguments.
1011
+ argspec = _get_argspec(function.value)
1012
+ try:
1013
+ args, kwargs = _parse_auto_apply_args(argspec, commandline_args,
1014
+ namespace, arg_mode=arg_mode)
1015
+ except _ParseInterruptedWantHelp:
1016
+ usage = _get_help(function, verbosity=1)
1017
+ print(usage)
1018
+ raise SystemExit(0)
1019
+ except _ParseInterruptedWantSource:
1020
+ usage = _get_help(function, verbosity=2)
1021
+ print(usage)
1022
+ raise SystemExit(0)
1023
+ except ParseError as e:
1024
+ # Failed parsing command-line arguments. Print usage.
1025
+ logger.error(e)
1026
+ usage = _get_help(function, verbosity=(1 if logger.info_enabled else 0))
1027
+ sys.stderr.write("\n" + usage)
1028
+ raise SystemExit(1)
1029
+ # Log what we're doing.
1030
+
1031
+ logger.info("%s", _format_call(str(function.source), argspec, args, kwargs))
1032
+
1033
+ # *** Call the function. ***
1034
+ f = function.value
1035
+ try:
1036
+ if debug:
1037
+ result = debug_statement("f(*args, **kwargs)")
1038
+ else:
1039
+ result = f(*args, **kwargs)
1040
+ return result
1041
+ except SystemExit:
1042
+ raise
1043
+ # TODO: handle "quit" by user here specially instead of returning None.
1044
+ # May need to reimplement pdb.runeval() so we can catch BdbQuit.
1045
+ except:
1046
+ # Handle exception in user code.
1047
+ _handle_user_exception()
1048
+
1049
+
1050
+ @total_ordering
1051
+ class LoggedList:
1052
+ """
1053
+ List that logs which members have not yet been accessed (nor removed).
1054
+ """
1055
+
1056
+ _ACCESSED = object()
1057
+
1058
+ def __init__(self, items):
1059
+ self._items = list(items)
1060
+ self._unaccessed = list(self._items)
1061
+
1062
+ def append(self, x):
1063
+ self._unaccessed.append(self._ACCESSED)
1064
+ self._items.append(x)
1065
+
1066
+ def count(self):
1067
+ return self._items.count()
1068
+
1069
+ def extend(self, new_items):
1070
+ new_items = list(new_items)
1071
+ self._unaccessed.extend([self._ACCESSED] * len(new_items))
1072
+ self._items.extend(new_items)
1073
+
1074
+ def index(self, x, *start_stop):
1075
+ index = self._items.index(x, *start_stop) # may raise ValueError
1076
+ self._unaccessed[index] = self._ACCESSED
1077
+ return index
1078
+
1079
+ def insert(self, index, x):
1080
+ self._unaccessed.insert(index, self._ACCESSED)
1081
+ self._items.insert(index, x)
1082
+
1083
+ def pop(self, index):
1084
+ self._unaccessed.pop(index)
1085
+ return self._items.pop(index)
1086
+
1087
+ def remove(self, x):
1088
+ index = self._items.index(x)
1089
+ self.pop(index)
1090
+
1091
+ def reverse(self):
1092
+ self._items.reverse()
1093
+ self._unaccessed.reverse()
1094
+
1095
+ def sort(self):
1096
+ indexes = range(len(self._items))
1097
+ indexes.sort(key=self._items.__getitem__) # argsort
1098
+ self._items = [self._items[i] for i in indexes]
1099
+ self._unaccessed = [self._unaccessed[i] for i in indexes]
1100
+
1101
+ def __add__(self, other):
1102
+ return self._items + other
1103
+
1104
+ def __contains__(self, x):
1105
+ try:
1106
+ self.index(x)
1107
+ return True
1108
+ except ValueError:
1109
+ return False
1110
+
1111
+ def __delitem__(self, x):
1112
+ del self._items[x]
1113
+ del self._unaccessed[x]
1114
+
1115
+ def __eq__(self, other):
1116
+ if not isinstance(other, LoggedList):
1117
+ return NotImplemented
1118
+ return self._items == other._items
1119
+
1120
+ def __ne__(self, other):
1121
+ return not (self == other)
1122
+
1123
+ # The rest are defined by total_ordering
1124
+ def __lt__(self, other):
1125
+ if not isinstance(other, LoggedList):
1126
+ return NotImplemented
1127
+ return self._items < other._items
1128
+
1129
+ def __cmp__(self, x):
1130
+ return cmp(self._items, x)
1131
+
1132
+ def __getitem__(self, idx):
1133
+ result = self._items[idx]
1134
+ if isinstance(idx, slice):
1135
+ self._unaccessed[idx] = [self._ACCESSED]*len(result)
1136
+ else:
1137
+ self._unaccessed[idx] = self._ACCESSED
1138
+ return result
1139
+
1140
+ def __hash__(self):
1141
+ raise TypeError("unhashable type: 'LoggedList'")
1142
+
1143
+ def __iadd__(self, x):
1144
+ self.extend(x)
1145
+
1146
+ def __imul__(self, n):
1147
+ self._items *= n
1148
+ self._unaccessed *= n
1149
+
1150
+ def __iter__(self):
1151
+ # Todo: detect mutation while iterating.
1152
+ for i, x in enumerate(self._items):
1153
+ self._unaccessed[i] = self._ACCESSED
1154
+ yield x
1155
+
1156
+ def __len__(self):
1157
+ return len(self._items)
1158
+
1159
+
1160
+ def __mul__(self, n):
1161
+ return self._items * n
1162
+
1163
+ def __reduce__(self):
1164
+ return
1165
+
1166
+ def __repr__(self):
1167
+ self._unaccessed[:] = [self._ACCESSED]*len(self._unaccessed)
1168
+ return repr(self._items)
1169
+
1170
+ def __reversed__(self):
1171
+ # Todo: detect mutation while iterating.
1172
+ for i in reversed(range(len(self._items))):
1173
+ self._unaccessed[i] = self._ACCESSED
1174
+ yield self._items[i]
1175
+
1176
+ def __rmul__(self, n):
1177
+ return self._items * n
1178
+
1179
+ def __setitem__(self, idx, value):
1180
+ self._items[idx] = value
1181
+ if isinstance(idx, slice):
1182
+ self._unaccessed[idx] = [self._ACCESSED]*len(value)
1183
+ else:
1184
+ self._unaccessed[idx] = value
1185
+
1186
+ def __str__(self):
1187
+ self._unaccessed[:] = [self._ACCESSED]*len(self._unaccessed)
1188
+ return str(self._items)
1189
+
1190
+ @property
1191
+ def unaccessed(self):
1192
+ return [x for x in self._unaccessed if x is not self._ACCESSED]
1193
+
1194
+
1195
+ @contextmanager
1196
+ def SysArgvCtx(*args):
1197
+ """
1198
+ Context manager that:
1199
+ * Temporarily sets sys.argv = args.
1200
+ * At end of context, complains if any args were never accessed.
1201
+ """
1202
+ # There should always be at least one arg, since the first one is
1203
+ # the program name.
1204
+ if not args:
1205
+ raise ValueError("Missing args")
1206
+ nargs = len(args) - 1
1207
+ # Create a list proxy that will log accesses.
1208
+ argv = LoggedList(args)
1209
+ # Don't consider first argument to be interesting.
1210
+ argv[0]
1211
+ orig_argv = list(sys.argv)
1212
+ try:
1213
+ sys.argv = argv
1214
+ # Run context code.
1215
+ yield
1216
+ # Complain if there are unaccessed arguments.
1217
+ unaccessed = argv.unaccessed
1218
+ if not unaccessed:
1219
+ pass
1220
+ else:
1221
+ if nargs == 1:
1222
+ msg = ("You specified a command-line argument, but your code didn't use it: %s"
1223
+ % (unaccessed[0],))
1224
+ elif len(unaccessed) == nargs:
1225
+ msg = ("You specified %d command-line arguments, but your code didn't use them: %s"
1226
+ % (len(unaccessed), " ".join(unaccessed)))
1227
+ else:
1228
+ msg = ("You specified %d command-line arguments, but your code didn't use %d of them: %s"
1229
+ % (nargs, len(unaccessed), " ".join(unaccessed)))
1230
+ msg2 = "\nIf this is intentional, access 'sys.argv[:]' somewhere in your code."
1231
+ logger.error(msg + msg2)
1232
+ raise SystemExit(1)
1233
+ finally:
1234
+ sys.argv = orig_argv
1235
+
1236
+
1237
+ def _as_filename_if_seems_like_filename(arg):
1238
+ """
1239
+ If ``arg`` seems like a filename, then return it as one.
1240
+
1241
+ >>> bool(_as_filename_if_seems_like_filename("foo.py"))
1242
+ True
1243
+
1244
+ >>> bool(_as_filename_if_seems_like_filename("%foo.py"))
1245
+ False
1246
+
1247
+ >>> bool(_as_filename_if_seems_like_filename("foo(bar)"))
1248
+ False
1249
+
1250
+ >>> bool(_as_filename_if_seems_like_filename("/foo/bar/baz.quux-660470"))
1251
+ True
1252
+
1253
+ >>> bool(_as_filename_if_seems_like_filename("../foo/bar-24084866"))
1254
+ True
1255
+
1256
+ :type arg:
1257
+ ``str``
1258
+ :rtype:
1259
+ ``Filename``
1260
+ """
1261
+ try:
1262
+ filename = Filename(arg)
1263
+ except UnsafeFilenameError:
1264
+ # If the filename isn't a "safe" filename, then don't treat it as one,
1265
+ # and don't even check whether it exists. This means that for an
1266
+ # argument like "foo(bar)" or "lambda x:x*y" we won't even check
1267
+ # existence. This is both a performance optimization and a safety
1268
+ # valve to avoid unsafe filenames being created to intercept expressions.
1269
+ return None
1270
+ # If the argument "looks" like a filename and is unlikely to be a python
1271
+ # expression, then assume it is a filename. We assume so regardless of
1272
+ # whether the file actually exists; if it turns out to not exist, we'll
1273
+ # complain later.
1274
+ if arg.startswith("/") or arg.startswith("./") or arg.startswith("../"):
1275
+ return filename
1276
+ if filename.ext == ".py":
1277
+ # TODO: .pyc, .pyo
1278
+ return which(arg) or filename
1279
+ # Even if it doesn't obviously look like a filename, but it does exist as
1280
+ # a filename, then use it as one.
1281
+ if filename.exists:
1282
+ return filename
1283
+ # If it's a plain name and we can find an executable on $PATH, then use
1284
+ # that.
1285
+ if re.match("^[a-zA-Z0-9_-]+$", arg):
1286
+ filename = which(arg)
1287
+ if not filename:
1288
+ return None
1289
+ if not _has_python_shebang(filename):
1290
+ logger.debug("Found %s but it doesn't seem like a python script",
1291
+ filename)
1292
+ return None
1293
+ return filename
1294
+ return None
1295
+
1296
+
1297
+ def _has_python_shebang(filename):
1298
+ """
1299
+ Return whether the first line contains #!...python...
1300
+
1301
+ Used for confirming that an executable script found via which() is
1302
+ actually supposed to be a python script.
1303
+
1304
+ Note that this test is only needed for scripts found via which(), since
1305
+ otherwise the shebang is not necessary.
1306
+ """
1307
+ assert isinstance(filename, Filename)
1308
+ with open(str(filename), 'rb') as f:
1309
+ line = f.readline(1024)
1310
+ return line.startswith(b"#!") and b"python" in line
1311
+
1312
+
1313
+
1314
+ def _interpret_arg_mode(arg, default="auto"):
1315
+ """
1316
+ >>> _interpret_arg_mode("Str")
1317
+ 'string'
1318
+ """
1319
+ if arg is None:
1320
+ arg = default
1321
+ if arg == "auto" or arg == "eval" or arg == "string":
1322
+ return arg # optimization for interned strings
1323
+ rarg = str(arg).strip().lower()
1324
+ if rarg in ["eval", "evaluate", "exprs", "expr", "expressions", "expression", "e"]:
1325
+ return "eval"
1326
+ elif rarg in ["strings", "string", "str", "strs", "literal", "literals", "s"]:
1327
+ return "string"
1328
+ elif rarg in ["auto", "automatic", "a"]:
1329
+ return "auto"
1330
+ elif rarg == "error":
1331
+ # Intentionally not documented to user
1332
+ return "error"
1333
+ else:
1334
+ raise ValueError(
1335
+ "Invalid arg_mode=%r; expected one of eval/string/auto"
1336
+ % (arg,))
1337
+
1338
+
1339
+ def _interpret_output_mode(arg, default="interactive"):
1340
+ """
1341
+ >>> _interpret_output_mode('Repr_If_Not_None')
1342
+ 'repr-if-not-none'
1343
+ """
1344
+ if arg is None:
1345
+ arg = default
1346
+ rarg = str(arg).strip().lower().replace("-", "").replace("_", "")
1347
+ if rarg in ["none", "no", "n", "silent"]:
1348
+ return "silent"
1349
+ elif rarg in ["interactive", "i"]:
1350
+ return "interactive"
1351
+ elif rarg in ["print", "p", "string", "str"]:
1352
+ return "str"
1353
+ elif rarg in ["repr", "r"]:
1354
+ return "repr"
1355
+ elif rarg in ["pprint", "pp"]:
1356
+ return "pprint"
1357
+ elif rarg in ["reprifnotnone", "reprunlessnone", "rn"]:
1358
+ return "repr-if-not-none"
1359
+ elif rarg in ["pprintifnotnone", "pprintunlessnone", "ppn"]:
1360
+ return "pprint-if-not-none"
1361
+ elif rarg in ["systemexit", "exit", "raise"]:
1362
+ return "exit"
1363
+ else:
1364
+ raise ValueError(
1365
+ "Invalid output=%r; expected one of "
1366
+ "silent/interactive/str/repr/pprint/repr-if-not-none/pprint-if-not-none/exit"
1367
+ % (arg,))
1368
+
1369
+
1370
+ def print_result(result, output_mode):
1371
+ output_mode = _interpret_output_mode(output_mode)
1372
+ if output_mode == "silent":
1373
+ return
1374
+ if output_mode == "interactive":
1375
+ # TODO: support IPython output stuff (text/plain)
1376
+ try:
1377
+ idisp = result.__interactive_display__
1378
+ except Exception:
1379
+ output_mode = "pprint-if-not-none"
1380
+ else:
1381
+ result = idisp()
1382
+ output_mode = "print-if-not-none"
1383
+ # Fall through.
1384
+ if output_mode == "str":
1385
+ print(str(result))
1386
+ elif output_mode == "repr":
1387
+ print(repr(result))
1388
+ elif output_mode == "pprint":
1389
+ import pprint
1390
+ pprint.pprint(result) # or equivalently, print pprint.pformat(result)
1391
+ elif output_mode == "repr-if-not-none":
1392
+ if result is not None:
1393
+ print(repr(result))
1394
+ elif output_mode == "print-if-not-none":
1395
+ if result is not None:
1396
+ print(result)
1397
+ elif output_mode == "pprint-if-not-none":
1398
+ if result is not None:
1399
+ import pprint
1400
+ pprint.pprint(result)
1401
+ elif output_mode == "exit":
1402
+ # TODO: only raise at end after pre_exit
1403
+ raise SystemExit(result)
1404
+ else:
1405
+ raise AssertionError("unexpected output_mode=%r" % (output_mode,))
1406
+
1407
+
1408
+ def _get_path_links(p: Path):
1409
+ """Gets path links including all symlinks.
1410
+
1411
+ Adapted from `IPython.core.interactiveshell.InteractiveShell.get_path_links`.
1412
+ """
1413
+ paths = [p]
1414
+ while p.is_symlink():
1415
+ new_path = Path(os.readlink(p))
1416
+ if not new_path.is_absolute():
1417
+ new_path = p.parent / new_path
1418
+ p = new_path
1419
+ paths.append(p)
1420
+ return paths
1421
+
1422
+
1423
+ def _init_virtualenv():
1424
+ """Add the current virtualenv to sys.path so the user can import modules from it.
1425
+
1426
+ A warning will appear suggesting the user installs IPython in the
1427
+ virtualenv, but for many cases, it probably works well enough.
1428
+
1429
+ Adapted `IPython.core.interactiveshell.InteractiveShell.init_virtualenv`.
1430
+ """
1431
+ if 'VIRTUAL_ENV' not in os.environ:
1432
+ # Not in a virtualenv
1433
+ return
1434
+ elif os.environ["VIRTUAL_ENV"] == "":
1435
+ warnings.warn("Virtual env path set to '', please check if this is intended.")
1436
+ return
1437
+
1438
+ p = Path(sys.executable)
1439
+ p_venv = Path(os.environ["VIRTUAL_ENV"]).resolve()
1440
+
1441
+ # fallback venv detection:
1442
+ # stdlib venv may symlink sys.executable, so we can't use realpath.
1443
+ # but others can symlink *to* the venv Python, so we can't just use sys.executable.
1444
+ # So we just check every item in the symlink tree (generally <= 3)
1445
+ paths = _get_path_links(p)
1446
+
1447
+ # In Cygwin paths like "c:\..." and '\cygdrive\c\...' are possible
1448
+ if len(p_venv.parts) > 2 and p_venv.parts[1] == "cygdrive":
1449
+ drive_name = p_venv.parts[2]
1450
+ p_venv = (drive_name + ":/") / Path(*p_venv.parts[3:])
1451
+
1452
+ if any(p_venv == p.parents[1].resolve() for p in paths):
1453
+ # Our exe is inside or has access to the virtualenv, don't need to do anything.
1454
+ return
1455
+
1456
+ if sys.platform == "win32":
1457
+ virtual_env = str(Path(os.environ["VIRTUAL_ENV"], "Lib", "site-packages"))
1458
+ else:
1459
+ virtual_env_path = Path(
1460
+ os.environ["VIRTUAL_ENV"], "lib", "python{}.{}", "site-packages"
1461
+ )
1462
+ p_ver = sys.version_info[:2]
1463
+
1464
+ # Predict version from py[thon]-x.x in the $VIRTUAL_ENV
1465
+ re_m = re.search(r"\bpy(?:thon)?([23])\.(\d+)\b", os.environ["VIRTUAL_ENV"])
1466
+ if re_m:
1467
+ predicted_path = Path(str(virtual_env_path).format(*re_m.groups()))
1468
+ if predicted_path.exists():
1469
+ p_ver = re_m.groups()
1470
+
1471
+ virtual_env = str(virtual_env_path).format(*p_ver)
1472
+
1473
+ warnings.warn(
1474
+ "Attempting to work in a virtualenv. If you encounter problems, "
1475
+ "please install pyflyby inside the virtualenv."
1476
+ )
1477
+ import site
1478
+ sys.path.insert(0, virtual_env)
1479
+ site.addsitedir(virtual_env)
1480
+
1481
+
1482
+ class _Namespace(object):
1483
+ fake_main: ModuleType
1484
+
1485
+ def __init__(self):
1486
+ self.fake_main = ModuleType("__main__")
1487
+ _init_virtualenv()
1488
+ self.fake_main.__dict__.setdefault("__builtin__", builtins)
1489
+ self.fake_main.__dict__.setdefault("__builtins__", builtins)
1490
+ self.globals = self.fake_main.__dict__
1491
+ self.autoimported = {}
1492
+
1493
+ def auto_import(self, arg):
1494
+ return auto_import(arg, [self.globals], autoimported=self.autoimported)
1495
+
1496
+ def auto_eval(self, block, mode=None, info=False, auto_import=True,
1497
+ debug=False):
1498
+ """
1499
+ Evaluate ``block`` with auto-importing.
1500
+ """
1501
+ # Equivalent to::
1502
+ # auto_eval(arg, mode=mode, flags=FLAGS, globals=self.globals)
1503
+ # but better logging and error raising.
1504
+ if not isinstance(block, PythonBlock):
1505
+ block = PythonBlock(block, flags=FLAGS, auto_flags=True)
1506
+ if auto_import and not self.auto_import(block):
1507
+ missing = find_missing_imports(block, [self.globals])
1508
+ mstr = ", ".join(repr(str(x)) for x in missing)
1509
+ if len(missing) == 1:
1510
+ msg = "name %s is not defined and not importable" % mstr
1511
+ elif len(missing) > 1:
1512
+ msg = "names %s are not defined and not importable" % mstr
1513
+ else:
1514
+ raise AssertionError
1515
+ raise UnimportableNameError(msg)
1516
+ if info:
1517
+ logger.info(block)
1518
+ try:
1519
+ # TODO: enter text into linecache
1520
+ if debug:
1521
+ return debug_statement(block, self.globals)
1522
+ else:
1523
+ code = block.compile(mode=mode)
1524
+ try:
1525
+ main = sys.modules["__main__"]
1526
+ sys.modules["__main__"] = self.fake_main
1527
+
1528
+ return eval(code, self.globals, self.globals)
1529
+ finally:
1530
+ sys.modules["__main__"] = main
1531
+ except SystemExit:
1532
+ raise
1533
+ except:
1534
+ _handle_user_exception()
1535
+
1536
+ def __repr__(self):
1537
+ return "<{} object at 0x{:0x} \nglobals:{} \nautoimported:{}>".format(
1538
+ type(self).__name__, id(self), self.globals, self.autoimported
1539
+ )
1540
+
1541
+
1542
+ class _PyMain(object):
1543
+
1544
+ def __init__(self, args):
1545
+ self.main_args = args
1546
+ self.namespace = _Namespace()
1547
+ self.result = None
1548
+ self.ipython_app = None
1549
+
1550
+ def exec_stdin(self, cmd_args):
1551
+ arg_mode = _interpret_arg_mode(self.arg_mode, default="string")
1552
+ output_mode = _interpret_output_mode(self.output_mode, default="silent")
1553
+ cmd_args = [UserExpr(a, self.namespace, arg_mode).value
1554
+ for a in cmd_args]
1555
+ with SysArgvCtx(*cmd_args):
1556
+ result = self.namespace.auto_eval(Filename.STDIN, debug=self.debug)
1557
+ print_result(result, output_mode)
1558
+ self.result = result
1559
+
1560
+ def eval(self, cmd, cmd_args):
1561
+ arg_mode = _interpret_arg_mode(self.arg_mode, default="string")
1562
+ output_mode = _interpret_output_mode(self.output_mode)
1563
+ cmd_args = [UserExpr(a, self.namespace, arg_mode).value
1564
+ for a in cmd_args]
1565
+ with SysArgvCtx("-c", *cmd_args):
1566
+ cmd = PythonBlock(cmd)
1567
+ result = self.namespace.auto_eval(cmd, info=True, debug=self.debug)
1568
+ # TODO: make auto_eval() plow ahead even if there are unimportable
1569
+ # names, after warning
1570
+ print_result(result, output_mode)
1571
+ self.result = result
1572
+
1573
+ def execfile(self, filename_arg, cmd_args):
1574
+ # TODO: pass filename to import db target_filename; unit test.
1575
+ # TODO: set __file__
1576
+ # TODO: support compiled (.pyc/.pyo) files
1577
+ arg_mode = _interpret_arg_mode(self.arg_mode, default="string")
1578
+ output_mode = _interpret_output_mode(self.output_mode)
1579
+ cmd_args = [UserExpr(a, self.namespace, arg_mode).value
1580
+ for a in cmd_args]
1581
+ additional_msg = ""
1582
+ if isinstance(filename_arg, Filename):
1583
+ filename = filename_arg
1584
+ elif filename_arg == "-":
1585
+ filename = Filename.STDIN
1586
+ elif "/" in filename_arg:
1587
+ filename = Filename(filename_arg)
1588
+ else:
1589
+ filename = which(filename_arg)
1590
+ if not filename:
1591
+ filename = Filename(filename_arg)
1592
+ additional_msg = (" (and didn't find %s on $PATH)"
1593
+ % (filename_arg,))
1594
+ elif not _has_python_shebang(filename):
1595
+ additional_msg = (" (found %s but it doesn't look "
1596
+ "like python source"
1597
+ % (filename,))
1598
+ filename = Filename(filename_arg)
1599
+ if not filename.exists:
1600
+ raise Exception("No such file: %s%s" % (filename, additional_msg))
1601
+ with SysArgvCtx(str(filename), *cmd_args):
1602
+ sys.path.insert(0, str(filename.dir))
1603
+ self.namespace.globals["__file__"] = str(filename)
1604
+ result = self.namespace.auto_eval(filename, debug=self.debug)
1605
+ print_result(result, output_mode)
1606
+ self.result = result
1607
+
1608
+ def apply(self, function, cmd_args):
1609
+ arg_mode = _interpret_arg_mode(self.arg_mode, default="auto")
1610
+ output_mode = _interpret_output_mode(self.output_mode)
1611
+ # Todo: what should we set argv to?
1612
+ result = auto_apply(function, cmd_args, self.namespace, arg_mode,
1613
+ debug=self.debug)
1614
+ print_result(result, output_mode)
1615
+ self.result = result
1616
+
1617
+ def _seems_like_runnable_module(self, arg):
1618
+ if not is_identifier(arg, dotted=True):
1619
+ # It's not a single (dotted) identifier.
1620
+ return False
1621
+ if not find_missing_imports(arg, [{}]):
1622
+ # It's off of a builtin, e.g. "str.upper"
1623
+ return False
1624
+ m = ModuleHandle(arg)
1625
+ if m.parent:
1626
+ # Auto-import the parent, which is necessary in order to get the
1627
+ # filename of the module. ``ModuleHandle.filename`` does this
1628
+ # automatically, but we do it explicitly here so that we log
1629
+ # the import of the parent module.
1630
+ if not self.namespace.auto_import(str(m.parent.name)):
1631
+ return False
1632
+ if not m.filename:
1633
+ logger.debug("Module %s doesn't have a source filename", m)
1634
+ return False
1635
+ # TODO: check that the source accesses __main__ (ast traversal?)
1636
+ return True
1637
+
1638
+ def heuristic_cmd(self, cmd, cmd_args, function_name=None):
1639
+ output_mode = _interpret_output_mode(self.output_mode)
1640
+ # If the "command" is just a module name, then call run_module. Make
1641
+ # sure we check that it's not a builtin.
1642
+ if self._seems_like_runnable_module(str(cmd)):
1643
+ self.heuristic_run_module(str(cmd), cmd_args)
1644
+ return
1645
+ # FIXME TODO heed arg_mode for non-apply case. This is tricky to
1646
+ # implement; will require assigning some proxy class to sys.argv
1647
+ # that's more sophisticated than just logging.
1648
+ with SysArgvCtx("-c", *cmd_args):
1649
+ # Log the expression before we evaluate it, unless we're likely to
1650
+ # log another substantially similar line. (We can only guess
1651
+ # heuristically whether it's interesting enough to log it. And we
1652
+ # can't know whether the result will be callable until we evaluate
1653
+ # it.)
1654
+ info = not re.match("^[a-zA-Z0-9_.]+$", function_name)
1655
+ result = self.namespace.auto_eval(cmd, info=info, debug=self.debug)
1656
+ if callable(result):
1657
+ function = UserExpr(
1658
+ result, self.namespace, "raw_value", function_name)
1659
+ result = auto_apply(function, cmd_args, self.namespace,
1660
+ self.arg_mode, debug=self.debug)
1661
+ print_result(result, output_mode)
1662
+ self.result = result
1663
+ sys.argv[:] # mark as accessed
1664
+ else:
1665
+ if not info:
1666
+ # We guessed wrong earlier and didn't log yet; log now.
1667
+ logger.info(cmd)
1668
+ print_result(result, output_mode)
1669
+ self.result = result
1670
+ unaccessed = sys.argv.unaccessed
1671
+ if unaccessed:
1672
+ logger.error(
1673
+ "%s is not callable. Unexpected argument(s): %s",
1674
+ result, " ".join(unaccessed))
1675
+ sys.argv[:] # don't complain again
1676
+
1677
+ def run_module(self, module, args):
1678
+ arg_mode = _interpret_arg_mode(self.arg_mode, default="string")
1679
+ if arg_mode != "string":
1680
+ raise NotImplementedError(
1681
+ "run_module only supports string arguments")
1682
+ module = ModuleHandle(module)
1683
+ logger.info("python -m %s", ' '.join([str(module.name)] + args))
1684
+ # Imitate 'python -m'.
1685
+ # TODO: include only the traceback below runpy.run_module
1686
+ # os.execvp(sys.executable, [sys.argv[0], "-m", modname] + args)
1687
+ sys.argv = [str(module.filename)] + args
1688
+ import runpy
1689
+ if self.debug:
1690
+ # TODO: break closer to user code
1691
+ debugger()
1692
+ try:
1693
+ runpy.run_module(str(module.name), run_name="__main__")
1694
+ except SystemExit:
1695
+ raise
1696
+ except:
1697
+ _handle_user_exception()
1698
+
1699
+ def heuristic_run_module(self, module, args):
1700
+ module = ModuleHandle(module)
1701
+ # If the user ran 'py numpy --version', then print the numpy
1702
+ # version, i.e. same as 'py --version numpy'. This has the
1703
+ # downside of shadowing a possible "--version" feature
1704
+ # implemented by the module itself. However, this is probably
1705
+ # not a big deal, because (1) a full-featured program that
1706
+ # supports --version would normally have a driver script and
1707
+ # not rely on 'python -m foo'; (2) it would probably do
1708
+ # something similar anyway; (3) the user can do 'py -m foo
1709
+ # --version' if necessary.
1710
+ if len(args)==1 and args[0] in ["--version", "-version"]:
1711
+ self.print_version(module)
1712
+ return
1713
+ if len(args)==1 and args[0] in ["--help", "-help", "--h", "-h",
1714
+ "--?", "-?", "?"]:
1715
+ expr = UserExpr(module.module, None, "raw_value",
1716
+ source=str(module.name))
1717
+ usage = _get_help(expr, 1)
1718
+ print(usage)
1719
+ return
1720
+ if len(args)==1 and args[0] in ["--source", "-source",
1721
+ "--??", "-??", "??"]:
1722
+ expr = UserExpr(module.module, None, "raw_value",
1723
+ source=str(module.name))
1724
+ usage = _get_help(expr, 2)
1725
+ print(usage)
1726
+ return
1727
+ # TODO: check if module checks __main__
1728
+ self.run_module(module, args)
1729
+
1730
+ def print_version(self, arg):
1731
+ if not arg:
1732
+ print_version_and_exit()
1733
+ return
1734
+ if isinstance(arg, (ModuleHandle, types.ModuleType)):
1735
+ module = ModuleHandle(arg).module
1736
+ else:
1737
+ module = self.namespace.auto_eval(arg, mode="eval")
1738
+ if not isinstance(module, types.ModuleType):
1739
+ raise TypeError("print_version(): got a %s instead of a module"
1740
+ % (type(module).__name__,))
1741
+ try:
1742
+ version = module.__version__
1743
+ except AttributeError:
1744
+ raise AttributeError(
1745
+ "Module %s does not have a __version__ attribute"
1746
+ % (module.__name__,))
1747
+ print(version)
1748
+
1749
+ def print_help(self, objname, verbosity=1):
1750
+ objname = objname and objname.strip()
1751
+ if not objname:
1752
+ print(__doc__)
1753
+ return
1754
+ expr = UserExpr(objname, self.namespace, "eval")
1755
+ usage = _get_help(expr, verbosity)
1756
+ print(usage)
1757
+
1758
+ def create_ipython_app(self):
1759
+ """
1760
+ Create an IPython application and initialize it, but don't start it.
1761
+ """
1762
+ assert self.ipython_app is None
1763
+ self.ipython_app = get_ipython_terminal_app_with_autoimporter()
1764
+
1765
+ def start_ipython(self, args=[]):
1766
+ user_ns = self.namespace.globals
1767
+ start_ipython_with_autoimporter(args, _user_ns=user_ns,
1768
+ app=self.ipython_app)
1769
+ # Don't need to do another interactive session after this one
1770
+ # (i.e. make 'py --interactive' the same as 'py').
1771
+ self.interactive = False
1772
+
1773
+ def _parse_global_opts(self):
1774
+ args = list(self.main_args)
1775
+ self.add_deprecated_builtins = False
1776
+ self.debug = False
1777
+ self.interactive = False
1778
+ self.verbosity = 1
1779
+ self.arg_mode = None
1780
+ self.output_mode = None
1781
+ postmortem = 'auto'
1782
+ while args:
1783
+ arg = args[0]
1784
+ if arg in ["debug", "pdb", "ipdb", "dbg"]:
1785
+ argname = "debug"
1786
+ elif not arg.startswith("-"):
1787
+ break
1788
+ elif arg.startswith("--"):
1789
+ argname = arg[2:]
1790
+ else:
1791
+ argname = arg[1:]
1792
+ argname, equalsign, value = argname.partition("=")
1793
+ def popvalue():
1794
+ if equalsign:
1795
+ return value
1796
+ else:
1797
+ try:
1798
+ return args.pop(0)
1799
+ except IndexError:
1800
+ raise ValueError("expected argument to %s" % arg)
1801
+ def novalue():
1802
+ if equalsign:
1803
+ raise ValueError("unexpected argument %s" % arg)
1804
+ if argname in ["interactive", "i"]:
1805
+ novalue()
1806
+ self.interactive = True
1807
+ del args[0]
1808
+ # Create and initialize the IPython app now (but don't start
1809
+ # it yet). We'll use it later. The reason to initialize it
1810
+ # now is that the code that we're running might check if it's
1811
+ # running in interactive mode based on whether an IPython app
1812
+ # has been initialized. Some user code even initializes
1813
+ # things differently at module import time based on this.
1814
+ self.create_ipython_app()
1815
+ elif argname in ["debug", "pdb", "ipdb", "dbg", "d"]:
1816
+ novalue()
1817
+ self.debug = True
1818
+ del args[0]
1819
+ if argname == "verbose":
1820
+ novalue()
1821
+ logger.set_level("DEBUG")
1822
+ del args[0]
1823
+ continue
1824
+ if argname in ["quiet", "q"]:
1825
+ novalue()
1826
+ logger.set_level("ERROR")
1827
+ del args[0]
1828
+ continue
1829
+ if argname in ["safe"]:
1830
+ del args[0]
1831
+ novalue()
1832
+ self.arg_mode = _interpret_arg_mode("string")
1833
+ # TODO: make this less hacky, something like
1834
+ # self.import_db = ""
1835
+ # TODO: also turn off which() behavior
1836
+ os.environ["PYFLYBY_PATH"] = "EMPTY"
1837
+ continue
1838
+ if argname in ["arguments", "argument", "args", "arg",
1839
+ "arg_mode", "arg-mode", "argmode"]:
1840
+ # Interpret --args=eval|string|auto.
1841
+ # Note that if the user didn't specify --args, then we
1842
+ # intentionally leave ``opts.arg_mode`` set to ``None`` for now,
1843
+ # because the default varies per action.
1844
+ del args[0]
1845
+ self.arg_mode = _interpret_arg_mode(popvalue())
1846
+ continue
1847
+ if argname in ["output", "output_mode", "output-mode",
1848
+ "out", "outmode", "out_mode", "out-mode", "o"]:
1849
+ del args[0]
1850
+ self.output_mode = _interpret_output_mode(popvalue())
1851
+ continue
1852
+ if argname in ["print", "pprint", "silent", "repr"]:
1853
+ del args[0]
1854
+ novalue()
1855
+ self.output_mode = _interpret_output_mode(argname)
1856
+ continue
1857
+ if argname in ["postmortem"]:
1858
+ del args[0]
1859
+ v = (value or "").lower().strip()
1860
+ if v in ["yes", "y", "always", "true", "t", "1", "enable", ""]:
1861
+ postmortem = True
1862
+ elif v in ["no", "n", "never", "false", "f", "0", "disable"]:
1863
+ postmortem = False
1864
+ elif v in ["auto", "automatic", "default", "if-tty"]:
1865
+ postmortem = "auto"
1866
+ else:
1867
+ raise ValueError(
1868
+ "unexpected %s=%s. "
1869
+ "Try --postmortem=yes or --postmortem=no."
1870
+ % (argname, value))
1871
+ continue
1872
+ if argname in ["no-postmortem", "np"]:
1873
+ del args[0]
1874
+ novalue()
1875
+ postmortem = False
1876
+ continue
1877
+ if argname in ["add-deprecated-builtins", "add_deprecated_builtins"]:
1878
+ del args[0]
1879
+ self.add_deprecated_builtins = True
1880
+ continue
1881
+ break
1882
+ self.args = args
1883
+ if postmortem == "auto":
1884
+ postmortem = os.isatty(1)
1885
+ global _enable_postmortem_debugger
1886
+ _enable_postmortem_debugger = postmortem
1887
+
1888
+ def _enable_debug_tools(self, *, add_deprecated: bool):
1889
+ # Enable a bunch of debugging tools.
1890
+ enable_faulthandler()
1891
+ enable_signal_handler_debugger()
1892
+ enable_sigterm_handler()
1893
+ add_debug_functions_to_builtins(add_deprecated=add_deprecated)
1894
+
1895
+ def run(self):
1896
+ # Parse global options.
1897
+ sys.orig_argv = list(sys.argv)
1898
+ self._parse_global_opts()
1899
+ self._enable_debug_tools(add_deprecated=self.add_deprecated_builtins)
1900
+ self._run_action()
1901
+ self._pre_exit()
1902
+
1903
+ def _run_action(self):
1904
+ args = self.args
1905
+ if not args or args[0] == "-":
1906
+ if os.isatty(0):
1907
+ # The user specified no arguments (or only a "-") and stdin is a
1908
+ # tty. Run ipython with autoimporter enabled, i.e. equivalent to
1909
+ # autoipython. Note that we directly start IPython in the same
1910
+ # process, instead of using subprocess.call(['autoipython']),
1911
+ # because the latter messes with Control-C handling.
1912
+ # TODO: add 'py shell' and make this an alias.
1913
+ # TODO: if IPython isn't installed, then do our own
1914
+ # interactive REPL with code.InteractiveConsole, readline, and
1915
+ # autoimporter.
1916
+ self.start_ipython()
1917
+ return
1918
+ else:
1919
+ # Emulate python args.
1920
+ cmd_args = args or [""]
1921
+ # Execute code from stdin, with auto importing.
1922
+ self.exec_stdin(cmd_args)
1923
+ return
1924
+
1925
+ # Consider --action=arg, --action arg, -action=arg, -action arg,
1926
+ # %action arg.
1927
+ arg0 = args.pop(0)
1928
+ if not arg0.strip():
1929
+ raise ValueError("got empty string as first argument")
1930
+ if arg0.startswith("--"):
1931
+ action, equalsign, cmdarg = arg0[2:].partition("=")
1932
+ elif arg0.startswith("-"):
1933
+ action, equalsign, cmdarg = arg0[1:].partition("=")
1934
+ elif arg0.startswith("%"):
1935
+ action, equalsign, cmdarg = arg0[1:], None, None
1936
+ elif len(arg0) > 1 or arg0 == "?":
1937
+ action, equalsign, cmdarg = arg0, None, None
1938
+ else:
1939
+ action, equalsign, cmdarg = None, None, None
1940
+ def popcmdarg():
1941
+ if equalsign:
1942
+ return cmdarg
1943
+ else:
1944
+ try:
1945
+ return args.pop(0)
1946
+ except IndexError:
1947
+ raise ValueError("expected argument to %s" % arg0)
1948
+ def nocmdarg():
1949
+ if equalsign:
1950
+ raise ValueError("unexpected argument %s" % arg0)
1951
+
1952
+ if action in ["eval", "c", "e"]:
1953
+ # Evaluate a command from the command-line, with auto importing.
1954
+ # Supports expressions as well as statements.
1955
+ # Note: Regular python supports "python -cfoo" as equivalent to
1956
+ # "python -c foo". For now, we intentionally don't support that.
1957
+ cmd = popcmdarg()
1958
+ self.eval(cmd, args)
1959
+ elif action in ["file", "execfile", "execf", "runfile", "run", "f",
1960
+ "python"]:
1961
+ # Execute a file named on the command-line, with auto importing.
1962
+ cmd = popcmdarg()
1963
+ self.execfile(cmd, args)
1964
+ elif action in ["apply", "call"]:
1965
+ # Call a function named on the command-line, with auto importing and
1966
+ # auto evaluation of arguments.
1967
+ function_name = popcmdarg()
1968
+ function = UserExpr(function_name, self.namespace, "eval")
1969
+ self.apply(function, args)
1970
+ elif action in ["map"]:
1971
+ # Call function on each argument.
1972
+ # TODO: instead of making this a standalone mode, change this to a
1973
+ # flag that can eval/apply/exec/etc. Set "_" to each argument.
1974
+ # E.g. py --map 'print _' obj1 obj2
1975
+ # py --map _.foo obj1 obj2
1976
+ # py --map '_**2' 3 4 5
1977
+ # when using heuristic mode, "lock in" the action mode on the
1978
+ # first argument.
1979
+ function_name = popcmdarg()
1980
+ function = UserExpr(function_name, self.namespace, "eval")
1981
+ if args and args[0] == '--':
1982
+ for arg in args[1:]:
1983
+ self.apply(function, ['--', arg])
1984
+ else:
1985
+ for arg in args:
1986
+ self.apply(function, [arg])
1987
+ elif action in ["xargs"]:
1988
+ # TODO: read lines from stdin and map. default arg_mode=string
1989
+ raise NotImplementedError("TODO: xargs")
1990
+ elif action in ["module", "m", "runmodule", "run_module", "run-module"]:
1991
+ # Exactly like `python -m'. Intentionally does NOT do auto
1992
+ # importing within the module, because modules should not be
1993
+ # sloppy; they should instead be tidied to have the correct
1994
+ # imports.
1995
+ modname = popcmdarg()
1996
+ self.run_module(modname, args)
1997
+ elif arg0.startswith("-m"):
1998
+ # Support "py -mbase64" in addition to "py -m base64".
1999
+ modname = arg0[2:]
2000
+ self.run_module(modname, args)
2001
+ elif action in ["attach"]:
2002
+ pid = int(popcmdarg())
2003
+ nocmdarg()
2004
+ attach_debugger(pid)
2005
+ elif action in ["stack", "stack_trace", "stacktrace", "backtrace", "bt"]:
2006
+ pid = int(popcmdarg())
2007
+ nocmdarg()
2008
+ print("Stack trace for process %s:" % (pid,))
2009
+ remote_print_stack(pid)
2010
+ elif action in ["ipython", "ip"]:
2011
+ # Start IPython.
2012
+ self.start_ipython(args)
2013
+ elif action in ["notebook", "nb"]:
2014
+ # Start IPython notebook.
2015
+ nocmdarg()
2016
+ self.start_ipython(["notebook"] + args)
2017
+ elif action in ["kernel"]:
2018
+ # Start IPython kernel.
2019
+ nocmdarg()
2020
+ self.start_ipython(["kernel"] + args)
2021
+ elif action in ["qtconsole", "qt"]:
2022
+ # Start IPython qtconsole.
2023
+ nocmdarg()
2024
+ self.start_ipython(["qtconsole"] + args)
2025
+ elif action in ["console"]:
2026
+ # Start IPython console (with new kernel).
2027
+ nocmdarg()
2028
+ self.start_ipython(["console"] + args)
2029
+ elif action in ["existing"]:
2030
+ # Start IPython console (with existing kernel).
2031
+ if equalsign:
2032
+ args.insert(0, cmdarg)
2033
+ self.start_ipython(["console", "--existing"] + args)
2034
+ elif action in ["nbconvert"]:
2035
+ # Start IPython nbconvert. (autoimporter is irrelevant.)
2036
+ if equalsign:
2037
+ args.insert(0, cmdarg)
2038
+ start_ipython_with_autoimporter(["nbconvert"] + args)
2039
+ elif action in ["timeit"]:
2040
+ # TODO: make --timeit and --time flags which work with any mode
2041
+ # and heuristic, instead of only eval.
2042
+ # TODO: fallback if IPython isn't available. above todo probably
2043
+ # requires not using IPython anyway.
2044
+ nocmdarg()
2045
+ run_ipython_line_magic("%timeit " + ' '.join(args))
2046
+ elif action in ["time"]:
2047
+ # TODO: make --timeit and --time flags which work with any mode
2048
+ # and heuristic, instead of only eval.
2049
+ # TODO: fallback if IPython isn't available. above todo probably
2050
+ # requires not using IPython anyway.
2051
+ nocmdarg()
2052
+ run_ipython_line_magic("%time " + ' '.join(args))
2053
+ elif action in ["version"]:
2054
+ if equalsign:
2055
+ args.insert(0, cmdarg)
2056
+ self.print_version(args[0] if args else None)
2057
+ elif action in ["help", "h", "?"]:
2058
+ if equalsign:
2059
+ args.insert(0, cmdarg)
2060
+ self.print_help(args[0] if args else None, verbosity=1)
2061
+ elif action in ["pinfo"]:
2062
+ self.print_help(popcmdarg(), verbosity=1)
2063
+ elif action in ["source", "pinfo2", "??"]:
2064
+ self.print_help(popcmdarg(), verbosity=2)
2065
+
2066
+ elif arg0.startswith("-"):
2067
+ # Unknown argument.
2068
+ msg = "Unknown option %s" % (arg0,)
2069
+ if arg0.startswith("-c"):
2070
+ msg += "; do you mean -c %s?" % (arg0[2:])
2071
+ syntax(msg, usage=usage)
2072
+
2073
+ elif arg0.startswith("??"):
2074
+ # TODO: check number of args
2075
+ self.print_help(arg0[2:], verbosity=2)
2076
+
2077
+ elif arg0.endswith("??"):
2078
+ # TODO: check number of args
2079
+ self.print_help(arg0[:-2], verbosity=2)
2080
+
2081
+ elif arg0.startswith("?"):
2082
+ # TODO: check number of args
2083
+ self.print_help(arg0[1:], verbosity=1)
2084
+
2085
+ elif arg0.endswith("?"):
2086
+ # TODO: check number of args
2087
+ self.print_help(arg0[:-1], verbosity=1)
2088
+
2089
+ elif arg0.startswith("%"):
2090
+ run_ipython_line_magic(' '.join([arg0]+args))
2091
+
2092
+ # Heuristically choose the behavior automatically based on what the
2093
+ # argument looks like.
2094
+ else:
2095
+ filename = _as_filename_if_seems_like_filename(arg0)
2096
+ if filename:
2097
+ # Implied --execfile.
2098
+ self.execfile(filename, args)
2099
+ return
2100
+ if not args and arg0.isdigit():
2101
+ if self.debug:
2102
+ attach_debugger(int(arg0, 10))
2103
+ return
2104
+ else:
2105
+ logger.error(
2106
+ "Use py -d %s if you want to attach a debugger", arg0)
2107
+ raise SystemExit(1)
2108
+ # Implied --eval.
2109
+ # When given multiple arguments, first see if the args can be
2110
+ # concatenated and parsed as a single python program/expression.
2111
+ # But don't try this if any arguments look like options, empty
2112
+ # string or whitespace, etc.
2113
+ # TODO: refactor
2114
+ if (args and
2115
+ self.arg_mode == None and
2116
+ not any(re.match(r"\s*$|-[a-zA-Z-]", a) for a in args)):
2117
+ cmd = PythonBlock(" ".join([arg0]+args),
2118
+ flags=FLAGS, auto_flags=True)
2119
+ if cmd.parsable and self.namespace.auto_import(cmd):
2120
+ with SysArgvCtx("-c"):
2121
+ output_mode = _interpret_output_mode(self.output_mode)
2122
+ result = self.namespace.auto_eval(
2123
+ cmd, info=True, auto_import=False)
2124
+ print_result(result, output_mode)
2125
+ self.result = result
2126
+ return
2127
+ # else fall through
2128
+ # Heuristic based on first arg: try run_module, apply, or exec/eval.
2129
+ cmd = PythonBlock(arg0, flags=FLAGS, auto_flags=True)
2130
+ if not cmd.parsable:
2131
+ logger.error(
2132
+ "Could not interpret as filename or expression: %s",
2133
+ arg0)
2134
+ syntax(usage=usage)
2135
+ self.heuristic_cmd(cmd, args, function_name=arg0)
2136
+
2137
+ def _pre_exit(self):
2138
+ self._pre_exit_matplotlib_show()
2139
+ self._pre_exit_interactive_shell()
2140
+
2141
+ def _pre_exit_matplotlib_show(self):
2142
+ """
2143
+ If matplotlib.pyplot (pylab) is loaded, then call the show() function.
2144
+ This will cause the program to block until all figures are closed.
2145
+ Without this, a command like 'py plot(...)' would exit immediately.
2146
+ """
2147
+ if self.interactive:
2148
+ return
2149
+ try:
2150
+ pyplot = sys.modules["matplotlib.pyplot"]
2151
+ except KeyError:
2152
+ return
2153
+ pyplot.show()
2154
+
2155
+ def _pre_exit_interactive_shell(self):
2156
+ if self.interactive:
2157
+ assert self.ipython_app is not None
2158
+ self.namespace.globals["_"] = self.result
2159
+ self.start_ipython()
2160
+
2161
+
2162
+ def py_main(args=None):
2163
+ if args is None:
2164
+ args = sys.argv[1:]
2165
+ _PyMain(args).run()