add settings support

This commit is contained in:
Maxim Kurnikov
2019-07-16 19:09:05 +03:00
parent 7b1b1b6bfe
commit 3c3122a93f
7 changed files with 63 additions and 56 deletions

View File

@@ -87,16 +87,16 @@ class DjangoContext:
self.config = DjangoPluginConfig()
self.fields_context = DjangoFieldsContext()
django_settings_module = None
self.django_settings_module = None
if plugin_toml_config:
self.config.ignore_missing_settings = plugin_toml_config.get('ignore_missing_settings', False)
self.config.ignore_missing_model_attributes = plugin_toml_config.get('ignore_missing_model_attributes', False)
django_settings_module = plugin_toml_config.get('django_settings_module', None)
self.django_settings_module = plugin_toml_config.get('django_settings_module', None)
self.apps_registry: Optional[Dict[str, str]] = None
self.settings: LazySettings = None
if django_settings_module:
apps, settings = initialize_django(django_settings_module)
if self.django_settings_module:
apps, settings = initialize_django(self.django_settings_module)
self.apps_registry = apps
self.settings = settings

View File

@@ -1,7 +1,7 @@
from typing import Dict, List, Optional, Set, Union
from mypy.checker import TypeChecker
from mypy.nodes import Expression, MypyFile, NameExpr, SymbolNode, TypeInfo, Var
from mypy.nodes import Expression, MypyFile, NameExpr, SymbolNode, TypeInfo, Var, SymbolTableNode
from mypy.plugin import FunctionContext, MethodContext
from mypy.types import AnyType, Instance, NoneTyp, Type as MypyType, TypeOfAny, UnionType
@@ -10,15 +10,22 @@ class IncompleteDefnException(Exception):
pass
def lookup_fully_qualified_generic(name: str, all_modules: Dict[str, MypyFile]) -> Optional[SymbolNode]:
if '.' not in name:
def lookup_fully_qualified_sym(fullname: str, all_modules: Dict[str, MypyFile]) -> Optional[SymbolTableNode]:
if '.' not in fullname:
return None
module, cls_name = name.rsplit('.', 1)
module, cls_name = fullname.rsplit('.', 1)
module_file = all_modules.get(module)
if module_file is None:
return None
sym = module_file.names.get(cls_name)
if sym is None:
return None
return sym
def lookup_fully_qualified_generic(name: str, all_modules: Dict[str, MypyFile]) -> Optional[SymbolNode]:
sym = lookup_fully_qualified_sym(name, all_modules)
if sym is None:
return None
return sym.node

View File

@@ -5,7 +5,7 @@ from typing import Callable, Dict, List, Optional, Tuple, Type
import toml
from mypy.nodes import MypyFile, TypeInfo
from mypy.options import Options
from mypy.plugin import ClassDefContext, FunctionContext, Plugin, MethodContext
from mypy.plugin import ClassDefContext, FunctionContext, Plugin, MethodContext, AttributeContext
from mypy.types import Type as MypyType
from django.db.models.fields.related import RelatedField
@@ -81,6 +81,10 @@ class NewSemanalDjangoPlugin(Plugin):
return 10, module, -1
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
# for settings
if file.fullname() == 'django.conf' and self.django_context.django_settings_module:
return [self._new_dependency(self.django_context.django_settings_module)]
# for `get_user_model()`
if file.fullname() == 'django.contrib.auth':
auth_user_model_name = self.django_context.settings.AUTH_USER_MODEL
@@ -142,34 +146,12 @@ class NewSemanalDjangoPlugin(Plugin):
if fullname in self._get_current_manager_bases():
return add_new_manager_base
# def get_attribute_hook(self, fullname: str
# ) -> Optional[Callable[[AttributeContext], MypyType]]:
# print(fullname)
# class_name, _, attr_name = fullname.rpartition('.')
# # if class_name == fullnames.DUMMY_SETTINGS_BASE_CLASS:
# # return partial(get_type_of_setting,
# # setting_name=attr_name,
# # settings_modules=self._get_settings_modules_in_order_of_priority(),
# # ignore_missing_settings=self.config.ignore_missing_settings)
#
# if class_name in self._get_current_model_bases():
# # if attr_name == 'id':
# # return return_type_for_id_field
#
# model_info = self._get_typeinfo_or_none(class_name)
# if model_info:
# attr_sym = model_info.get(attr_name)
# if attr_sym and isinstance(attr_sym.node, TypeInfo) \
# and helpers.has_any_of_bases(attr_sym.node, fullnames.MANAGER_CLASSES):
# return partial(querysets.determite_manager_type, django_context=self.django_context)
#
# # related_managers = metadata.get_related_managers_metadata(model_info)
# # if attr_name in related_managers:
# # return partial(determine_type_of_related_manager,
# # related_manager_name=attr_name)
#
# # if attr_name.endswith('_id'):
# # return extract_and_return_primary_key_of_bound_related_field_parameter
def get_attribute_hook(self, fullname: str
) -> Optional[Callable[[AttributeContext], MypyType]]:
class_name, _, attr_name = fullname.rpartition('.')
if class_name == fullnames.DUMMY_SETTINGS_BASE_CLASS:
return partial(settings.get_type_of_settings_attribute,
django_context=self.django_context)
# def get_type_analyze_hook(self, fullname: str
# ) -> Optional[Callable[[AnalyzeTypeContext], MypyType]]:

View File

@@ -1,5 +1,5 @@
from mypy.nodes import TypeInfo
from mypy.plugin import FunctionContext
from mypy.nodes import TypeInfo, MemberExpr
from mypy.plugin import FunctionContext, AttributeContext
from mypy.types import Type as MypyType, TypeType, Instance
from mypy_django_plugin_newsemanal.django.context import DjangoContext
@@ -16,3 +16,30 @@ def get_user_model_hook(ctx: FunctionContext, django_context: DjangoContext) ->
return TypeType(Instance(model_info, []))
def get_type_of_settings_attribute(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
assert isinstance(ctx.context, MemberExpr)
setting_name = ctx.context.name
if not hasattr(django_context.settings, setting_name):
ctx.api.fail(f"'Settings' object has no attribute {setting_name!r}", ctx.context)
return ctx.default_attr_type
# first look for the setting in the project settings file, then global settings
settings_module = ctx.api.modules.get(django_context.django_settings_module)
global_settings_module = ctx.api.modules.get('django.conf.global_settings')
for module in [settings_module, global_settings_module]:
if module is not None:
sym = module.names.get(setting_name)
if sym is not None and sym.type is not None:
return sym.type
# if by any reason it isn't present there, get type from django settings
value = getattr(django_context.settings, setting_name)
value_fullname = helpers.get_class_fullname(value.__class__)
value_info = helpers.lookup_fully_qualified_typeinfo(ctx.api, value_fullname)
if value_info is None:
return ctx.default_attr_type
return Instance(value_info, [])

View File

@@ -24,9 +24,12 @@ class NewSemanalDjangoTestItem(YamlTestItem):
name, _, val = item.partition('=')
settings[name] = val
installed_apps = self.parsed_test_data.get('installed_apps')
if installed_apps:
installed_apps_as_str = '(' + ','.join([repr(app) for app in installed_apps]) + ',)'
installed_apps = self.parsed_test_data.get('installed_apps', None)
if installed_apps is not None:
if not installed_apps:
installed_apps_as_str = '()'
else:
installed_apps_as_str = '(' + ','.join([repr(app) for app in installed_apps]) + ',)'
pyproject_toml_file = File(path='pyproject.toml',
content='[tool.django-stubs]\ndjango_settings_module=\'mysettings\'')

View File

@@ -24,16 +24,6 @@
if TYPE_CHECKING:
reveal_type(MyModel.user) # N: Revealed type is 'django.contrib.auth.models.User*'
- case: missing_settings_ignored_flag
main: |
from django.conf import settings
reveal_type(settings.NO_SUCH_SETTING) # N: Revealed type is 'Any'
files:
- path: pyproject.toml
content: |
[tool.django-stubs]
ignore_missing_settings = true
- case: generate_pyproject_toml_and_settings_file_from_installed_apps_key
main: |
from myapp.models import MyModel

View File

@@ -1,11 +1,11 @@
- case: settings_loaded_from_different_files
disable_cache: true
main: |
from django.conf import settings
# standard settings
reveal_type(settings.AUTH_USER_MODEL) # N: Revealed type is 'builtins.str'
reveal_type(settings.ROOT_DIR) # N: Revealed type is 'builtins.str'
reveal_type(settings.APPS_DIR) # N: Revealed type is 'pathlib.Path'
reveal_type(settings.OBJ) # N: Revealed type is 'django.utils.functional.LazyObject'
reveal_type(settings.NUMBERS) # N: Revealed type is 'builtins.list[builtins.str*]'
reveal_type(settings.DICT) # N: Revealed type is 'builtins.dict[Any, Any]'
files:
@@ -19,9 +19,6 @@
SECRET_KEY = 112233
NUMBERS = ['one', 'two']
DICT = {} # type: ignore
from django.utils.functional import LazyObject
OBJ = LazyObject()
- path: base.py
content: |
from pathlib import Path
@@ -33,6 +30,7 @@
from django.conf import settings
reveal_type(settings.AUTH_USER_MODEL) # N: Revealed type is 'builtins.str'
reveal_type(settings.AUTHENTICATION_BACKENDS) # N: Revealed type is 'typing.Sequence[builtins.str]'
installed_apps: []
- case: fail_if_there_is_no_setting
main: |