mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-06 12:14:28 +08:00
rework settings, add loading of the django.conf.global_settings, cleanups
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
import os
|
||||
from typing import Callable, Optional, cast, Dict
|
||||
from typing import Callable, Dict, Optional, cast
|
||||
|
||||
from mypy.checker import TypeChecker
|
||||
from mypy.nodes import TypeInfo
|
||||
from mypy.options import Options
|
||||
from mypy.plugin import Plugin, FunctionContext, ClassDefContext, MethodContext
|
||||
from mypy.types import Type, Instance
|
||||
from mypy.plugin import ClassDefContext, FunctionContext, MethodContext, Plugin
|
||||
from mypy.types import Instance, Type
|
||||
|
||||
from mypy_django_plugin import helpers, monkeypatch
|
||||
from mypy_django_plugin.plugins.fields import determine_type_of_array_field
|
||||
from mypy_django_plugin.plugins.migrations import determine_model_cls_from_string_for_migrations
|
||||
from mypy_django_plugin.plugins.models import process_model_class
|
||||
from mypy_django_plugin.plugins.related_fields import extract_to_parameter_as_get_ret_type_for_related_field, reparametrize_with
|
||||
from mypy_django_plugin.plugins.settings import DjangoConfSettingsInitializerHook
|
||||
from mypy_django_plugin.plugins.settings import AddSettingValuesToDjangoConfObject
|
||||
|
||||
|
||||
def transform_model_class(ctx: ClassDefContext) -> None:
|
||||
@@ -55,19 +55,21 @@ def determine_proper_manager_type(ctx: FunctionContext) -> Type:
|
||||
|
||||
|
||||
class DjangoPlugin(Plugin):
|
||||
def __init__(self,
|
||||
options: Options) -> None:
|
||||
def __init__(self, options: Options) -> None:
|
||||
super().__init__(options)
|
||||
|
||||
monkeypatch.restore_original_load_graph()
|
||||
monkeypatch.restore_original_dependencies_handling()
|
||||
|
||||
settings_modules = ['django.conf.global_settings']
|
||||
self.django_settings = os.environ.get('DJANGO_SETTINGS_MODULE')
|
||||
if self.django_settings:
|
||||
monkeypatch.load_graph_to_add_settings_file_as_a_source_seed(self.django_settings)
|
||||
monkeypatch.inject_dependencies(self.django_settings)
|
||||
else:
|
||||
monkeypatch.restore_original_load_graph()
|
||||
monkeypatch.restore_original_dependencies_handling()
|
||||
settings_modules.append(self.django_settings)
|
||||
|
||||
def get_current_model_bases(self) -> Dict[str, int]:
|
||||
monkeypatch.add_modules_as_a_source_seed_files(settings_modules)
|
||||
monkeypatch.inject_modules_as_dependencies_for_django_conf_settings(settings_modules)
|
||||
|
||||
def _get_current_model_bases(self) -> Dict[str, int]:
|
||||
model_sym = self.lookup_fully_qualified(helpers.MODEL_CLASS_FULLNAME)
|
||||
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
||||
if 'django' not in model_sym.node.metadata:
|
||||
@@ -78,7 +80,7 @@ class DjangoPlugin(Plugin):
|
||||
else:
|
||||
return {}
|
||||
|
||||
def get_current_manager_bases(self) -> Dict[str, int]:
|
||||
def _get_current_manager_bases(self) -> Dict[str, int]:
|
||||
manager_sym = self.lookup_fully_qualified(helpers.MANAGER_CLASS_FULLNAME)
|
||||
if manager_sym is not None and isinstance(manager_sym.node, TypeInfo):
|
||||
if 'django' not in manager_sym.node.metadata:
|
||||
@@ -99,7 +101,7 @@ class DjangoPlugin(Plugin):
|
||||
if fullname == 'django.contrib.postgres.fields.array.ArrayField':
|
||||
return determine_type_of_array_field
|
||||
|
||||
manager_bases = self.get_current_manager_bases()
|
||||
manager_bases = self._get_current_manager_bases()
|
||||
if fullname in manager_bases:
|
||||
return determine_proper_manager_type
|
||||
|
||||
@@ -112,13 +114,16 @@ class DjangoPlugin(Plugin):
|
||||
|
||||
def get_base_class_hook(self, fullname: str
|
||||
) -> Optional[Callable[[ClassDefContext], None]]:
|
||||
if fullname in self.get_current_model_bases():
|
||||
if fullname in self._get_current_model_bases():
|
||||
return transform_model_class
|
||||
|
||||
if fullname == helpers.DUMMY_SETTINGS_BASE_CLASS:
|
||||
return DjangoConfSettingsInitializerHook(settings_module=self.django_settings)
|
||||
settings_modules = ['django.conf.global_settings']
|
||||
if self.django_settings:
|
||||
settings_modules.append(self.django_settings)
|
||||
return AddSettingValuesToDjangoConfObject(settings_modules)
|
||||
|
||||
if fullname in self.get_current_manager_bases():
|
||||
if fullname in self._get_current_manager_bases():
|
||||
return transform_manager_class
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from .dependencies import (load_graph_to_add_settings_file_as_a_source_seed,
|
||||
inject_dependencies,
|
||||
from .dependencies import (add_modules_as_a_source_seed_files,
|
||||
inject_modules_as_dependencies_for_django_conf_settings,
|
||||
restore_original_load_graph,
|
||||
restore_original_dependencies_handling,
|
||||
process_settings_before_dependants)
|
||||
restore_original_dependencies_handling)
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
from typing import List, Optional, AbstractSet, MutableSet, Set
|
||||
from typing import List, Optional
|
||||
|
||||
from mypy.build import BuildManager, Graph, State, PRI_ALL
|
||||
from mypy import build
|
||||
from mypy.build import BuildManager, Graph, State
|
||||
from mypy.modulefinder import BuildSource
|
||||
|
||||
old_load_graph = build.load_graph
|
||||
OldState = build.State
|
||||
|
||||
|
||||
def is_module_present_in_sources(module_name: str, sources: List[BuildSource]):
|
||||
return any([source.module == module_name for source in sources])
|
||||
|
||||
|
||||
from mypy import build
|
||||
|
||||
old_load_graph = build.load_graph
|
||||
OldState = build.State
|
||||
old_sorted_components = build.sorted_components
|
||||
|
||||
|
||||
def load_graph_to_add_settings_file_as_a_source_seed(settings_module: str):
|
||||
def add_modules_as_a_source_seed_files(modules: List[str]) -> None:
|
||||
def patched_load_graph(sources: List[BuildSource], manager: BuildManager,
|
||||
old_graph: Optional[Graph] = None,
|
||||
new_modules: Optional[List[State]] = None):
|
||||
if not is_module_present_in_sources(settings_module, sources):
|
||||
sources.append(BuildSource(None, settings_module, None))
|
||||
# add global settings
|
||||
for module_name in modules:
|
||||
if not is_module_present_in_sources(module_name, sources):
|
||||
sources.append(BuildSource(None, module_name, None))
|
||||
|
||||
return old_load_graph(sources=sources, manager=manager,
|
||||
old_graph=old_graph,
|
||||
@@ -35,14 +34,14 @@ def restore_original_load_graph():
|
||||
build.load_graph = old_load_graph
|
||||
|
||||
|
||||
def inject_dependencies(settings_module: str):
|
||||
def inject_modules_as_dependencies_for_django_conf_settings(modules: List[str]) -> None:
|
||||
from mypy import build
|
||||
|
||||
class PatchedState(build.State):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.id == 'django.conf':
|
||||
self.dependencies.append(settings_module)
|
||||
self.dependencies.extend(modules)
|
||||
|
||||
build.State = PatchedState
|
||||
|
||||
@@ -51,40 +50,3 @@ def restore_original_dependencies_handling():
|
||||
from mypy import build
|
||||
|
||||
build.State = OldState
|
||||
|
||||
|
||||
def _extract_dependencies(graph: Graph, state_id: str, visited_modules: Set[str]) -> Set[str]:
|
||||
visited_modules.add(state_id)
|
||||
dependencies = set(graph[state_id].dependencies)
|
||||
for new_dep_id in dependencies.copy():
|
||||
if new_dep_id not in visited_modules:
|
||||
dependencies.update(_extract_dependencies(graph, new_dep_id, visited_modules))
|
||||
return dependencies
|
||||
|
||||
|
||||
def extract_module_dependencies(graph: Graph, state_id: str) -> Set[str]:
|
||||
visited_modules = set()
|
||||
return _extract_dependencies(graph, state_id, visited_modules=visited_modules)
|
||||
|
||||
|
||||
def process_settings_before_dependants(settings_module: str):
|
||||
def patched_sorted_components(graph: Graph,
|
||||
vertices: Optional[AbstractSet[str]] = None,
|
||||
pri_max: int = PRI_ALL) -> List[AbstractSet[str]]:
|
||||
sccs = old_sorted_components(graph,
|
||||
vertices=vertices,
|
||||
pri_max=pri_max)
|
||||
for i, scc in enumerate(sccs.copy()):
|
||||
if 'django.conf' in scc:
|
||||
django_conf_deps = set(extract_module_dependencies(graph, 'django.conf')).union({'django.conf'})
|
||||
old_scc_modified = scc.difference(django_conf_deps)
|
||||
new_scc = scc.difference(old_scc_modified)
|
||||
if not old_scc_modified:
|
||||
# already processed
|
||||
break
|
||||
sccs[i] = frozenset(old_scc_modified)
|
||||
sccs.insert(i, frozenset(new_scc))
|
||||
break
|
||||
return sccs
|
||||
|
||||
build.sorted_components = patched_sorted_components
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import dataclasses
|
||||
from abc import abstractmethod, ABCMeta
|
||||
from typing import cast, Iterator, Tuple, Optional, Dict
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from typing import Dict, Iterator, Optional, Tuple, cast
|
||||
|
||||
from mypy.nodes import ClassDef, AssignmentStmt, CallExpr, MemberExpr, StrExpr, NameExpr, MDEF, TypeInfo, Var, SymbolTableNode, \
|
||||
Lvalue, Expression, MypyFile, Context
|
||||
import dataclasses
|
||||
from mypy.nodes import AssignmentStmt, CallExpr, ClassDef, Context, Expression, Lvalue, MDEF, MemberExpr, MypyFile, NameExpr, \
|
||||
StrExpr, SymbolTableNode, TypeInfo, Var
|
||||
from mypy.plugin import ClassDefContext
|
||||
from mypy.semanal import SemanticAnalyzerPass2
|
||||
from mypy.types import Instance
|
||||
@@ -45,6 +45,7 @@ class ModelClassInitializer(metaclass=ABCMeta):
|
||||
var._fullname = self.model_classdef.info.fullname() + '.' + name
|
||||
var.is_inferred = True
|
||||
var.is_initialized_in_class = True
|
||||
var.is_classvar = True
|
||||
self.model_classdef.info.names[name] = SymbolTableNode(MDEF, var)
|
||||
|
||||
@abstractmethod
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from typing import cast, List, Optional
|
||||
from typing import List, Optional, cast
|
||||
|
||||
from mypy.nodes import Var, Context, SymbolNode, SymbolTableNode
|
||||
from mypy.nodes import ClassDef, Context, MypyFile, SymbolNode, SymbolTableNode, Var
|
||||
from mypy.plugin import ClassDefContext
|
||||
from mypy.semanal import SemanticAnalyzerPass2
|
||||
from mypy.types import Instance, UnionType, NoneTyp, Type
|
||||
from mypy.types import Instance, NoneTyp, Type, UnionType
|
||||
|
||||
|
||||
def get_error_context(node: SymbolNode) -> Context:
|
||||
@@ -36,39 +36,22 @@ def make_sym_copy_of_setting(sym: SymbolTableNode) -> Optional[SymbolTableNode]:
|
||||
return None
|
||||
|
||||
|
||||
def add_settings_to_django_conf_object(ctx: ClassDefContext,
|
||||
settings_module: str) -> None:
|
||||
api = cast(SemanticAnalyzerPass2, ctx.api)
|
||||
if settings_module not in api.modules:
|
||||
return None
|
||||
|
||||
settings_file = api.modules[settings_module]
|
||||
for name, sym in settings_file.names.items():
|
||||
def load_settings_from_module(settings_classdef: ClassDef, module: MypyFile) -> None:
|
||||
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
|
||||
ctx.cls.info.names[name] = copied
|
||||
# else:
|
||||
# TODO: figure out suggestion to add type annotation
|
||||
# context = Context()
|
||||
# module, node_name = sym.node.fullname().rsplit('.', 1)
|
||||
# module_file = api.modules.get(module)
|
||||
# if module_file is None:
|
||||
# return None
|
||||
# context.set_line(sym.node)
|
||||
# api.msg.report(f"Need type annotation for '{sym.node.name()}'", context,
|
||||
# severity='error', file=module_file.path)
|
||||
ctx.cls.info.fallback_to_any = True
|
||||
settings_classdef.info.names[name] = copied
|
||||
|
||||
|
||||
class DjangoConfSettingsInitializerHook(object):
|
||||
def __init__(self, settings_module: Optional[str]):
|
||||
self.settings_module = settings_module
|
||||
class AddSettingValuesToDjangoConfObject:
|
||||
def __init__(self, settings_modules: List[str]):
|
||||
self.settings_modules = settings_modules
|
||||
|
||||
def __call__(self, ctx: ClassDefContext) -> None:
|
||||
if not self.settings_module:
|
||||
return
|
||||
|
||||
add_settings_to_django_conf_object(ctx, self.settings_module)
|
||||
api = cast(SemanticAnalyzerPass2, ctx.api)
|
||||
for module_name in self.settings_modules:
|
||||
module = api.modules[module_name]
|
||||
load_settings_from_module(ctx.cls, module=module)
|
||||
|
||||
Reference in New Issue
Block a user