Source code for pyfileconf.io.func.load.extractimp

from typing import List, cast
import ast
import warnings

from pyfileconf.io.func.load.config import function_args_as_arg_and_annotation_dict
from pyfileconf.io.file.load.parsers.extname import extract_unique_external_names_from_assign_value
from pyfileconf.imports.models.statements.container import ImportStatementContainer, ObjectImportStatement
from pyfileconf.assignments.models.container import AssignmentStatementContainer
from pyfileconf.logic.inspect import _is_str_matching_builtin_type

[docs]def extract_import_statements_from_function_args_imports_and_assigns(args: ast.arguments, imports: ImportStatementContainer, assigns: AssignmentStatementContainer, current_module_section_path_str: str, ) -> ImportStatementContainer: defaults_dict, annotation_dict = function_args_as_arg_and_annotation_dict(args) # Extract external names. These are the root names (not attrs, the base of attrs) for anything that is not builtin external_names = _unique_external_names_from_default_dict_and_annotation_dict(defaults_dict, annotation_dict) out_imports = ImportStatementContainer([]) # External names may be either due to assignment or due to imports # Handle names due to assignment for name in external_names: if assigns.contains_varname(name): # External name is due to assignment in this module. Create import from this module out_imports.append(ObjectImportStatement.from_str(f'from {current_module_section_path_str} import {name}')) # Handle names which are imported into this file for name in external_names: import_or_none = imports.get_import_for_module_or_obj_name(name) if import_or_none is not None: out_imports.append(import_or_none) # Sanity check, did we find all the imports? n_external = len(external_names) n_imports = len(out_imports) if n_external != n_imports: warnings.warn(f'had {n_external} external names from function definition, only ' f'found {n_imports} imports. May be missing imports') return out_imports
def _unique_external_names_from_default_dict_and_annotation_dict(default_dict: dict, annotation_dict: dict) -> List[str]: names = _unique_external_names_from_assignment_dict(default_dict) names += _unique_external_names_from_annotation_dict(annotation_dict) return list(set(names)) def _unique_external_names_from_multiple_assignment_dicts(*assignment_dicts) -> List[str]: all_names: List[str] = [] for assignment_dict in assignment_dicts: all_names += _unique_external_names_from_assignment_dict(assignment_dict) return list(set(all_names)) def _unique_external_names_from_assignment_dict(assignment_dict: dict) -> List[str]: names = [] for key, value in assignment_dict.items(): new_names = extract_unique_external_names_from_assign_value(value) names.extend(new_names) no_none_names: List[str] = [name for name in names if name is not None] return list(set(no_none_names)) def _unique_external_names_from_annotation_dict(annotation_dict: dict) -> List[str]: type_str_names = _extract_unique_type_str_names_from_annotation_dict(annotation_dict) return [type_str for type_str in type_str_names if not _is_str_matching_builtin_type(type_str)] def _extract_unique_type_str_names_from_annotation_dict(annotation_dict: dict) -> List[str]: names = [] for value in annotation_dict.values(): names.extend(_extract_str_names_from_ambiguous_annotation(value)) return list(set(names)) def _extract_str_names_from_ambiguous_annotation(annotation) -> List[str]: # TODO [#26]: remove type ignores once typeshed has better ast support # # Hitting errors "expr" has no attribute "id" and "slice" has no attribute "value" names = [] if isinstance(annotation, ast.Name): names.append(annotation.id) # handles most types elif hasattr(annotation, 'elts'): # type: ignore # got multiple values, e.g. List[str, bool] # in this case, value.slice.value is a Tuple, and Tuple.elts contains the items names.extend(_extract_str_names_from_tuple(annotation)) # type: ignore elif isinstance(annotation, ast.Index): # e.g. [str] # use .value to extract the str part, but then as it can be anything, call recursively on this names.extend(_extract_str_names_from_ambiguous_annotation(annotation.value)) elif isinstance(annotation, ast.Subscript): # E.g. Tuple[int] names.extend(_extract_str_names_from_subscript(annotation)) elif isinstance(annotation, ast.Attribute): # E.g. pd.DataFrame names.append(annotation.value.id) # type: ignore elif isinstance(annotation, ast.Str): # E.g. 'DataPipeline' (quoted type declaration where type was imported if TYPE_CHECKING) names.append(annotation.s) else: raise NotImplementedError(f'no handling for {annotation} of type {type(annotation)}') return names def _extract_str_names_from_subscript(subscript: ast.Subscript) -> List[str]: names = [] # for example, List[str] outer_ast = subscript.value # List portion inner_ast = subscript.slice # [str] portion names.extend(_extract_str_names_from_ambiguous_annotation(outer_ast)) names.extend(_extract_str_names_from_ambiguous_annotation(inner_ast)) return names def _extract_str_names_from_tuple(tuple: ast.Tuple) -> List[str]: names = [] for item in tuple.elts: names.extend(_extract_str_names_from_ambiguous_annotation(item)) return names