Blame SOURCES/import_all_modules.py

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