add support for django.conf.settings.SETTING_NAME

This commit is contained in:
Maxim Kurnikov
2018-11-18 15:58:17 +03:00
parent 71218d4f01
commit 06bb3cd50b
11 changed files with 412 additions and 8 deletions

View File

@@ -1,8 +1,10 @@
from typing import Dict, Optional, Type, Tuple, NamedTuple
from typing import Dict, Optional, NamedTuple
from mypy.nodes import SymbolTableNode, Var, Expression, MemberExpr
from mypy.semanal import SemanticAnalyzerPass2
from mypy.types import Type
from mypy.nodes import SymbolTableNode, Var, Expression
from mypy.plugin import FunctionContext
from mypy.types import Instance
from mypy.types import Instance, UnionType, NoneTyp
MODEL_CLASS_FULLNAME = 'django.db.models.base.Model'
QUERYSET_CLASS_FULLNAME = 'django.db.models.query.QuerySet'
@@ -13,7 +15,6 @@ ONETOONE_FIELD_FULLNAME = 'django.db.models.fields.related.OneToOneField'
def create_new_symtable_node(name: str, kind: int, instance: Instance) -> SymbolTableNode:
new_var = Var(name, instance)
new_var.info = instance.type
return SymbolTableNode(kind, new_var,
plugin_generated=True)
@@ -53,3 +54,14 @@ def get_call_signature_or_none(ctx: FunctionContext) -> Optional[Dict[str, Argum
result[arg_name] = (arg[0], arg_type[0])
return result
def make_optional(typ: Type) -> Type:
return UnionType.make_simplified_union([typ, NoneTyp()])
def make_required(typ: Type) -> Type:
if not isinstance(typ, UnionType):
return typ
items = [item for item in typ.items if not isinstance(item, NoneTyp)]
return UnionType.make_union(items)

View File

@@ -1,5 +1,8 @@
import os
from typing import Callable, Optional
from django.conf import Settings
from mypy.options import Options
from mypy.plugin import Plugin, FunctionContext, ClassDefContext
from mypy.types import Type
@@ -8,6 +11,7 @@ from mypy_django_plugin.plugins.objects_queryset import set_objects_queryset_to_
from mypy_django_plugin.plugins.postgres_fields import determine_type_of_array_field
from mypy_django_plugin.plugins.related_fields import set_related_name_instance_for_onetoonefield, \
set_related_name_manager_for_foreign_key, set_fieldname_attrs_for_related_fields
from mypy_django_plugin.plugins.setup_settings import DjangoConfSettingsInitializerHook
base_model_classes = {helpers.MODEL_CLASS_FULLNAME}
@@ -21,6 +25,15 @@ def transform_model_class(ctx: ClassDefContext) -> None:
class DjangoPlugin(Plugin):
def __init__(self,
options: Options) -> None:
super().__init__(options)
self.django_settings = None
django_settings_module = os.environ.get('DJANGO_SETTINGS_MODULE')
if django_settings_module:
self.django_settings = Settings(django_settings_module)
def get_function_hook(self, fullname: str
) -> Optional[Callable[[FunctionContext], Type]]:
if fullname == helpers.FOREIGN_KEY_FULLNAME:
@@ -37,6 +50,8 @@ class DjangoPlugin(Plugin):
) -> Optional[Callable[[ClassDefContext], None]]:
if fullname in base_model_classes:
return transform_model_class
if fullname == 'django.conf._DjangoConfLazyObject':
return DjangoConfSettingsInitializerHook(settings=self.django_settings)
return None

View File

@@ -0,0 +1,34 @@
from typing import cast, Any
from django.conf import Settings
from mypy.nodes import MDEF, TypeInfo, SymbolTable
from mypy.plugin import ClassDefContext
from mypy.semanal import SemanticAnalyzerPass2
from mypy.types import Instance, AnyType, TypeOfAny
from mypy_django_plugin import helpers
def get_obj_type_name(value: Any) -> str:
return type(value).__module__ + '.' + type(value).__qualname__
class DjangoConfSettingsInitializerHook(object):
def __init__(self, settings: Settings):
self.settings = settings
def __call__(self, ctx: ClassDefContext) -> None:
api = cast(SemanticAnalyzerPass2, ctx.api)
for name, value in self.settings.__dict__.items():
if name.isupper():
if value is None:
ctx.cls.info.names[name] = helpers.create_new_symtable_node(name, MDEF,
instance=api.builtin_type('builtins.object'))
continue
type_fullname = get_obj_type_name(value)
sym = api.lookup_fully_qualified_or_none(type_fullname)
if sym is not None:
args = len(sym.node.type_vars) * [AnyType(TypeOfAny.from_omitted_generics)]
ctx.cls.info.names[name] = helpers.create_new_symtable_node(name, MDEF,
instance=Instance(sym.node, args))