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