mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-07 04:34:29 +08:00
add settings support
This commit is contained in:
@@ -87,16 +87,16 @@ class DjangoContext:
|
|||||||
self.config = DjangoPluginConfig()
|
self.config = DjangoPluginConfig()
|
||||||
self.fields_context = DjangoFieldsContext()
|
self.fields_context = DjangoFieldsContext()
|
||||||
|
|
||||||
django_settings_module = None
|
self.django_settings_module = None
|
||||||
if plugin_toml_config:
|
if plugin_toml_config:
|
||||||
self.config.ignore_missing_settings = plugin_toml_config.get('ignore_missing_settings', False)
|
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)
|
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.apps_registry: Optional[Dict[str, str]] = None
|
||||||
self.settings: LazySettings = None
|
self.settings: LazySettings = None
|
||||||
if django_settings_module:
|
if self.django_settings_module:
|
||||||
apps, settings = initialize_django(django_settings_module)
|
apps, settings = initialize_django(self.django_settings_module)
|
||||||
self.apps_registry = apps
|
self.apps_registry = apps
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import Dict, List, Optional, Set, Union
|
from typing import Dict, List, Optional, Set, Union
|
||||||
|
|
||||||
from mypy.checker import TypeChecker
|
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.plugin import FunctionContext, MethodContext
|
||||||
from mypy.types import AnyType, Instance, NoneTyp, Type as MypyType, TypeOfAny, UnionType
|
from mypy.types import AnyType, Instance, NoneTyp, Type as MypyType, TypeOfAny, UnionType
|
||||||
|
|
||||||
@@ -10,15 +10,22 @@ class IncompleteDefnException(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def lookup_fully_qualified_generic(name: str, all_modules: Dict[str, MypyFile]) -> Optional[SymbolNode]:
|
def lookup_fully_qualified_sym(fullname: str, all_modules: Dict[str, MypyFile]) -> Optional[SymbolTableNode]:
|
||||||
if '.' not in name:
|
if '.' not in fullname:
|
||||||
return None
|
return None
|
||||||
module, cls_name = name.rsplit('.', 1)
|
module, cls_name = fullname.rsplit('.', 1)
|
||||||
|
|
||||||
module_file = all_modules.get(module)
|
module_file = all_modules.get(module)
|
||||||
if module_file is None:
|
if module_file is None:
|
||||||
return None
|
return None
|
||||||
sym = module_file.names.get(cls_name)
|
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:
|
if sym is None:
|
||||||
return None
|
return None
|
||||||
return sym.node
|
return sym.node
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from typing import Callable, Dict, List, Optional, Tuple, Type
|
|||||||
import toml
|
import toml
|
||||||
from mypy.nodes import MypyFile, TypeInfo
|
from mypy.nodes import MypyFile, TypeInfo
|
||||||
from mypy.options import Options
|
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 mypy.types import Type as MypyType
|
||||||
|
|
||||||
from django.db.models.fields.related import RelatedField
|
from django.db.models.fields.related import RelatedField
|
||||||
@@ -81,6 +81,10 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
return 10, module, -1
|
return 10, module, -1
|
||||||
|
|
||||||
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
|
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()`
|
# for `get_user_model()`
|
||||||
if file.fullname() == 'django.contrib.auth':
|
if file.fullname() == 'django.contrib.auth':
|
||||||
auth_user_model_name = self.django_context.settings.AUTH_USER_MODEL
|
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():
|
if fullname in self._get_current_manager_bases():
|
||||||
return add_new_manager_base
|
return add_new_manager_base
|
||||||
|
|
||||||
# def get_attribute_hook(self, fullname: str
|
def get_attribute_hook(self, fullname: str
|
||||||
# ) -> Optional[Callable[[AttributeContext], MypyType]]:
|
) -> Optional[Callable[[AttributeContext], MypyType]]:
|
||||||
# print(fullname)
|
class_name, _, attr_name = fullname.rpartition('.')
|
||||||
# class_name, _, attr_name = fullname.rpartition('.')
|
if class_name == fullnames.DUMMY_SETTINGS_BASE_CLASS:
|
||||||
# # if class_name == fullnames.DUMMY_SETTINGS_BASE_CLASS:
|
return partial(settings.get_type_of_settings_attribute,
|
||||||
# # return partial(get_type_of_setting,
|
django_context=self.django_context)
|
||||||
# # 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_type_analyze_hook(self, fullname: str
|
# def get_type_analyze_hook(self, fullname: str
|
||||||
# ) -> Optional[Callable[[AnalyzeTypeContext], MypyType]]:
|
# ) -> Optional[Callable[[AnalyzeTypeContext], MypyType]]:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from mypy.nodes import TypeInfo
|
from mypy.nodes import TypeInfo, MemberExpr
|
||||||
from mypy.plugin import FunctionContext
|
from mypy.plugin import FunctionContext, AttributeContext
|
||||||
from mypy.types import Type as MypyType, TypeType, Instance
|
from mypy.types import Type as MypyType, TypeType, Instance
|
||||||
|
|
||||||
from mypy_django_plugin_newsemanal.django.context import DjangoContext
|
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, []))
|
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, [])
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,12 @@ class NewSemanalDjangoTestItem(YamlTestItem):
|
|||||||
name, _, val = item.partition('=')
|
name, _, val = item.partition('=')
|
||||||
settings[name] = val
|
settings[name] = val
|
||||||
|
|
||||||
installed_apps = self.parsed_test_data.get('installed_apps')
|
installed_apps = self.parsed_test_data.get('installed_apps', None)
|
||||||
if installed_apps:
|
if installed_apps is not None:
|
||||||
installed_apps_as_str = '(' + ','.join([repr(app) for app in installed_apps]) + ',)'
|
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',
|
pyproject_toml_file = File(path='pyproject.toml',
|
||||||
content='[tool.django-stubs]\ndjango_settings_module=\'mysettings\'')
|
content='[tool.django-stubs]\ndjango_settings_module=\'mysettings\'')
|
||||||
|
|||||||
@@ -24,16 +24,6 @@
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
reveal_type(MyModel.user) # N: Revealed type is 'django.contrib.auth.models.User*'
|
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
|
- case: generate_pyproject_toml_and_settings_file_from_installed_apps_key
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import MyModel
|
from myapp.models import MyModel
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
- case: settings_loaded_from_different_files
|
- case: settings_loaded_from_different_files
|
||||||
|
disable_cache: true
|
||||||
main: |
|
main: |
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
# standard settings
|
# standard settings
|
||||||
reveal_type(settings.AUTH_USER_MODEL) # N: Revealed type is 'builtins.str'
|
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.ROOT_DIR) # N: Revealed type is 'builtins.str'
|
||||||
reveal_type(settings.APPS_DIR) # N: Revealed type is 'pathlib.Path'
|
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.NUMBERS) # N: Revealed type is 'builtins.list[builtins.str*]'
|
||||||
reveal_type(settings.DICT) # N: Revealed type is 'builtins.dict[Any, Any]'
|
reveal_type(settings.DICT) # N: Revealed type is 'builtins.dict[Any, Any]'
|
||||||
files:
|
files:
|
||||||
@@ -19,9 +19,6 @@
|
|||||||
SECRET_KEY = 112233
|
SECRET_KEY = 112233
|
||||||
NUMBERS = ['one', 'two']
|
NUMBERS = ['one', 'two']
|
||||||
DICT = {} # type: ignore
|
DICT = {} # type: ignore
|
||||||
|
|
||||||
from django.utils.functional import LazyObject
|
|
||||||
OBJ = LazyObject()
|
|
||||||
- path: base.py
|
- path: base.py
|
||||||
content: |
|
content: |
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -33,6 +30,7 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
reveal_type(settings.AUTH_USER_MODEL) # N: Revealed type is 'builtins.str'
|
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]'
|
reveal_type(settings.AUTHENTICATION_BACKENDS) # N: Revealed type is 'typing.Sequence[builtins.str]'
|
||||||
|
installed_apps: []
|
||||||
|
|
||||||
- case: fail_if_there_is_no_setting
|
- case: fail_if_there_is_no_setting
|
||||||
main: |
|
main: |
|
||||||
|
|||||||
Reference in New Issue
Block a user