mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-08 13:04:47 +08:00
Output a more clear configuration error (#421)
* Output a more clear configuration error * Performance related improvements in config read handling * Check python 3.6 with travis * Revert the .travis.yml py36 inclusion * Added tests for input error handling * Thanks isort, isorted it out * Make exit() function a bit more aesthetic * Single quote -> double quote docstrings * Whitespace removed
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
import configparser
|
import configparser
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Callable, Dict, List, Optional, Tuple
|
from typing import Callable, Dict, List, NoReturn, Optional, Tuple, cast
|
||||||
|
|
||||||
from django.db.models.fields.related import RelatedField
|
from django.db.models.fields.related import RelatedField
|
||||||
from mypy.errors import Errors
|
|
||||||
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 (
|
from mypy.plugin import (
|
||||||
@@ -52,25 +51,40 @@ def add_new_manager_base(ctx: ClassDefContext) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def extract_django_settings_module(config_file_path: Optional[str]) -> str:
|
def extract_django_settings_module(config_file_path: Optional[str]) -> str:
|
||||||
errors = Errors()
|
|
||||||
if config_file_path is None:
|
def exit(error_type: int) -> NoReturn:
|
||||||
errors.report(0, None, "'django_settings_module' is not set: no mypy config file specified")
|
"""Using mypy's argument parser, raise `SystemExit` to fail hard if validation fails.
|
||||||
errors.raise_error()
|
|
||||||
|
Considering that the plugin's startup duration is around double as long as mypy's, this aims to
|
||||||
|
import and construct objects only when that's required - which happens once and terminates the
|
||||||
|
run. Considering that most of the runs are successful, there's no need for this to linger in the
|
||||||
|
global scope.
|
||||||
|
"""
|
||||||
|
from mypy.main import CapturableArgumentParser
|
||||||
|
|
||||||
|
usage = """(config)
|
||||||
|
...
|
||||||
|
[mypy.plugins.django_stubs]
|
||||||
|
django_settings_module: str (required)
|
||||||
|
...
|
||||||
|
""".replace("\n" + 8 * " ", "\n")
|
||||||
|
handler = CapturableArgumentParser(prog='(django-stubs) mypy', usage=usage)
|
||||||
|
messages = {1: 'mypy config file is not specified or found',
|
||||||
|
2: 'no section [mypy.plugins.django-stubs]',
|
||||||
|
3: 'the setting is not provided'}
|
||||||
|
handler.error("'django_settings_module' is not set: " + messages[error_type])
|
||||||
|
|
||||||
parser = configparser.ConfigParser()
|
parser = configparser.ConfigParser()
|
||||||
parser.read(config_file_path) # type: ignore
|
try:
|
||||||
|
parser.read_file(open(cast(str, config_file_path), 'r'), source=config_file_path)
|
||||||
|
except (IsADirectoryError, OSError):
|
||||||
|
exit(1)
|
||||||
|
|
||||||
if not parser.has_section('mypy.plugins.django-stubs'):
|
section = 'mypy.plugins.django-stubs'
|
||||||
errors.report(0, None, "'django_settings_module' is not set: no section [mypy.plugins.django-stubs]",
|
if not parser.has_section(section):
|
||||||
file=config_file_path)
|
exit(2)
|
||||||
errors.raise_error()
|
settings = parser.get(section, 'django_settings_module', fallback=None) or exit(3)
|
||||||
if not parser.has_option('mypy.plugins.django-stubs', 'django_settings_module'):
|
return cast(str, settings).strip('\'"')
|
||||||
errors.report(0, None, "'django_settings_module' is not set: setting is not provided",
|
|
||||||
file=config_file_path)
|
|
||||||
errors.raise_error()
|
|
||||||
|
|
||||||
django_settings_module = parser.get('mypy.plugins.django-stubs', 'django_settings_module').strip('\'"')
|
|
||||||
return django_settings_module
|
|
||||||
|
|
||||||
|
|
||||||
class NewSemanalDjangoPlugin(Plugin):
|
class NewSemanalDjangoPlugin(Plugin):
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
[pytest]
|
[pytest]
|
||||||
testpaths = ./test-data
|
testpaths =
|
||||||
|
./test-plugin
|
||||||
|
./test-data
|
||||||
addopts =
|
addopts =
|
||||||
--tb=native
|
--tb=native
|
||||||
-s
|
-s
|
||||||
|
|||||||
73
test-plugin/test_error_handling.py
Normal file
73
test-plugin/test_error_handling.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import tempfile
|
||||||
|
import typing
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from mypy_django_plugin.main import extract_django_settings_module
|
||||||
|
|
||||||
|
TEMPLATE = """usage: (config)
|
||||||
|
...
|
||||||
|
[mypy.plugins.django_stubs]
|
||||||
|
django_settings_module: str (required)
|
||||||
|
...
|
||||||
|
(django-stubs) mypy: error: 'django_settings_module' is not set: {}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'config_file_contents,message_part',
|
||||||
|
[
|
||||||
|
pytest.param(
|
||||||
|
None,
|
||||||
|
'mypy config file is not specified or found',
|
||||||
|
id='missing-file',
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
['[not-really-django-stubs]'],
|
||||||
|
'no section [mypy.plugins.django-stubs]',
|
||||||
|
id='missing-section',
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
['[mypy.plugins.django-stubs]',
|
||||||
|
'\tnot_django_not_settings_module = badbadmodule'],
|
||||||
|
'the setting is not provided',
|
||||||
|
id='missing-settings-module',
|
||||||
|
),
|
||||||
|
pytest.param(
|
||||||
|
['[mypy.plugins.django-stubs]'],
|
||||||
|
'the setting is not provided',
|
||||||
|
id='no-settings-given',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_misconfiguration_handling(capsys, config_file_contents, message_part):
|
||||||
|
# type: (typing.Any, typing.List[str], str) -> None
|
||||||
|
"""Invalid configuration raises `SystemExit` with a precise error message."""
|
||||||
|
with tempfile.NamedTemporaryFile(mode='w+') as config_file:
|
||||||
|
if not config_file_contents:
|
||||||
|
config_file.close()
|
||||||
|
else:
|
||||||
|
config_file.write('\n'.join(config_file_contents).expandtabs(4))
|
||||||
|
config_file.seek(0)
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit, match='2'):
|
||||||
|
extract_django_settings_module(config_file.name)
|
||||||
|
|
||||||
|
error_message = TEMPLATE.format(message_part)
|
||||||
|
assert error_message == capsys.readouterr().err
|
||||||
|
|
||||||
|
|
||||||
|
def test_correct_configuration() -> None:
|
||||||
|
"""Django settings module gets extracted given valid configuration."""
|
||||||
|
config_file_contents = [
|
||||||
|
'[mypy.plugins.django-stubs]',
|
||||||
|
'\tsome_other_setting = setting',
|
||||||
|
'\tdjango_settings_module = my.module',
|
||||||
|
]
|
||||||
|
with tempfile.NamedTemporaryFile(mode='w+') as config_file:
|
||||||
|
config_file.write('\n'.join(config_file_contents).expandtabs(4))
|
||||||
|
config_file.seek(0)
|
||||||
|
|
||||||
|
extracted = extract_django_settings_module(config_file.name)
|
||||||
|
|
||||||
|
assert extracted == 'my.module'
|
||||||
Reference in New Issue
Block a user