mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-07 04:34:29 +08:00
95 lines
3.8 KiB
Python
95 lines
3.8 KiB
Python
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
|