Repository URL to install this package:
|
Version:
2022.2.8 ▾
|
pytype
/
imports_map_loader.py
|
|---|
"""Import and set up the imports_map."""
import collections
import logging
import os
log = logging.getLogger(__name__)
def _read_imports_map(options_info_path, open_function):
"""Read the imports_map file, fold duplicate entries into a multimap."""
if options_info_path is None:
return None
imports_multimap = collections.defaultdict(set)
with open_function(options_info_path) as fi:
for line in fi:
line = line.strip()
if line:
short_path, path = line.split(" ", 1)
short_path, _ = os.path.splitext(short_path) # drop extension
imports_multimap[short_path].add(path)
# Sort the multimap. Move items with '#' in the base name, generated for
# analysis results via --api, first, so we prefer them over others.
return {short_path: sorted(paths, key=os.path.basename)
for short_path, paths in imports_multimap.items()}
def _validate_imports_map(imports_map):
"""Validate the imports map against the command line arguments.
Args:
imports_map: The map returned by _read_imports_map.
Returns:
A list of invalid entries, in the form (short_path, long_path)
"""
errors = []
for short_path, paths in imports_map.items():
for path in paths:
if not os.path.exists(path):
errors.append((short_path, path))
if errors:
log.error("Invalid imports_map entries (checking from root dir: %s)",
os.path.abspath("."))
for short_path, path in errors:
log.error(" file does not exist: %r (mapped from %r)", path, short_path)
return errors
def build_imports_map(options_info_path, open_function=open):
"""Create a file mapping from a .imports_info file.
Builds a dict of short_path to full name
(e.g. "path/to/file.py" =>
"$GENDIR/rulename~~pytype-gen/path_to_file.py~~pytype"
Args:
options_info_path: The file with the info (may be None, for do-nothing)
open_function: A custom file opening function.
Returns:
Dict of .py short_path to list of .pytd path or None if no options_info_path
Raises:
ValueError if the imports map is invalid
"""
imports_multimap = _read_imports_map(options_info_path, open_function)
# Output warnings for all multiple
# mappings and keep the lexicographically first.
for short_path, paths in imports_multimap.items():
if len(paths) > 1:
log.warning("Multiple files for %r => %r ignoring %r",
short_path, paths[0], paths[1:])
imports_map = {short_path: os.path.abspath(paths[0])
for short_path, paths in imports_multimap.items()}
errors = _validate_imports_map(imports_multimap)
if errors:
msg = "Invalid imports_map: %s\nBad entries:\n" % options_info_path
msg += "\n".join(" %s -> %s" % (k, v) for k, v in errors)
raise ValueError(msg)
# Add the potential directory nodes for adding "__init__", because some build
# systems automatically create __init__.py in empty directories. These are
# added with the path name appended with "/" (os.sep), mapping to the empty
# file. See also load_pytd._import_file which also checks for an empty
# directory and acts as if an empty __init__.py is there.
dir_paths = {}
for short_path, path in sorted(imports_map.items()):
dir_paths[short_path] = path
short_path_pieces = short_path.split(os.sep)
# If we have a mapping file foo/bar/quux.py', then the pieces are ["foo",
# "bar", "quux"] and we want to add foo/__init__.py and foo/bar/__init__.py
for i in range(1, len(short_path_pieces)):
intermediate_dir_init = os.path.join(*(
short_path_pieces[:i] + ["__init__"]))
if (intermediate_dir_init not in imports_map and
intermediate_dir_init not in dir_paths):
log.warning("Created empty __init__ %r", intermediate_dir_init)
dir_paths[intermediate_dir_init] = os.devnull
return dir_paths