from typing import Iterable, List, Optional, cast from mypy.nodes import ClassDef, Context, ImportAll, MypyFile, SymbolNode, SymbolTableNode, TypeInfo, Var from mypy.plugin import ClassDefContext from mypy.semanal import SemanticAnalyzerPass2 from mypy.types import AnyType, Instance, NoneTyp, Type, TypeOfAny, UnionType from mypy.util import correct_relative_import def get_error_context(node: SymbolNode) -> Context: context = Context() context.set_line(node) return context def filter_out_nones(typ: UnionType) -> List[Type]: return [item for item in typ.items if not isinstance(item, NoneTyp)] def make_sym_copy_of_setting(sym: SymbolTableNode) -> Optional[SymbolTableNode]: if isinstance(sym.type, Instance): copied = sym.copy() copied.node.info = sym.type.type return copied elif isinstance(sym.type, UnionType): instances = filter_out_nones(sym.type) if len(instances) > 1: # plain unions not supported yet return None typ = instances[0] if isinstance(typ, Instance): copied = sym.copy() copied.node.info = typ.type return copied return None else: return None def get_settings_metadata(lazy_settings_info: TypeInfo): return lazy_settings_info.metadata.setdefault('django', {}).setdefault('settings', {}) def load_settings_from_names(settings_classdef: ClassDef, modules: Iterable[MypyFile], api: SemanticAnalyzerPass2) -> None: settings_metadata = get_settings_metadata(settings_classdef.info) for module in modules: for name, sym in module.names.items(): if name.isupper() and isinstance(sym.node, Var): if sym.type is not None: copied = make_sym_copy_of_setting(sym) if copied is None: continue settings_classdef.info.names[name] = copied else: var = Var(name, AnyType(TypeOfAny.unannotated)) var.info = api.named_type('__builtins__.object').type settings_classdef.info.names[name] = SymbolTableNode(sym.kind, var) settings_metadata[name] = module.fullname() def get_import_star_modules(api: SemanticAnalyzerPass2, module: MypyFile) -> List[str]: import_star_modules = [] for module_import in module.imports: # relative import * are not resolved by mypy if isinstance(module_import, ImportAll) and module_import.relative: absolute_import_path, correct = correct_relative_import(module.fullname(), module_import.relative, module_import.id, is_cur_package_init_file=False) if not correct: return [] for path in [absolute_import_path] + get_import_star_modules(api, module=api.modules.get(absolute_import_path)): if path not in import_star_modules: import_star_modules.append(path) return import_star_modules class AddSettingValuesToDjangoConfObject: def __init__(self, settings_modules: List[str], ignore_missing_settings: bool): self.settings_modules = settings_modules self.ignore_missing_settings = ignore_missing_settings def __call__(self, ctx: ClassDefContext) -> None: api = cast(SemanticAnalyzerPass2, ctx.api) for module_name in self.settings_modules: module = api.modules[module_name] star_deps = [api.modules[star_dep] for star_dep in reversed(get_import_star_modules(api, module))] load_settings_from_names(ctx.cls, modules=star_deps + [module], api=api) if self.ignore_missing_settings: ctx.cls.info.fallback_to_any = True