|
|
6f4bc5 |
'''Script to perform import of each module given to %%py_check_import
|
|
|
6f4bc5 |
'''
|
|
|
6f4bc5 |
import argparse
|
|
|
6f4bc5 |
import importlib
|
|
|
6f4bc5 |
import fnmatch
|
|
|
6f4bc5 |
import os
|
|
|
6f4bc5 |
import re
|
|
|
6f4bc5 |
import site
|
|
|
6f4bc5 |
import sys
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
from contextlib import contextmanager
|
|
|
6f4bc5 |
from pathlib import Path
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
def read_modules_files(file_paths):
|
|
|
6f4bc5 |
'''Read module names from the files (modules must be newline separated).
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
Return the module names list or, if no files were provided, an empty list.
|
|
|
6f4bc5 |
'''
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
if not file_paths:
|
|
|
6f4bc5 |
return []
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
modules = []
|
|
|
6f4bc5 |
for file in file_paths:
|
|
|
6f4bc5 |
file_contents = file.read_text()
|
|
|
6f4bc5 |
modules.extend(file_contents.split())
|
|
|
6f4bc5 |
return modules
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
def read_modules_from_cli(argv):
|
|
|
6f4bc5 |
'''Read module names from command-line arguments (space or comma separated).
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
Return the module names list.
|
|
|
6f4bc5 |
'''
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
if not argv:
|
|
|
6f4bc5 |
return []
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
# %%py3_check_import allows to separate module list with comma or whitespace,
|
|
|
6f4bc5 |
# we need to unify the output to a list of particular elements
|
|
|
6f4bc5 |
modules_as_str = ' '.join(argv)
|
|
|
6f4bc5 |
modules = re.split(r'[\s,]+', modules_as_str)
|
|
|
6f4bc5 |
# Because of shell expansion in some less typical cases it may happen
|
|
|
6f4bc5 |
# that a trailing space will occur at the end of the list.
|
|
|
6f4bc5 |
# Remove the empty items from the list before passing it further
|
|
|
6f4bc5 |
modules = [m for m in modules if m]
|
|
|
6f4bc5 |
return modules
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
def filter_top_level_modules_only(modules):
|
|
|
6f4bc5 |
'''Filter out entries with nested modules (containing dot) ie. 'foo.bar'.
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
Return the list of top-level modules.
|
|
|
6f4bc5 |
'''
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
return [module for module in modules if '.' not in module]
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
def any_match(text, globs):
|
|
|
6f4bc5 |
'''Return True if any of given globs fnmatchcase's the given text.'''
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
return any(fnmatch.fnmatchcase(text, g) for g in globs)
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
def exclude_unwanted_module_globs(globs, modules):
|
|
|
6f4bc5 |
'''Filter out entries which match the either of the globs given as argv.
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
Return the list of filtered modules.
|
|
|
6f4bc5 |
'''
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
return [m for m in modules if not any_match(m, globs)]
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
def read_modules_from_all_args(args):
|
|
|
6f4bc5 |
'''Return a joined list of modules from all given command-line arguments.
|
|
|
6f4bc5 |
'''
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
modules = read_modules_files(args.filename)
|
|
|
6f4bc5 |
modules.extend(read_modules_from_cli(args.modules))
|
|
|
6f4bc5 |
if args.exclude:
|
|
|
6f4bc5 |
modules = exclude_unwanted_module_globs(args.exclude, modules)
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
if args.top_level:
|
|
|
6f4bc5 |
modules = filter_top_level_modules_only(modules)
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
# Error when someone accidentally managed to filter out everything
|
|
|
6f4bc5 |
if len(modules) == 0:
|
|
|
6f4bc5 |
raise ValueError('No modules to check were left')
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
return modules
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
def import_modules(modules):
|
|
|
6f4bc5 |
'''Procedure to perform import check for each module name from the given list of modules.
|
|
|
6f4bc5 |
'''
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
for module in modules:
|
|
|
6f4bc5 |
print('Check import:', module, file=sys.stderr)
|
|
|
6f4bc5 |
importlib.import_module(module)
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
def argparser():
|
|
|
6f4bc5 |
parser = argparse.ArgumentParser(
|
|
|
6f4bc5 |
description='Generate list of all importable modules for import check.'
|
|
|
6f4bc5 |
)
|
|
|
6f4bc5 |
parser.add_argument(
|
|
|
6f4bc5 |
'modules', nargs='*',
|
|
|
6f4bc5 |
help=('Add modules to check the import (space or comma separated).'),
|
|
|
6f4bc5 |
)
|
|
|
6f4bc5 |
parser.add_argument(
|
|
|
6f4bc5 |
'-f', '--filename', action='append', type=Path,
|
|
|
6f4bc5 |
help='Add importable module names list from file.',
|
|
|
6f4bc5 |
)
|
|
|
6f4bc5 |
parser.add_argument(
|
|
|
6f4bc5 |
'-t', '--top-level', action='store_true',
|
|
|
6f4bc5 |
help='Check only top-level modules.',
|
|
|
6f4bc5 |
)
|
|
|
6f4bc5 |
parser.add_argument(
|
|
|
6f4bc5 |
'-e', '--exclude', action='append',
|
|
|
6f4bc5 |
help='Provide modules globs to be excluded from the check.',
|
|
|
6f4bc5 |
)
|
|
|
6f4bc5 |
return parser
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
@contextmanager
|
|
|
6f4bc5 |
def remove_unwanteds_from_sys_path():
|
|
|
6f4bc5 |
'''Remove cwd and this script's parent from sys.path for the import test.
|
|
|
6f4bc5 |
Bring the original contents back after import is done (or failed)
|
|
|
6f4bc5 |
'''
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
cwd_absolute = Path.cwd().absolute()
|
|
|
6f4bc5 |
this_file_parent = Path(__file__).parent.absolute()
|
|
|
6f4bc5 |
old_sys_path = list(sys.path)
|
|
|
6f4bc5 |
for path in old_sys_path:
|
|
|
6f4bc5 |
if Path(path).absolute() in (cwd_absolute, this_file_parent):
|
|
|
6f4bc5 |
sys.path.remove(path)
|
|
|
6f4bc5 |
try:
|
|
|
6f4bc5 |
yield
|
|
|
6f4bc5 |
finally:
|
|
|
6f4bc5 |
sys.path = old_sys_path
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
def addsitedirs_from_environ():
|
|
|
6f4bc5 |
'''Load directories from the _PYTHONSITE environment variable (separated by :)
|
|
|
6f4bc5 |
and load the ones already present in sys.path via site.addsitedir()
|
|
|
6f4bc5 |
to handle .pth files in them.
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
This is needed to properly import old-style namespace packages with nspkg.pth files.
|
|
|
6f4bc5 |
See https://bugzilla.redhat.com/2018551 for a more detailed rationale.'''
|
|
|
6f4bc5 |
for path in os.getenv('_PYTHONSITE', '').split(':'):
|
|
|
6f4bc5 |
if path in sys.path:
|
|
|
6f4bc5 |
site.addsitedir(path)
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
def main(argv=None):
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
cli_args = argparser().parse_args(argv)
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
if not cli_args.modules and not cli_args.filename:
|
|
|
6f4bc5 |
raise ValueError('No modules to check were provided')
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
modules = read_modules_from_all_args(cli_args)
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
with remove_unwanteds_from_sys_path():
|
|
|
6f4bc5 |
addsitedirs_from_environ()
|
|
|
6f4bc5 |
import_modules(modules)
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
|
|
|
6f4bc5 |
if __name__ == '__main__':
|
|
|
6f4bc5 |
main()
|