add config file support

This commit is contained in:
Maxim Kurnikov
2019-02-09 03:21:49 +03:00
parent 5c6aa9a00b
commit 5f6f597266
4 changed files with 74 additions and 6 deletions

View File

@@ -24,12 +24,23 @@ plugins =
in your `mypy.ini` file. in your `mypy.ini` file.
### `django.conf.settings` support ## Configuration
`settings.SETTING_NAME` will only work if `DJANGO_SETTINGS_MODULE` will be present in the environment, when mypy is executed. In order to specify config file, set `MYPY_DJANGO_CONFIG` environment variable with path to the config file.
If some setting is not recognized to the plugin, but it's clearly there, try adding type annotation to it. Config file format (.ini):
```
[mypy_django_plugin]
# specify settings module to use for django.conf.settings, this setting
# could also be specified with DJANGO_SETTINGS_MODULE environment variable
# (it also takes priority over config file)
django_settings = mysettings.local
# if True, all unknown settings in django.conf.settings will fallback to Any,
# specify it if your settings are loaded dynamically to avoid false positives
ignore_missing_settings = True
```
## To get help ## To get help

View File

@@ -1,6 +1,8 @@
import os import os
from configparser import ConfigParser
from typing import Callable, Dict, Optional, cast from typing import Callable, Dict, Optional, cast
from dataclasses import dataclass
from mypy.checker import TypeChecker from mypy.checker import TypeChecker
from mypy.nodes import TypeInfo from mypy.nodes import TypeInfo
from mypy.options import Options from mypy.options import Options
@@ -89,6 +91,23 @@ def set_primary_key_marking(ctx: FunctionContext) -> Type:
return ctx.default_return_type return ctx.default_return_type
@dataclass
class Config:
django_settings_module: Optional[str] = None
ignore_missing_settings: bool = False
@classmethod
def from_config_file(self, fpath: str) -> 'Config':
ini_config = ConfigParser()
ini_config.read(fpath)
if not ini_config.has_section('mypy_django_plugin'):
raise ValueError('Invalid config file: no [mypy_django_plugin] section')
return Config(django_settings_module=ini_config.get('mypy_django_plugin', 'django_settings',
fallback=None),
ignore_missing_settings=ini_config.get('mypy_django_plugin', 'ignore_missing_settings',
fallback=False))
class DjangoPlugin(Plugin): class DjangoPlugin(Plugin):
def __init__(self, options: Options) -> None: def __init__(self, options: Options) -> None:
super().__init__(options) super().__init__(options)
@@ -96,8 +115,18 @@ class DjangoPlugin(Plugin):
monkeypatch.restore_original_load_graph() monkeypatch.restore_original_load_graph()
monkeypatch.restore_original_dependencies_handling() monkeypatch.restore_original_dependencies_handling()
config_fpath = os.environ.get('MYPY_DJANGO_CONFIG')
if config_fpath:
self.config = Config.from_config_file(config_fpath)
self.django_settings = self.config.django_settings_module
else:
self.config = Config()
self.django_settings = None
if 'DJANGO_SETTINGS_MODULE' in os.environ:
self.django_settings = os.environ['DJANGO_SETTINGS_MODULE']
settings_modules = ['django.conf.global_settings'] settings_modules = ['django.conf.global_settings']
self.django_settings = os.environ.get('DJANGO_SETTINGS_MODULE')
if self.django_settings: if self.django_settings:
settings_modules.append(self.django_settings) settings_modules.append(self.django_settings)
@@ -163,7 +192,8 @@ class DjangoPlugin(Plugin):
settings_modules = ['django.conf.global_settings'] settings_modules = ['django.conf.global_settings']
if self.django_settings: if self.django_settings:
settings_modules.append(self.django_settings) settings_modules.append(self.django_settings)
return AddSettingValuesToDjangoConfObject(settings_modules) return AddSettingValuesToDjangoConfObject(settings_modules,
self.config.ignore_missing_settings)
if fullname in self._get_current_manager_bases(): if fullname in self._get_current_manager_bases():
return transform_manager_class return transform_manager_class

View File

@@ -47,11 +47,15 @@ def load_settings_from_module(settings_classdef: ClassDef, module: MypyFile) ->
class AddSettingValuesToDjangoConfObject: class AddSettingValuesToDjangoConfObject:
def __init__(self, settings_modules: List[str]): def __init__(self, settings_modules: List[str], ignore_missing_settings: bool):
self.settings_modules = settings_modules self.settings_modules = settings_modules
self.ignore_missing_settings = ignore_missing_settings
def __call__(self, ctx: ClassDefContext) -> None: def __call__(self, ctx: ClassDefContext) -> None:
api = cast(SemanticAnalyzerPass2, ctx.api) api = cast(SemanticAnalyzerPass2, ctx.api)
for module_name in self.settings_modules: for module_name in self.settings_modules:
module = api.modules[module_name] module = api.modules[module_name]
load_settings_from_module(ctx.cls, module=module) load_settings_from_module(ctx.cls, module=module)
if self.ignore_missing_settings:
ctx.cls.info.fallback_to_any = True

View File

@@ -0,0 +1,23 @@
[CASE missing_settings_ignored_flag]
from django.conf import settings
reveal_type(settings.NO_SUCH_SETTING) # E: Revealed type is 'Any'
[env MYPY_DJANGO_CONFIG=${MYPY_CWD}/mypy_django.ini]
[file mypy_django.ini]
[[mypy_django_plugin]
ignore_missing_settings = True
[out]
[CASE django_settings_via_config_file]
from django.conf import settings
reveal_type(settings.MY_SETTING) # E: Revealed type is 'builtins.int'
[env MYPY_DJANGO_CONFIG=${MYPY_CWD}/mypy_django.ini]
[file mypy_django.ini]
[[mypy_django_plugin]
django_settings = mysettings
[file mysettings.py]
MY_SETTING: int = 1
[out]