From b6a5ccabdf0270abaea489a01a5c9e1b9992b04b Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Wed, 24 Jul 2019 22:32:52 +0300 Subject: [PATCH] move configuration to [mypy.plugins.django-stubs] inside mypy config file --- mypy_django_plugin/django/context.py | 7 ++-- mypy_django_plugin/main.py | 36 +++++++++++++++------ scripts/tests_extension_hook.py | 30 ++++++++--------- setup.py | 1 - test-data/typecheck/fields/test_related.yml | 7 ++-- test-data/typecheck/test_config.yml | 14 ++++---- test-data/typecheck/test_request.yml | 7 ++-- test-data/typecheck/test_settings.yml | 15 +++------ test-data/typecheck/test_shortcuts.yml | 7 ++-- 9 files changed, 63 insertions(+), 61 deletions(-) diff --git a/mypy_django_plugin/django/context.py b/mypy_django_plugin/django/context.py index a91393d..1b5dd9b 100644 --- a/mypy_django_plugin/django/context.py +++ b/mypy_django_plugin/django/context.py @@ -10,6 +10,7 @@ from django.db.models.fields.reverse_related import ForeignObjectRel from django.db.models.sql.query import Query from django.utils.functional import cached_property from mypy.checker import TypeChecker +from mypy.errors import Errors from mypy.types import Instance, Type as MypyType from django.contrib.postgres.fields import ArrayField @@ -139,13 +140,11 @@ class DjangoLookupsContext: class DjangoContext: - def __init__(self, plugin_toml_config: Optional[Dict[str, Any]]) -> None: + def __init__(self, django_settings_module: str) -> None: self.fields_context = DjangoFieldsContext(self) self.lookups_context = DjangoLookupsContext(self) - self.django_settings_module = None - if plugin_toml_config: - self.django_settings_module = plugin_toml_config.get('django_settings_module', None) + self.django_settings_module = django_settings_module self.apps_registry: Optional[Dict[str, str]] = None self.settings: LazySettings = None diff --git a/mypy_django_plugin/main.py b/mypy_django_plugin/main.py index 438cc03..fe8938d 100644 --- a/mypy_django_plugin/main.py +++ b/mypy_django_plugin/main.py @@ -1,9 +1,9 @@ -import os +import configparser from functools import partial from typing import Callable, Dict, List, Optional, Tuple -import toml from django.db.models.fields.related import RelatedField +from mypy.errors import Errors from mypy.nodes import MypyFile, TypeInfo from mypy.options import Options from mypy.plugin import ( @@ -47,17 +47,33 @@ def add_new_manager_base(ctx: ClassDefContext) -> None: helpers.get_django_metadata(sym.node)['manager_bases'][ctx.cls.fullname] = 1 +def extract_django_settings_module(config_file_path: Optional[str]) -> str: + errors = Errors() + if config_file_path is None: + errors.report(0, None, "'django_settings_module' is not set: no mypy config file specified") + errors.raise_error() + + parser = configparser.ConfigParser() + parser.read(config_file_path) + + if not parser.has_section('mypy.plugins.django-stubs'): + errors.report(0, None, "'django_settings_module' is not set: no section [mypy.plugins.django-stubs]", + file=config_file_path) + errors.raise_error() + if not parser.has_option('mypy.plugins.django-stubs', 'django_settings_module'): + 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): def __init__(self, options: Options) -> None: super().__init__(options) - - plugin_toml_config = None - if os.path.exists('pyproject.toml'): - with open('pyproject.toml', 'r') as f: - pyproject_toml = toml.load(f) - plugin_toml_config = pyproject_toml.get('tool', {}).get('django-stubs') - - self.django_context = DjangoContext(plugin_toml_config) + django_settings_module = extract_django_settings_module(options.config_file) + self.django_context = DjangoContext(django_settings_module) def _get_current_queryset_bases(self) -> Dict[str, int]: model_sym = self.lookup_fully_qualified(fullnames.QUERYSET_CLASS_FULLNAME) diff --git a/scripts/tests_extension_hook.py b/scripts/tests_extension_hook.py index ac7aa83..dc97c24 100644 --- a/scripts/tests_extension_hook.py +++ b/scripts/tests_extension_hook.py @@ -3,26 +3,24 @@ from pytest_mypy.item import YamlTestItem def django_plugin_hook(test_item: YamlTestItem) -> None: - settings = { - 'SECRET_KEY': '"1"', - } - additional_settings = test_item.parsed_test_data.get('additional_settings') - if additional_settings: - for item in additional_settings: - name, _, val = item.partition('=') - settings[name] = val - + custom_settings = test_item.parsed_test_data.get('custom_settings', '') installed_apps = test_item.parsed_test_data.get('installed_apps', None) + + if installed_apps and custom_settings: + raise ValueError('"installed_apps" and "custom_settings" are not compatible, please use one or the other') + if installed_apps is not None: + # custom_settings is empty, add INSTALLED_APPS installed_apps += ['django.contrib.contenttypes'] installed_apps_as_str = '(' + ','.join([repr(app) for app in installed_apps]) + ',)' + custom_settings += 'INSTALLED_APPS = ' + installed_apps_as_str - pyproject_toml_file = File(path='pyproject.toml', - content='[tool.django-stubs]\ndjango_settings_module=\'mysettings\'') - test_item.files.append(pyproject_toml_file) + if 'SECRET_KEY' not in custom_settings: + custom_settings = 'SECRET_KEY = "1"\n' + custom_settings - settings_contents = f'INSTALLED_APPS={installed_apps_as_str}\n' - settings_contents += '\n'.join([f'{key}={val}' for key, val in settings.items()]) + if not test_item.additional_mypy_config: + test_item.additional_mypy_config = "[mypy.plugins.django-stubs]\n" \ + "django_settings_module = mysettings" - mysettings_file = File(path='mysettings.py', content=settings_contents) - test_item.files.append(mysettings_file) + mysettings_file = File(path='mysettings.py', content=custom_settings) + test_item.files.append(mysettings_file) diff --git a/setup.py b/setup.py index dc71b9b..167e9bc 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,6 @@ with open('README.md', 'r') as f: dependencies = [ 'mypy>=0.720,<0.730', 'typing-extensions', - 'toml', 'django' ] diff --git a/test-data/typecheck/fields/test_related.yml b/test-data/typecheck/fields/test_related.yml index b0bd874..3951db3 100644 --- a/test-data/typecheck/fields/test_related.yml +++ b/test-data/typecheck/fields/test_related.yml @@ -435,10 +435,9 @@ from myapp.models import Book book = Book() reveal_type(book.publisher) # N: Revealed type is 'myapp.models.Publisher*' - installed_apps: - - myapp - additional_settings: - - BOOK_RELATED_MODEL='myapp.Publisher' + custom_settings: | + INSTALLED_APPS = ('django.contrib.contenttypes', 'myapp') + BOOK_RELATED_MODEL = 'myapp.Publisher' files: - path: myapp/__init__.py - path: myapp/models.py diff --git a/test-data/typecheck/test_config.yml b/test-data/typecheck/test_config.yml index 500ab91..5d475ba 100644 --- a/test-data/typecheck/test_config.yml +++ b/test-data/typecheck/test_config.yml @@ -5,15 +5,13 @@ reveal_type(mymodel.id) # N: Revealed type is 'builtins.int*' reveal_type(mymodel.user) # N: Revealed type is 'django.contrib.auth.models.User*' reveal_type(mymodel.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel]' + mypy_config: | + [mypy.plugins.django-stubs] + django_settings_module = mysettings + custom_settings: | + SECRET_KEY = '1' + INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth', 'myapp') files: - - path: pyproject.toml - content: | - [tool.django-stubs] - django_settings_module = 'mysettings' - - path: mysettings.py - content: | - SECRET_KEY = '1' - INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth', 'myapp') - path: myapp/__init__.py - path: myapp/models.py content: | diff --git a/test-data/typecheck/test_request.yml b/test-data/typecheck/test_request.yml index aac2a2f..1c0850a 100644 --- a/test-data/typecheck/test_request.yml +++ b/test-data/typecheck/test_request.yml @@ -5,10 +5,9 @@ reveal_type(HttpRequest().user) # N: Revealed type is 'myapp.models.MyUser' # check that other fields work ok reveal_type(HttpRequest().method) # N: Revealed type is 'Union[builtins.str, None]' - installed_apps: - - myapp - additional_settings: - - AUTH_USER_MODEL='myapp.MyUser' + custom_settings: | + INSTALLED_APPS = ('django.contrib.contenttypes', 'myapp') + AUTH_USER_MODEL='myapp.MyUser' files: - path: myapp/__init__.py - path: myapp/models.py diff --git a/test-data/typecheck/test_settings.yml b/test-data/typecheck/test_settings.yml index 3e0a8d3..4915c6f 100644 --- a/test-data/typecheck/test_settings.yml +++ b/test-data/typecheck/test_settings.yml @@ -8,17 +8,12 @@ reveal_type(settings.APPS_DIR) # N: Revealed type is 'pathlib.Path' reveal_type(settings.NUMBERS) # N: Revealed type is 'builtins.list[builtins.str*]' reveal_type(settings.DICT) # N: Revealed type is 'builtins.dict[Any, Any]' + custom_settings: | + from base import * + SECRET_KEY = 112233 + NUMBERS = ['one', 'two'] + DICT = {} # type: ignore files: - - path: pyproject.toml - content: | - [tool.django-stubs] - django_settings_module = 'mysettings' - - path: mysettings.py - content: | - from base import * - SECRET_KEY = 112233 - NUMBERS = ['one', 'two'] - DICT = {} # type: ignore - path: base.py content: | from pathlib import Path diff --git a/test-data/typecheck/test_shortcuts.yml b/test-data/typecheck/test_shortcuts.yml index 8927758..e0db4ee 100644 --- a/test-data/typecheck/test_shortcuts.yml +++ b/test-data/typecheck/test_shortcuts.yml @@ -26,10 +26,9 @@ from django.contrib.auth import get_user_model UserModel = get_user_model() reveal_type(UserModel.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyUser]' - additional_settings: - - AUTH_USER_MODEL='myapp.MyUser' - installed_apps: - - myapp + custom_settings: | + INSTALLED_APPS = ('django.contrib.contenttypes', 'myapp') + AUTH_USER_MODEL = 'myapp.MyUser' files: - path: myapp/__init__.py - path: myapp/models.py