rework settings, add loading of the django.conf.global_settings, cleanups

This commit is contained in:
Maxim Kurnikov
2019-02-07 19:13:39 +03:00
parent faee26703e
commit d4cb729c93
11 changed files with 180 additions and 361 deletions

View File

@@ -23,7 +23,13 @@ plugins =
in your `mypy.ini` file. in your `mypy.ini` file.
Also, it uses value of `DJANGO_SETTINGS_MODULE` from the environment, so set it before execution, otherwise some features will not work.
### `django.conf.settings` support
`settings.SETTING_NAME` will only work if `DJANGO_SETTINGS_MODULE` will be present in the environment, when mypy is executed.
If some setting is not recognized to the plugin, but it's clearly there, try adding type annotation to it.
## To get help ## To get help

View File

@@ -5,156 +5,67 @@ by the DJANGO_SETTINGS_MODULE environment variable.
# This is defined here as a do-nothing function because we can't import # This is defined here as a do-nothing function because we can't import
# django.utils.translation -- that module depends on the settings. # django.utils.translation -- that module depends on the settings.
def gettext_noop(s): from typing import Any, Dict, List, Optional, Pattern, Tuple, Protocol, Union, Callable, TYPE_CHECKING
return s
#################### ####################
# CORE # # CORE #
#################### ####################
if TYPE_CHECKING:
from django.db.models.base import Model
DEBUG = False DEBUG: bool = ...
# Whether the framework should propagate raw exceptions rather than catching # Whether the framework should propagate raw exceptions rather than catching
# them. This is useful under some testing situations and should never be used # them. This is useful under some testing situations and should never be used
# on a live site. # on a live site.
DEBUG_PROPAGATE_EXCEPTIONS = False DEBUG_PROPAGATE_EXCEPTIONS: bool = ...
# People who get code error notifications. # People who get code error notifications.
# In the format [('Full Name', 'email@example.com'), ('Full Name', 'anotheremail@example.com')] # In the format [('Full Name', 'email@example.com'), ('Full Name', 'anotheremail@example.com')]
ADMINS = [] ADMINS: List[Tuple[str, str]] = ...
# List of IP addresses, as strings, that: # List of IP addresses, as strings, that:
# * See debug comments, when DEBUG is true # * See debug comments, when DEBUG is true
# * Receive x-headers # * Receive x-headers
INTERNAL_IPS = [] INTERNAL_IPS: List[str] = ...
# Hosts/domain names that are valid for this site. # Hosts/domain names that are valid for this site.
# "*" matches anything, ".example.com" matches example.com and all subdomains # "*" matches anything, ".example.com" matches example.com and all subdomains
ALLOWED_HOSTS = [] ALLOWED_HOSTS: List[str] = ...
# Local time zone for this installation. All choices can be found here: # Local time zone for this installation. All choices can be found here:
# https://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all # https://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all
# systems may support all possibilities). When USE_TZ is True, this is # systems may support all possibilities). When USE_TZ is True, this is
# interpreted as the default user time zone. # interpreted as the default user time zone.
TIME_ZONE = "America/Chicago" TIME_ZONE: str = ...
# If you set this to True, Django will use timezone-aware datetimes. # If you set this to True, Django will use timezone-aware datetimes.
USE_TZ = False USE_TZ: bool = ...
# Language code for this installation. All choices can be found here: # Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html # http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = "en-us" LANGUAGE_CODE: str = ...
# Languages we provide translations for, out of the box. # Languages we provide translations for, out of the box.
LANGUAGES = [ LANGUAGES: List[Tuple[str, str]] = ...
("af", gettext_noop("Afrikaans")),
("ar", gettext_noop("Arabic")),
("ast", gettext_noop("Asturian")),
("az", gettext_noop("Azerbaijani")),
("bg", gettext_noop("Bulgarian")),
("be", gettext_noop("Belarusian")),
("bn", gettext_noop("Bengali")),
("br", gettext_noop("Breton")),
("bs", gettext_noop("Bosnian")),
("ca", gettext_noop("Catalan")),
("cs", gettext_noop("Czech")),
("cy", gettext_noop("Welsh")),
("da", gettext_noop("Danish")),
("de", gettext_noop("German")),
("dsb", gettext_noop("Lower Sorbian")),
("el", gettext_noop("Greek")),
("en", gettext_noop("English")),
("en-au", gettext_noop("Australian English")),
("en-gb", gettext_noop("British English")),
("eo", gettext_noop("Esperanto")),
("es", gettext_noop("Spanish")),
("es-ar", gettext_noop("Argentinian Spanish")),
("es-co", gettext_noop("Colombian Spanish")),
("es-mx", gettext_noop("Mexican Spanish")),
("es-ni", gettext_noop("Nicaraguan Spanish")),
("es-ve", gettext_noop("Venezuelan Spanish")),
("et", gettext_noop("Estonian")),
("eu", gettext_noop("Basque")),
("fa", gettext_noop("Persian")),
("fi", gettext_noop("Finnish")),
("fr", gettext_noop("French")),
("fy", gettext_noop("Frisian")),
("ga", gettext_noop("Irish")),
("gd", gettext_noop("Scottish Gaelic")),
("gl", gettext_noop("Galician")),
("he", gettext_noop("Hebrew")),
("hi", gettext_noop("Hindi")),
("hr", gettext_noop("Croatian")),
("hsb", gettext_noop("Upper Sorbian")),
("hu", gettext_noop("Hungarian")),
("ia", gettext_noop("Interlingua")),
("id", gettext_noop("Indonesian")),
("io", gettext_noop("Ido")),
("is", gettext_noop("Icelandic")),
("it", gettext_noop("Italian")),
("ja", gettext_noop("Japanese")),
("ka", gettext_noop("Georgian")),
("kab", gettext_noop("Kabyle")),
("kk", gettext_noop("Kazakh")),
("km", gettext_noop("Khmer")),
("kn", gettext_noop("Kannada")),
("ko", gettext_noop("Korean")),
("lb", gettext_noop("Luxembourgish")),
("lt", gettext_noop("Lithuanian")),
("lv", gettext_noop("Latvian")),
("mk", gettext_noop("Macedonian")),
("ml", gettext_noop("Malayalam")),
("mn", gettext_noop("Mongolian")),
("mr", gettext_noop("Marathi")),
("my", gettext_noop("Burmese")),
("nb", gettext_noop("Norwegian Bokmål")),
("ne", gettext_noop("Nepali")),
("nl", gettext_noop("Dutch")),
("nn", gettext_noop("Norwegian Nynorsk")),
("os", gettext_noop("Ossetic")),
("pa", gettext_noop("Punjabi")),
("pl", gettext_noop("Polish")),
("pt", gettext_noop("Portuguese")),
("pt-br", gettext_noop("Brazilian Portuguese")),
("ro", gettext_noop("Romanian")),
("ru", gettext_noop("Russian")),
("sk", gettext_noop("Slovak")),
("sl", gettext_noop("Slovenian")),
("sq", gettext_noop("Albanian")),
("sr", gettext_noop("Serbian")),
("sr-latn", gettext_noop("Serbian Latin")),
("sv", gettext_noop("Swedish")),
("sw", gettext_noop("Swahili")),
("ta", gettext_noop("Tamil")),
("te", gettext_noop("Telugu")),
("th", gettext_noop("Thai")),
("tr", gettext_noop("Turkish")),
("tt", gettext_noop("Tatar")),
("udm", gettext_noop("Udmurt")),
("uk", gettext_noop("Ukrainian")),
("ur", gettext_noop("Urdu")),
("vi", gettext_noop("Vietnamese")),
("zh-hans", gettext_noop("Simplified Chinese")),
("zh-hant", gettext_noop("Traditional Chinese")),
]
# Languages using BiDi (right-to-left) layout # Languages using BiDi (right-to-left) layout
LANGUAGES_BIDI = ["he", "ar", "fa", "ur"] LANGUAGES_BIDI: List[str] = ...
# If you set this to False, Django will make some optimizations so as not # If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery. # to load the internationalization machinery.
USE_I18N = True USE_I18N: bool = ...
LOCALE_PATHS = [] LOCALE_PATHS: List[str] = ...
# Settings for language cookie # Settings for language cookie
LANGUAGE_COOKIE_NAME = "django_language" LANGUAGE_COOKIE_NAME: str = ...
LANGUAGE_COOKIE_AGE = None LANGUAGE_COOKIE_AGE: Optional[int] = ...
LANGUAGE_COOKIE_DOMAIN = None LANGUAGE_COOKIE_DOMAIN: Optional[str] = ...
LANGUAGE_COOKIE_PATH = "/" LANGUAGE_COOKIE_PATH: str = ...
# If you set this to True, Django will format dates, numbers and calendars # If you set this to True, Django will format dates, numbers and calendars
# according to user current locale. # according to user current locale.
USE_L10N = False USE_L10N: bool = ...
# Not-necessarily-technical managers of the site. They get broken link # Not-necessarily-technical managers of the site. They get broken link
# notifications and other various emails. # notifications and other various emails.
@@ -163,66 +74,69 @@ MANAGERS = ADMINS
# Default content type and charset to use for all HttpResponse objects, if a # Default content type and charset to use for all HttpResponse objects, if a
# MIME type isn't manually specified. These are used to construct the # MIME type isn't manually specified. These are used to construct the
# Content-Type header. # Content-Type header.
DEFAULT_CONTENT_TYPE = "text/html" DEFAULT_CONTENT_TYPE: str = ...
DEFAULT_CHARSET = "utf-8" DEFAULT_CHARSET: str = ...
# Encoding of files read from disk (template and initial SQL files). # Encoding of files read from disk (template and initial SQL files).
FILE_CHARSET = "utf-8" FILE_CHARSET: str = ...
# Email address that error messages come from. # Email address that error messages come from.
SERVER_EMAIL = "root@localhost" SERVER_EMAIL: str = ...
# Database connection info. If left empty, will default to the dummy backend. # Database connection info. If left empty, will default to the dummy backend.
DATABASES = {} DATABASES: Dict[str, Dict[str, Any]] = ...
# Classes used to implement DB routing behavior. # Classes used to implement DB routing behavior.
DATABASE_ROUTERS = [] class Router(Protocol):
def allow_migrate(self, db, app_label, **hints): ...
DATABASE_ROUTERS: List[Union[str, Router]] = ...
# The email backend to use. For possible shortcuts see django.core.mail. # The email backend to use. For possible shortcuts see django.core.mail.
# The default is to use the SMTP backend. # The default is to use the SMTP backend.
# Third-party backends can be specified by providing a Python path # Third-party backends can be specified by providing a Python path
# to a module that defines an EmailBackend class. # to a module that defines an EmailBackend class.
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" EMAIL_BACKEND: str = ...
# Host for sending email. # Host for sending email.
EMAIL_HOST = "localhost" EMAIL_HOST: str = ...
# Port for sending email. # Port for sending email.
EMAIL_PORT = 25 EMAIL_PORT: int = ...
# Whether to send SMTP 'Date' header in the local time zone or in UTC. # Whether to send SMTP 'Date' header in the local time zone or in UTC.
EMAIL_USE_LOCALTIME = False EMAIL_USE_LOCALTIME: bool = ...
# Optional SMTP authentication information for EMAIL_HOST. # Optional SMTP authentication information for EMAIL_HOST.
EMAIL_HOST_USER = "" EMAIL_HOST_USER: str = ...
EMAIL_HOST_PASSWORD = "" EMAIL_HOST_PASSWORD: str = ...
EMAIL_USE_TLS = False EMAIL_USE_TLS: bool = ...
EMAIL_USE_SSL = False EMAIL_USE_SSL: bool = ...
EMAIL_SSL_CERTFILE = None EMAIL_SSL_CERTFILE: Optional[str] = ...
EMAIL_SSL_KEYFILE = None EMAIL_SSL_KEYFILE: Optional[str] = ...
EMAIL_TIMEOUT = None EMAIL_TIMEOUT: Optional[int] = ...
# List of strings representing installed apps. # List of strings representing installed apps.
INSTALLED_APPS = [] INSTALLED_APPS: List[str] = ...
TEMPLATES = [] TEMPLATES: List[Dict[str, Any]] = ...
# Default form rendering class. # Default form rendering class.
FORM_RENDERER = "django.forms.renderers.DjangoTemplates" FORM_RENDERER: str = ...
# Default email address to use for various automated correspondence from # Default email address to use for various automated correspondence from
# the site managers. # the site managers.
DEFAULT_FROM_EMAIL = "webmaster@localhost" DEFAULT_FROM_EMAIL: str = ...
# Subject-line prefix for email messages send with django.core.mail.mail_admins # Subject-line prefix for email messages send with django.core.mail.mail_admins
# or ...mail_managers. Make sure to include the trailing space. # or ...mail_managers. Make sure to include the trailing space.
EMAIL_SUBJECT_PREFIX = "[Django] " EMAIL_SUBJECT_PREFIX: str = ...
# Whether to append trailing slashes to URLs. # Whether to append trailing slashes to URLs.
APPEND_SLASH = True APPEND_SLASH: bool = ...
# Whether to prepend the "www." subdomain to URLs that don't have it. # Whether to prepend the "www." subdomain to URLs that don't have it.
PREPEND_WWW = False PREPEND_WWW: bool = ...
# Override the server-derived value of SCRIPT_NAME # Override the server-derived value of SCRIPT_NAME
FORCE_SCRIPT_NAME = None FORCE_SCRIPT_NAME = None
@@ -237,9 +151,9 @@ FORCE_SCRIPT_NAME = None
# re.compile(r'^SiteSucker.*'), # re.compile(r'^SiteSucker.*'),
# re.compile(r'^sohu-search'), # re.compile(r'^sohu-search'),
# ] # ]
DISALLOWED_USER_AGENTS = [] DISALLOWED_USER_AGENTS: List[Pattern] = ...
ABSOLUTE_URL_OVERRIDES = {} ABSOLUTE_URL_OVERRIDES: Dict[str, Callable[[Model], str]] = ...
# List of compiled regular expression objects representing URLs that need not # List of compiled regular expression objects representing URLs that need not
# be reported by BrokenLinkEmailsMiddleware. Here are a few examples: # be reported by BrokenLinkEmailsMiddleware. Here are a few examples:
@@ -251,54 +165,51 @@ ABSOLUTE_URL_OVERRIDES = {}
# re.compile(r'^/phpmyadmin/'), # re.compile(r'^/phpmyadmin/'),
# re.compile(r'\.(cgi|php|pl)$'), # re.compile(r'\.(cgi|php|pl)$'),
# ] # ]
IGNORABLE_404_URLS = [] IGNORABLE_404_URLS: List[Pattern] = ...
# A secret key for this particular Django installation. Used in secret-key # A secret key for this particular Django installation. Used in secret-key
# hashing algorithms. Set this in your settings, or Django will complain # hashing algorithms. Set this in your settings, or Django will complain
# loudly. # loudly.
SECRET_KEY = "" SECRET_KEY: str = ...
# Default file storage mechanism that holds media. # Default file storage mechanism that holds media.
DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage" DEFAULT_FILE_STORAGE: str = ...
# Absolute filesystem path to the directory that will hold user-uploaded files. # Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/var/www/example.com/media/" # Example: "/var/www/example.com/media/"
MEDIA_ROOT = "" MEDIA_ROOT: str = ...
# URL that handles the media served from MEDIA_ROOT. # URL that handles the media served from MEDIA_ROOT.
# Examples: "http://example.com/media/", "http://media.example.com/" # Examples: "http://example.com/media/", "http://media.example.com/"
MEDIA_URL = "" MEDIA_URL: str = ...
# Absolute path to the directory static files should be collected to. # Absolute path to the directory static files should be collected to.
# Example: "/var/www/example.com/static/" # Example: "/var/www/example.com/static/"
STATIC_ROOT = None STATIC_ROOT: Optional[str] = ...
# URL that handles the static files served from STATIC_ROOT. # URL that handles the static files served from STATIC_ROOT.
# Example: "http://example.com/static/", "http://static.example.com/" # Example: "http://example.com/static/", "http://static.example.com/"
STATIC_URL = None STATIC_URL: Optional[str] = ...
# List of upload handler classes to be applied in order. # List of upload handler classes to be applied in order.
FILE_UPLOAD_HANDLERS = [ FILE_UPLOAD_HANDLERS: List[str] = ...
"django.core.files.uploadhandler.MemoryFileUploadHandler",
"django.core.files.uploadhandler.TemporaryFileUploadHandler",
]
# Maximum size, in bytes, of a request before it will be streamed to the # Maximum size, in bytes, of a request before it will be streamed to the
# file system instead of into memory. # file system instead of into memory.
FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 # i.e. 2.5 MB FILE_UPLOAD_MAX_MEMORY_SIZE: int = ... # i.e. 2.5 MB
# Maximum size in bytes of request data (excluding file uploads) that will be # Maximum size in bytes of request data (excluding file uploads) that will be
# read before a SuspiciousOperation (RequestDataTooBig) is raised. # read before a SuspiciousOperation (RequestDataTooBig) is raised.
DATA_UPLOAD_MAX_MEMORY_SIZE = 2621440 # i.e. 2.5 MB DATA_UPLOAD_MAX_MEMORY_SIZE: int = ... # i.e. 2.5 MB
# Maximum number of GET/POST parameters that will be read before a # Maximum number of GET/POST parameters that will be read before a
# SuspiciousOperation (TooManyFieldsSent) is raised. # SuspiciousOperation (TooManyFieldsSent) is raised.
DATA_UPLOAD_MAX_NUMBER_FIELDS = 1000 DATA_UPLOAD_MAX_NUMBER_FIELDS: int = ...
# Directory in which upload streamed files will be temporarily saved. A value of # Directory in which upload streamed files will be temporarily saved. A value of
# `None` will make Django use the operating system's default temporary directory # `None` will make Django use the operating system's default temporary directory
# (i.e. "/tmp" on *nix systems). # (i.e. "/tmp" on *nix systems).
FILE_UPLOAD_TEMP_DIR = None FILE_UPLOAD_TEMP_DIR: Optional[str] = ...
# The numeric mode to set newly-uploaded files to. The value should be a mode # The numeric mode to set newly-uploaded files to. The value should be a mode
# you'd pass directly to os.chmod; see https://docs.python.org/library/os.html#files-and-directories. # you'd pass directly to os.chmod; see https://docs.python.org/library/os.html#files-and-directories.
@@ -313,116 +224,91 @@ FILE_UPLOAD_DIRECTORY_PERMISSIONS = None
# The directory where this setting is pointing should contain subdirectories # The directory where this setting is pointing should contain subdirectories
# named as the locales, containing a formats.py file # named as the locales, containing a formats.py file
# (i.e. "myproject.locale" for myproject/locale/en/formats.py etc. use) # (i.e. "myproject.locale" for myproject/locale/en/formats.py etc. use)
FORMAT_MODULE_PATH = None FORMAT_MODULE_PATH: Optional[str] = ...
# Default formatting for date objects. See all available format strings here: # Default formatting for date objects. See all available format strings here:
# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date # https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
DATE_FORMAT = "N j, Y" DATE_FORMAT: str = ...
# Default formatting for datetime objects. See all available format strings here: # Default formatting for datetime objects. See all available format strings here:
# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date # https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
DATETIME_FORMAT = "N j, Y, P" DATETIME_FORMAT: str = ...
# Default formatting for time objects. See all available format strings here: # Default formatting for time objects. See all available format strings here:
# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date # https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
TIME_FORMAT = "P" TIME_FORMAT: str = ...
# Default formatting for date objects when only the year and month are relevant. # Default formatting for date objects when only the year and month are relevant.
# See all available format strings here: # See all available format strings here:
# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date # https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
YEAR_MONTH_FORMAT = "F Y" YEAR_MONTH_FORMAT: str = ...
# Default formatting for date objects when only the month and day are relevant. # Default formatting for date objects when only the month and day are relevant.
# See all available format strings here: # See all available format strings here:
# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date # https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
MONTH_DAY_FORMAT = "F j" MONTH_DAY_FORMAT: str = ...
# Default short formatting for date objects. See all available format strings here: # Default short formatting for date objects. See all available format strings here:
# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date # https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
SHORT_DATE_FORMAT = "m/d/Y" SHORT_DATE_FORMAT: str = ...
# Default short formatting for datetime objects. # Default short formatting for datetime objects.
# See all available format strings here: # See all available format strings here:
# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date # https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
SHORT_DATETIME_FORMAT = "m/d/Y P" SHORT_DATETIME_FORMAT: str = ...
# Default formats to be used when parsing dates from input boxes, in order # Default formats to be used when parsing dates from input boxes, in order
# See all available format string here: # See all available format string here:
# https://docs.python.org/library/datetime.html#strftime-behavior # https://docs.python.org/library/datetime.html#strftime-behavior
# * Note that these format strings are different from the ones to display dates # * Note that these format strings are different from the ones to display dates
DATE_INPUT_FORMATS = [ DATE_INPUT_FORMATS: List[str] = ...
"%Y-%m-%d",
"%m/%d/%Y",
"%m/%d/%y", # '2006-10-25', '10/25/2006', '10/25/06'
"%b %d %Y",
"%b %d, %Y", # 'Oct 25 2006', 'Oct 25, 2006'
"%d %b %Y",
"%d %b, %Y", # '25 Oct 2006', '25 Oct, 2006'
"%B %d %Y",
"%B %d, %Y", # 'October 25 2006', 'October 25, 2006'
"%d %B %Y",
"%d %B, %Y", # '25 October 2006', '25 October, 2006'
]
# Default formats to be used when parsing times from input boxes, in order # Default formats to be used when parsing times from input boxes, in order
# See all available format string here: # See all available format string here:
# https://docs.python.org/library/datetime.html#strftime-behavior # https://docs.python.org/library/datetime.html#strftime-behavior
# * Note that these format strings are different from the ones to display dates # * Note that these format strings are different from the ones to display dates
TIME_INPUT_FORMATS = ["%H:%M:%S", "%H:%M:%S.%f", "%H:%M"] # '14:30:59' # '14:30:59.000200' # '14:30' TIME_INPUT_FORMATS: List[str] = ... # '14:30:59' # '14:30:59.000200' # '14:30'
# Default formats to be used when parsing dates and times from input boxes, # Default formats to be used when parsing dates and times from input boxes,
# in order # in order
# See all available format string here: # See all available format string here:
# https://docs.python.org/library/datetime.html#strftime-behavior # https://docs.python.org/library/datetime.html#strftime-behavior
# * Note that these format strings are different from the ones to display dates # * Note that these format strings are different from the ones to display dates
DATETIME_INPUT_FORMATS = [ DATETIME_INPUT_FORMATS: List[str] = ...
"%Y-%m-%d %H:%M:%S", # '2006-10-25 14:30:59'
"%Y-%m-%d %H:%M:%S.%f", # '2006-10-25 14:30:59.000200'
"%Y-%m-%d %H:%M", # '2006-10-25 14:30'
"%Y-%m-%d", # '2006-10-25'
"%m/%d/%Y %H:%M:%S", # '10/25/2006 14:30:59'
"%m/%d/%Y %H:%M:%S.%f", # '10/25/2006 14:30:59.000200'
"%m/%d/%Y %H:%M", # '10/25/2006 14:30'
"%m/%d/%Y", # '10/25/2006'
"%m/%d/%y %H:%M:%S", # '10/25/06 14:30:59'
"%m/%d/%y %H:%M:%S.%f", # '10/25/06 14:30:59.000200'
"%m/%d/%y %H:%M", # '10/25/06 14:30'
"%m/%d/%y", # '10/25/06'
]
# First day of week, to be used on calendars # First day of week, to be used on calendars
# 0 means Sunday, 1 means Monday... # 0 means Sunday, 1 means Monday...
FIRST_DAY_OF_WEEK = 0 FIRST_DAY_OF_WEEK: int = ...
# Decimal separator symbol # Decimal separator symbol
DECIMAL_SEPARATOR = "." DECIMAL_SEPARATOR: str = ...
# Boolean that sets whether to add thousand separator when formatting numbers # Boolean that sets whether to add thousand separator when formatting numbers
USE_THOUSAND_SEPARATOR = False USE_THOUSAND_SEPARATOR: bool = ...
# Number of digits that will be together, when splitting them by # Number of digits that will be together, when splitting them by
# THOUSAND_SEPARATOR. 0 means no grouping, 3 means splitting by thousands... # THOUSAND_SEPARATOR. 0 means no grouping, 3 means splitting by thousands...
NUMBER_GROUPING = 0 NUMBER_GROUPING: int = ...
# Thousand separator symbol # Thousand separator symbol
THOUSAND_SEPARATOR = "," THOUSAND_SEPARATOR: str = ...
# The tablespaces to use for each model when not specified otherwise. # The tablespaces to use for each model when not specified otherwise.
DEFAULT_TABLESPACE = "" DEFAULT_TABLESPACE: str = ...
DEFAULT_INDEX_TABLESPACE = "" DEFAULT_INDEX_TABLESPACE: str = ...
# Default X-Frame-Options header value # Default X-Frame-Options header value
X_FRAME_OPTIONS = "SAMEORIGIN" X_FRAME_OPTIONS: str = ...
USE_X_FORWARDED_HOST = False USE_X_FORWARDED_HOST: bool = ...
USE_X_FORWARDED_PORT = False USE_X_FORWARDED_PORT: bool = ...
# The Python dotted path to the WSGI application that Django's internal server # The Python dotted path to the WSGI application that Django's internal server
# (runserver) will use. If `None`, the return value of # (runserver) will use. If `None`, the return value of
# 'django.core.wsgi.get_wsgi_application' is used, thus preserving the same # 'django.core.wsgi.get_wsgi_application' is used, thus preserving the same
# behavior as previous versions of Django. Otherwise this should point to an # behavior as previous versions of Django. Otherwise this should point to an
# actual WSGI application object. # actual WSGI application object.
WSGI_APPLICATION = None WSGI_APPLICATION: Optional[str] = ...
# If your Django app is behind a proxy that sets a header to specify secure # If your Django app is behind a proxy that sets a header to specify secure
# connections, AND that proxy ensures that user-submitted headers with the # connections, AND that proxy ensures that user-submitted headers with the
@@ -431,7 +317,7 @@ WSGI_APPLICATION = None
# that header/value, request.is_secure() will return True. # that header/value, request.is_secure() will return True.
# WARNING! Only set this if you fully understand what you're doing. Otherwise, # WARNING! Only set this if you fully understand what you're doing. Otherwise,
# you may be opening yourself up to a security risk. # you may be opening yourself up to a security risk.
SECURE_PROXY_SSL_HEADER = None SECURE_PROXY_SSL_HEADER: Optional[Tuple[str, str]] = ...
############## ##############
# MIDDLEWARE # # MIDDLEWARE #
@@ -440,7 +326,7 @@ SECURE_PROXY_SSL_HEADER = None
# List of middleware to use. Order is important; in the request phase, these # List of middleware to use. Order is important; in the request phase, these
# middleware will be applied in the order given, and in the response # middleware will be applied in the order given, and in the response
# phase the middleware will be applied in reverse order. # phase the middleware will be applied in reverse order.
MIDDLEWARE = [] MIDDLEWARE: List[str] = ...
############ ############
# SESSIONS # # SESSIONS #
@@ -453,7 +339,7 @@ SESSION_COOKIE_NAME = "sessionid"
# Age of cookie, in seconds (default: 2 weeks). # Age of cookie, in seconds (default: 2 weeks).
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2
# A string like "example.com", or None for standard domain cookie. # A string like "example.com", or None for standard domain cookie.
SESSION_COOKIE_DOMAIN = None SESSION_COOKIE_DOMAIN: Optional[str] = ...
# Whether the session cookie should be secure (https:// only). # Whether the session cookie should be secure (https:// only).
SESSION_COOKIE_SECURE = False SESSION_COOKIE_SECURE = False
# The path of the session cookie. # The path of the session cookie.
@@ -471,7 +357,7 @@ SESSION_EXPIRE_AT_BROWSER_CLOSE = False
SESSION_ENGINE = "django.contrib.sessions.backends.db" SESSION_ENGINE = "django.contrib.sessions.backends.db"
# Directory to store session files if using the file session module. If None, # Directory to store session files if using the file session module. If None,
# the backend will use a sensible default. # the backend will use a sensible default.
SESSION_FILE_PATH = None SESSION_FILE_PATH: Optional[str] = ...
# class to serialize session data # class to serialize session data
SESSION_SERIALIZER = "django.contrib.sessions.serializers.JSONSerializer" SESSION_SERIALIZER = "django.contrib.sessions.serializers.JSONSerializer"
@@ -480,7 +366,7 @@ SESSION_SERIALIZER = "django.contrib.sessions.serializers.JSONSerializer"
######### #########
# The cache backends to use. # The cache backends to use.
CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}} CACHES: Dict[str, Dict[str, Any]] = ...
CACHE_MIDDLEWARE_KEY_PREFIX = "" CACHE_MIDDLEWARE_KEY_PREFIX = ""
CACHE_MIDDLEWARE_SECONDS = 600 CACHE_MIDDLEWARE_SECONDS = 600
CACHE_MIDDLEWARE_ALIAS = "default" CACHE_MIDDLEWARE_ALIAS = "default"
@@ -489,15 +375,15 @@ CACHE_MIDDLEWARE_ALIAS = "default"
# AUTHENTICATION # # AUTHENTICATION #
################## ##################
AUTH_USER_MODEL = "auth.User" AUTH_USER_MODEL: str = ...
AUTHENTICATION_BACKENDS = ["django.contrib.auth.backends.ModelBackend"] AUTHENTICATION_BACKENDS: List[str] = ...
LOGIN_URL = "/accounts/login/" LOGIN_URL = "/accounts/login/"
LOGIN_REDIRECT_URL = "/accounts/profile/" LOGIN_REDIRECT_URL: str = ...
LOGOUT_REDIRECT_URL = None LOGOUT_REDIRECT_URL: Optional[str] = ...
# The number of days a password reset link is valid for # The number of days a password reset link is valid for
PASSWORD_RESET_TIMEOUT_DAYS = 3 PASSWORD_RESET_TIMEOUT_DAYS = 3
@@ -505,14 +391,9 @@ PASSWORD_RESET_TIMEOUT_DAYS = 3
# the first hasher in this list is the preferred algorithm. any # the first hasher in this list is the preferred algorithm. any
# password using different algorithms will be converted automatically # password using different algorithms will be converted automatically
# upon login # upon login
PASSWORD_HASHERS = [ PASSWORD_HASHERS: List[str] = ...
"django.contrib.auth.hashers.PBKDF2PasswordHasher",
"django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
"django.contrib.auth.hashers.Argon2PasswordHasher",
"django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
]
AUTH_PASSWORD_VALIDATORS = [] AUTH_PASSWORD_VALIDATORS: List[Dict[str, str]] = ...
########### ###########
# SIGNING # # SIGNING #
@@ -537,7 +418,7 @@ CSRF_COOKIE_SECURE = False
CSRF_COOKIE_HTTPONLY = False CSRF_COOKIE_HTTPONLY = False
CSRF_COOKIE_SAMESITE = "Lax" CSRF_COOKIE_SAMESITE = "Lax"
CSRF_HEADER_NAME = "HTTP_X_CSRFTOKEN" CSRF_HEADER_NAME = "HTTP_X_CSRFTOKEN"
CSRF_TRUSTED_ORIGINS = [] CSRF_TRUSTED_ORIGINS: List[str] = ...
CSRF_USE_SESSIONS = False CSRF_USE_SESSIONS = False
############ ############
@@ -558,7 +439,7 @@ MESSAGE_STORAGE = "django.contrib.messages.storage.fallback.FallbackStorage"
LOGGING_CONFIG = "logging.config.dictConfig" LOGGING_CONFIG = "logging.config.dictConfig"
# Custom logging configuration. # Custom logging configuration.
LOGGING = {} LOGGING: Dict[str, Any] = ...
# Default exception reporter filter class used in case none has been # Default exception reporter filter class used in case none has been
# specifically assigned to the HttpRequest instance. # specifically assigned to the HttpRequest instance.
@@ -573,39 +454,35 @@ TEST_RUNNER = "django.test.runner.DiscoverRunner"
# Apps that don't need to be serialized at test database creation time # Apps that don't need to be serialized at test database creation time
# (only apps with migrations are to start with) # (only apps with migrations are to start with)
TEST_NON_SERIALIZED_APPS = [] TEST_NON_SERIALIZED_APPS: List[str] = ...
############ ############
# FIXTURES # # FIXTURES #
############ ############
# The list of directories to search for fixtures # The list of directories to search for fixtures
FIXTURE_DIRS = [] FIXTURE_DIRS: List[str] = ...
############### ###############
# STATICFILES # # STATICFILES #
############### ###############
# A list of locations of additional static files # A list of locations of additional static files
STATICFILES_DIRS = [] STATICFILES_DIRS: List[str] = ...
# The default file storage backend used during the build process # The default file storage backend used during the build process
STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage" STATICFILES_STORAGE: str = ...
# List of finder classes that know how to find static files in # List of finder classes that know how to find static files in
# various locations. # various locations.
STATICFILES_FINDERS = [ STATICFILES_FINDERS: List[str] = ...
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
]
############## ##############
# MIGRATIONS # # MIGRATIONS #
############## ##############
# Migration module overrides for apps, by app label. # Migration module overrides for apps, by app label.
MIGRATION_MODULES = {} MIGRATION_MODULES: Dict[str, str] = ...
################# #################
# SYSTEM CHECKS # # SYSTEM CHECKS #
@@ -615,7 +492,7 @@ MIGRATION_MODULES = {}
# issues like warnings, infos or debugs will not generate a message. Silencing # issues like warnings, infos or debugs will not generate a message. Silencing
# serious issues like errors and criticals does not result in hiding the # serious issues like errors and criticals does not result in hiding the
# message, but Django will not stop you from e.g. running server. # message, but Django will not stop you from e.g. running server.
SILENCED_SYSTEM_CHECKS = [] SILENCED_SYSTEM_CHECKS: List[str] = ...
####################### #######################
# SECURITY MIDDLEWARE # # SECURITY MIDDLEWARE #
@@ -625,6 +502,6 @@ SECURE_CONTENT_TYPE_NOSNIFF = False
SECURE_HSTS_INCLUDE_SUBDOMAINS = False SECURE_HSTS_INCLUDE_SUBDOMAINS = False
SECURE_HSTS_PRELOAD = False SECURE_HSTS_PRELOAD = False
SECURE_HSTS_SECONDS = 0 SECURE_HSTS_SECONDS = 0
SECURE_REDIRECT_EXEMPT = [] SECURE_REDIRECT_EXEMPT: List[str] = ...
SECURE_SSL_HOST = None SECURE_SSL_HOST = None
SECURE_SSL_REDIRECT = False SECURE_SSL_REDIRECT = False

View File

@@ -58,7 +58,7 @@ class PermissionsMixin(models.Model):
def has_perms(self, perm_list: Union[List[str], Set[str], Tuple[str]], obj: None = ...) -> bool: ... def has_perms(self, perm_list: Union[List[str], Set[str], Tuple[str]], obj: None = ...) -> bool: ...
def has_module_perms(self, app_label: str) -> bool: ... def has_module_perms(self, app_label: str) -> bool: ...
class AbstractUser(AbstractBaseUser, PermissionsMixin): class AbstractUser(AbstractBaseUser, PermissionsMixin): # type: ignore
is_superuser: bool is_superuser: bool
username_validator: Any = ... username_validator: Any = ...
username: str = ... username: str = ...

View File

@@ -2,7 +2,7 @@ from typing import Any, Callable, Dict, List, Optional, Type, Union, Sequence, P
from django.db.models import Manager, QuerySet from django.db.models import Manager, QuerySet
from django.db.models.base import Model from django.db.models.base import Model
from django.http.response import HttpResponse, HttpResponseRedirect from django.http.response import HttpResponse as HttpResponse, HttpResponseRedirect as HttpResponseRedirect
from django.http import HttpRequest from django.http import HttpRequest

View File

@@ -61,6 +61,7 @@ class override(ContextDecorator):
def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None: ... def __exit__(self, exc_type: None, exc_value: None, traceback: None) -> None: ...
def get_language() -> Optional[str]: ... def get_language() -> Optional[str]: ...
def get_language_from_path(path: str) -> Optional[str]: ...
def get_language_bidi() -> bool: ... def get_language_bidi() -> bool: ...
def check_for_language(lang_code: Optional[str]) -> bool: ... def check_for_language(lang_code: Optional[str]) -> bool: ...
def to_language(locale: str) -> str: ... def to_language(locale: str) -> str: ...

View File

@@ -1,18 +1,18 @@
import os import os
from typing import Callable, Optional, cast, Dict from typing import Callable, Dict, Optional, cast
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
from mypy.plugin import Plugin, FunctionContext, ClassDefContext, MethodContext from mypy.plugin import ClassDefContext, FunctionContext, MethodContext, Plugin
from mypy.types import Type, Instance from mypy.types import Instance, Type
from mypy_django_plugin import helpers, monkeypatch from mypy_django_plugin import helpers, monkeypatch
from mypy_django_plugin.plugins.fields import determine_type_of_array_field from mypy_django_plugin.plugins.fields import determine_type_of_array_field
from mypy_django_plugin.plugins.migrations import determine_model_cls_from_string_for_migrations from mypy_django_plugin.plugins.migrations import determine_model_cls_from_string_for_migrations
from mypy_django_plugin.plugins.models import process_model_class from mypy_django_plugin.plugins.models import process_model_class
from mypy_django_plugin.plugins.related_fields import extract_to_parameter_as_get_ret_type_for_related_field, reparametrize_with from mypy_django_plugin.plugins.related_fields import extract_to_parameter_as_get_ret_type_for_related_field, reparametrize_with
from mypy_django_plugin.plugins.settings import DjangoConfSettingsInitializerHook from mypy_django_plugin.plugins.settings import AddSettingValuesToDjangoConfObject
def transform_model_class(ctx: ClassDefContext) -> None: def transform_model_class(ctx: ClassDefContext) -> None:
@@ -55,19 +55,21 @@ def determine_proper_manager_type(ctx: FunctionContext) -> Type:
class DjangoPlugin(Plugin): class DjangoPlugin(Plugin):
def __init__(self, def __init__(self, options: Options) -> None:
options: Options) -> None:
super().__init__(options) super().__init__(options)
self.django_settings = os.environ.get('DJANGO_SETTINGS_MODULE')
if self.django_settings:
monkeypatch.load_graph_to_add_settings_file_as_a_source_seed(self.django_settings)
monkeypatch.inject_dependencies(self.django_settings)
else:
monkeypatch.restore_original_load_graph() monkeypatch.restore_original_load_graph()
monkeypatch.restore_original_dependencies_handling() monkeypatch.restore_original_dependencies_handling()
def get_current_model_bases(self) -> Dict[str, int]: settings_modules = ['django.conf.global_settings']
self.django_settings = os.environ.get('DJANGO_SETTINGS_MODULE')
if self.django_settings:
settings_modules.append(self.django_settings)
monkeypatch.add_modules_as_a_source_seed_files(settings_modules)
monkeypatch.inject_modules_as_dependencies_for_django_conf_settings(settings_modules)
def _get_current_model_bases(self) -> Dict[str, int]:
model_sym = self.lookup_fully_qualified(helpers.MODEL_CLASS_FULLNAME) model_sym = self.lookup_fully_qualified(helpers.MODEL_CLASS_FULLNAME)
if model_sym is not None and isinstance(model_sym.node, TypeInfo): if model_sym is not None and isinstance(model_sym.node, TypeInfo):
if 'django' not in model_sym.node.metadata: if 'django' not in model_sym.node.metadata:
@@ -78,7 +80,7 @@ class DjangoPlugin(Plugin):
else: else:
return {} return {}
def get_current_manager_bases(self) -> Dict[str, int]: def _get_current_manager_bases(self) -> Dict[str, int]:
manager_sym = self.lookup_fully_qualified(helpers.MANAGER_CLASS_FULLNAME) manager_sym = self.lookup_fully_qualified(helpers.MANAGER_CLASS_FULLNAME)
if manager_sym is not None and isinstance(manager_sym.node, TypeInfo): if manager_sym is not None and isinstance(manager_sym.node, TypeInfo):
if 'django' not in manager_sym.node.metadata: if 'django' not in manager_sym.node.metadata:
@@ -99,7 +101,7 @@ class DjangoPlugin(Plugin):
if fullname == 'django.contrib.postgres.fields.array.ArrayField': if fullname == 'django.contrib.postgres.fields.array.ArrayField':
return determine_type_of_array_field return determine_type_of_array_field
manager_bases = self.get_current_manager_bases() manager_bases = self._get_current_manager_bases()
if fullname in manager_bases: if fullname in manager_bases:
return determine_proper_manager_type return determine_proper_manager_type
@@ -112,13 +114,16 @@ class DjangoPlugin(Plugin):
def get_base_class_hook(self, fullname: str def get_base_class_hook(self, fullname: str
) -> Optional[Callable[[ClassDefContext], None]]: ) -> Optional[Callable[[ClassDefContext], None]]:
if fullname in self.get_current_model_bases(): if fullname in self._get_current_model_bases():
return transform_model_class return transform_model_class
if fullname == helpers.DUMMY_SETTINGS_BASE_CLASS: if fullname == helpers.DUMMY_SETTINGS_BASE_CLASS:
return DjangoConfSettingsInitializerHook(settings_module=self.django_settings) settings_modules = ['django.conf.global_settings']
if self.django_settings:
settings_modules.append(self.django_settings)
return AddSettingValuesToDjangoConfObject(settings_modules)
if fullname in self.get_current_manager_bases(): if fullname in self._get_current_manager_bases():
return transform_manager_class return transform_manager_class
return None return None

View File

@@ -1,5 +1,4 @@
from .dependencies import (load_graph_to_add_settings_file_as_a_source_seed, from .dependencies import (add_modules_as_a_source_seed_files,
inject_dependencies, inject_modules_as_dependencies_for_django_conf_settings,
restore_original_load_graph, restore_original_load_graph,
restore_original_dependencies_handling, restore_original_dependencies_handling)
process_settings_before_dependants)

View File

@@ -1,26 +1,25 @@
from typing import List, Optional, AbstractSet, MutableSet, Set from typing import List, Optional
from mypy.build import BuildManager, Graph, State, PRI_ALL from mypy import build
from mypy.build import BuildManager, Graph, State
from mypy.modulefinder import BuildSource from mypy.modulefinder import BuildSource
old_load_graph = build.load_graph
OldState = build.State
def is_module_present_in_sources(module_name: str, sources: List[BuildSource]): def is_module_present_in_sources(module_name: str, sources: List[BuildSource]):
return any([source.module == module_name for source in sources]) return any([source.module == module_name for source in sources])
from mypy import build def add_modules_as_a_source_seed_files(modules: List[str]) -> None:
old_load_graph = build.load_graph
OldState = build.State
old_sorted_components = build.sorted_components
def load_graph_to_add_settings_file_as_a_source_seed(settings_module: str):
def patched_load_graph(sources: List[BuildSource], manager: BuildManager, def patched_load_graph(sources: List[BuildSource], manager: BuildManager,
old_graph: Optional[Graph] = None, old_graph: Optional[Graph] = None,
new_modules: Optional[List[State]] = None): new_modules: Optional[List[State]] = None):
if not is_module_present_in_sources(settings_module, sources): # add global settings
sources.append(BuildSource(None, settings_module, None)) for module_name in modules:
if not is_module_present_in_sources(module_name, sources):
sources.append(BuildSource(None, module_name, None))
return old_load_graph(sources=sources, manager=manager, return old_load_graph(sources=sources, manager=manager,
old_graph=old_graph, old_graph=old_graph,
@@ -35,14 +34,14 @@ def restore_original_load_graph():
build.load_graph = old_load_graph build.load_graph = old_load_graph
def inject_dependencies(settings_module: str): def inject_modules_as_dependencies_for_django_conf_settings(modules: List[str]) -> None:
from mypy import build from mypy import build
class PatchedState(build.State): class PatchedState(build.State):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if self.id == 'django.conf': if self.id == 'django.conf':
self.dependencies.append(settings_module) self.dependencies.extend(modules)
build.State = PatchedState build.State = PatchedState
@@ -51,40 +50,3 @@ def restore_original_dependencies_handling():
from mypy import build from mypy import build
build.State = OldState build.State = OldState
def _extract_dependencies(graph: Graph, state_id: str, visited_modules: Set[str]) -> Set[str]:
visited_modules.add(state_id)
dependencies = set(graph[state_id].dependencies)
for new_dep_id in dependencies.copy():
if new_dep_id not in visited_modules:
dependencies.update(_extract_dependencies(graph, new_dep_id, visited_modules))
return dependencies
def extract_module_dependencies(graph: Graph, state_id: str) -> Set[str]:
visited_modules = set()
return _extract_dependencies(graph, state_id, visited_modules=visited_modules)
def process_settings_before_dependants(settings_module: str):
def patched_sorted_components(graph: Graph,
vertices: Optional[AbstractSet[str]] = None,
pri_max: int = PRI_ALL) -> List[AbstractSet[str]]:
sccs = old_sorted_components(graph,
vertices=vertices,
pri_max=pri_max)
for i, scc in enumerate(sccs.copy()):
if 'django.conf' in scc:
django_conf_deps = set(extract_module_dependencies(graph, 'django.conf')).union({'django.conf'})
old_scc_modified = scc.difference(django_conf_deps)
new_scc = scc.difference(old_scc_modified)
if not old_scc_modified:
# already processed
break
sccs[i] = frozenset(old_scc_modified)
sccs.insert(i, frozenset(new_scc))
break
return sccs
build.sorted_components = patched_sorted_components

View File

@@ -1,9 +1,9 @@
import dataclasses from abc import ABCMeta, abstractmethod
from abc import abstractmethod, ABCMeta from typing import Dict, Iterator, Optional, Tuple, cast
from typing import cast, Iterator, Tuple, Optional, Dict
from mypy.nodes import ClassDef, AssignmentStmt, CallExpr, MemberExpr, StrExpr, NameExpr, MDEF, TypeInfo, Var, SymbolTableNode, \ import dataclasses
Lvalue, Expression, MypyFile, Context from mypy.nodes import AssignmentStmt, CallExpr, ClassDef, Context, Expression, Lvalue, MDEF, MemberExpr, MypyFile, NameExpr, \
StrExpr, SymbolTableNode, TypeInfo, Var
from mypy.plugin import ClassDefContext from mypy.plugin import ClassDefContext
from mypy.semanal import SemanticAnalyzerPass2 from mypy.semanal import SemanticAnalyzerPass2
from mypy.types import Instance from mypy.types import Instance
@@ -45,6 +45,7 @@ class ModelClassInitializer(metaclass=ABCMeta):
var._fullname = self.model_classdef.info.fullname() + '.' + name var._fullname = self.model_classdef.info.fullname() + '.' + name
var.is_inferred = True var.is_inferred = True
var.is_initialized_in_class = True var.is_initialized_in_class = True
var.is_classvar = True
self.model_classdef.info.names[name] = SymbolTableNode(MDEF, var) self.model_classdef.info.names[name] = SymbolTableNode(MDEF, var)
@abstractmethod @abstractmethod

View File

@@ -1,9 +1,9 @@
from typing import cast, List, Optional from typing import List, Optional, cast
from mypy.nodes import Var, Context, SymbolNode, SymbolTableNode from mypy.nodes import ClassDef, Context, MypyFile, SymbolNode, SymbolTableNode, Var
from mypy.plugin import ClassDefContext from mypy.plugin import ClassDefContext
from mypy.semanal import SemanticAnalyzerPass2 from mypy.semanal import SemanticAnalyzerPass2
from mypy.types import Instance, UnionType, NoneTyp, Type from mypy.types import Instance, NoneTyp, Type, UnionType
def get_error_context(node: SymbolNode) -> Context: def get_error_context(node: SymbolNode) -> Context:
@@ -36,39 +36,22 @@ def make_sym_copy_of_setting(sym: SymbolTableNode) -> Optional[SymbolTableNode]:
return None return None
def add_settings_to_django_conf_object(ctx: ClassDefContext, def load_settings_from_module(settings_classdef: ClassDef, module: MypyFile) -> None:
settings_module: str) -> None: for name, sym in module.names.items():
api = cast(SemanticAnalyzerPass2, ctx.api)
if settings_module not in api.modules:
return None
settings_file = api.modules[settings_module]
for name, sym in settings_file.names.items():
if name.isupper() and isinstance(sym.node, Var): if name.isupper() and isinstance(sym.node, Var):
if sym.type is not None: if sym.type is not None:
copied = make_sym_copy_of_setting(sym) copied = make_sym_copy_of_setting(sym)
if copied is None: if copied is None:
continue continue
ctx.cls.info.names[name] = copied settings_classdef.info.names[name] = copied
# else:
# TODO: figure out suggestion to add type annotation
# context = Context()
# module, node_name = sym.node.fullname().rsplit('.', 1)
# module_file = api.modules.get(module)
# if module_file is None:
# return None
# context.set_line(sym.node)
# api.msg.report(f"Need type annotation for '{sym.node.name()}'", context,
# severity='error', file=module_file.path)
ctx.cls.info.fallback_to_any = True
class DjangoConfSettingsInitializerHook(object): class AddSettingValuesToDjangoConfObject:
def __init__(self, settings_module: Optional[str]): def __init__(self, settings_modules: List[str]):
self.settings_module = settings_module self.settings_modules = settings_modules
def __call__(self, ctx: ClassDefContext) -> None: def __call__(self, ctx: ClassDefContext) -> None:
if not self.settings_module: api = cast(SemanticAnalyzerPass2, ctx.api)
return for module_name in self.settings_modules:
module = api.modules[module_name]
add_settings_to_django_conf_object(ctx, self.settings_module) load_settings_from_module(ctx.cls, module=module)

View File

@@ -32,30 +32,13 @@ from pathlib import Path
ROOT_DIR = Path(__file__) ROOT_DIR = Path(__file__)
[CASE test_circular_dependency_in_settings] [CASE global_settings_are_always_loaded]
from django.conf import settings from django.conf import settings
class Class:
pass reveal_type(settings.AUTH_USER_MODEL) # E: Revealed type is 'builtins.str'
reveal_type(settings.REGISTRY) # E: Revealed type is 'Union[main.Class, None]' reveal_type(settings.AUTHENTICATION_BACKENDS) # E: Revealed type is 'builtins.list[builtins.str]'
reveal_type(settings.LIST) # E: Revealed type is 'Any'
reveal_type(settings.BASE_LIST) # E: Revealed type is 'Any'
[out] [out]
[env DJANGO_SETTINGS_MODULE=mysettings]
[file mysettings.py]
from typing import Optional
from base import *
LIST = ['1', '2']
[file base.py]
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from main import Class
REGISTRY: Optional['Class'] = None
BASE_LIST = ['3', '4']
[CASE test_circular_dependency_in_settings_works_if_settings_have_annotations] [CASE test_circular_dependency_in_settings_works_if_settings_have_annotations]
from django.conf import settings from django.conf import settings
class Class: class Class:
@@ -76,10 +59,12 @@ MYSETTING = 1122
REGISTRY: Optional['Class'] = None REGISTRY: Optional['Class'] = None
LIST: List[str] = ['1', '2'] LIST: List[str] = ['1', '2']
[CASE allow_calls_to_nonexistent_members_for_now] [CASE fail_if_there_is_no_setting]
from django.conf import settings from django.conf import settings
reveal_type(settings.NOT_EXISTING) # E: Revealed type is 'Any' reveal_type(settings.NOT_EXISTING)
[env DJANGO_SETTINGS_MODULE=mysettings] [env DJANGO_SETTINGS_MODULE=mysettings]
[file mysettings.py] [file mysettings.py]
[out] [out]
main:2: error: Revealed type is 'Any'
main:2: error: "LazySettings" has no attribute "NOT_EXISTING"