Close #18626: add a basic CLI for the inspect module
This commit is contained in:
parent
4c7fe6a5ad
commit
f94a16b494
|
@ -1006,3 +1006,20 @@ updated as expected:
|
|||
return an empty dictionary.
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
|
||||
Command Line Interface
|
||||
----------------------
|
||||
|
||||
The :mod:`inspect` module also provides a basic introspection capability
|
||||
from the command line.
|
||||
|
||||
.. program:: inspect
|
||||
|
||||
By default, accepts the name of a module and prints the source of that
|
||||
module. A class or function within the module can be printed instead by
|
||||
appended a colon and the qualified name of the target object.
|
||||
|
||||
.. cmdoption:: --details
|
||||
|
||||
Print information about the specified object rather than the source code
|
||||
|
|
|
@ -264,11 +264,15 @@ New :func:`functools.singledispatch` decorator: see the :pep:`443`.
|
|||
inspect
|
||||
-------
|
||||
|
||||
|
||||
The inspect module now offers a basic command line interface to quickly
|
||||
display source code and other information for modules, classes and
|
||||
functions.
|
||||
|
||||
:func:`~inspect.unwrap` makes it easy to unravel wrapper function chains
|
||||
created by :func:`functools.wraps` (and any other API that sets the
|
||||
``__wrapped__`` attribute on a wrapper function).
|
||||
|
||||
|
||||
mmap
|
||||
----
|
||||
|
||||
|
|
|
@ -2109,3 +2109,64 @@ class Signature:
|
|||
rendered += ' -> {}'.format(anno)
|
||||
|
||||
return rendered
|
||||
|
||||
def _main():
|
||||
""" Logic for inspecting an object given at command line """
|
||||
import argparse
|
||||
import importlib
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'object',
|
||||
help="The object to be analysed. "
|
||||
"It supports the 'module:qualname' syntax")
|
||||
parser.add_argument(
|
||||
'-d', '--details', action='store_true',
|
||||
help='Display info about the module rather than its source code')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
target = args.object
|
||||
mod_name, has_attrs, attrs = target.partition(":")
|
||||
try:
|
||||
obj = module = importlib.import_module(mod_name)
|
||||
except Exception as exc:
|
||||
msg = "Failed to import {} ({}: {})".format(mod_name,
|
||||
type(exc).__name__,
|
||||
exc)
|
||||
print(msg, file=sys.stderr)
|
||||
exit(2)
|
||||
|
||||
if has_attrs:
|
||||
parts = attrs.split(".")
|
||||
obj = module
|
||||
for part in parts:
|
||||
obj = getattr(obj, part)
|
||||
|
||||
if module.__name__ in sys.builtin_module_names:
|
||||
print("Can't get info for builtin modules.", file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
if args.details:
|
||||
print('Target: {}'.format(target))
|
||||
print('Origin: {}'.format(getsourcefile(module)))
|
||||
print('Cached: {}'.format(module.__cached__))
|
||||
if obj is module:
|
||||
print('Loader: {}'.format(repr(module.__loader__)))
|
||||
if hasattr(module, '__path__'):
|
||||
print('Submodule search path: {}'.format(module.__path__))
|
||||
else:
|
||||
try:
|
||||
__, lineno = findsource(obj)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
print('Line: {}'.format(lineno))
|
||||
|
||||
print('\n')
|
||||
else:
|
||||
print(getsource(obj))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_main()
|
||||
|
|
|
@ -9,10 +9,11 @@ import collections
|
|||
import os
|
||||
import shutil
|
||||
import functools
|
||||
import importlib
|
||||
from os.path import normcase
|
||||
|
||||
from test.support import run_unittest, TESTFN, DirsOnSysPath
|
||||
|
||||
from test.script_helper import assert_python_ok, assert_python_failure
|
||||
from test import inspect_fodder as mod
|
||||
from test import inspect_fodder2 as mod2
|
||||
|
||||
|
@ -2372,6 +2373,47 @@ class TestUnwrap(unittest.TestCase):
|
|||
__wrapped__ = func
|
||||
self.assertIsNone(inspect.unwrap(C()))
|
||||
|
||||
class TestMain(unittest.TestCase):
|
||||
def test_only_source(self):
|
||||
module = importlib.import_module('unittest')
|
||||
rc, out, err = assert_python_ok('-m', 'inspect',
|
||||
'unittest')
|
||||
lines = out.decode().splitlines()
|
||||
# ignore the final newline
|
||||
self.assertEqual(lines[:-1], inspect.getsource(module).splitlines())
|
||||
self.assertEqual(err, b'')
|
||||
|
||||
def test_qualname_source(self):
|
||||
module = importlib.import_module('concurrent.futures')
|
||||
member = getattr(module, 'ThreadPoolExecutor')
|
||||
rc, out, err = assert_python_ok('-m', 'inspect',
|
||||
'concurrent.futures:ThreadPoolExecutor')
|
||||
lines = out.decode().splitlines()
|
||||
# ignore the final newline
|
||||
self.assertEqual(lines[:-1],
|
||||
inspect.getsource(member).splitlines())
|
||||
self.assertEqual(err, b'')
|
||||
|
||||
def test_builtins(self):
|
||||
module = importlib.import_module('unittest')
|
||||
_, out, err = assert_python_failure('-m', 'inspect',
|
||||
'sys')
|
||||
lines = err.decode().splitlines()
|
||||
self.assertEqual(lines, ["Can't get info for builtin modules."])
|
||||
|
||||
def test_details(self):
|
||||
module = importlib.import_module('unittest')
|
||||
rc, out, err = assert_python_ok('-m', 'inspect',
|
||||
'unittest', '--details')
|
||||
output = out.decode()
|
||||
# Just a quick sanity check on the output
|
||||
self.assertIn(module.__name__, output)
|
||||
self.assertIn(module.__file__, output)
|
||||
self.assertIn(module.__cached__, output)
|
||||
self.assertEqual(err, b'')
|
||||
|
||||
|
||||
|
||||
|
||||
def test_main():
|
||||
run_unittest(
|
||||
|
@ -2380,7 +2422,7 @@ def test_main():
|
|||
TestGetcallargsFunctions, TestGetcallargsMethods,
|
||||
TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState,
|
||||
TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject,
|
||||
TestBoundArguments, TestGetClosureVars, TestUnwrap
|
||||
TestBoundArguments, TestGetClosureVars, TestUnwrap, TestMain
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
Loading…
Reference in New Issue