From 708f4deca8cbcf481f579548cbb87d0e7bcc4396 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Tue, 6 Dec 2022 13:27:11 +1300 Subject: [PATCH 1/9] Revert "Revert "Move all the complex settings types to a module"" This reverts commit 9ef2e9220e2164af9ed822d4fd43b659b7bebb21. --- README.md | 5 +- pydantic_settings/settings.py | 359 ++++++++++++++-------------------- pydantic_settings/types.py | 46 +++++ 3 files changed, 197 insertions(+), 213 deletions(-) create mode 100644 pydantic_settings/types.py diff --git a/README.md b/README.md index e1770a7..8fb388c 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,11 @@ Google Cloud SQL database connections from within Google Cloud Run are supported Alternatively you can set all your databases at once, by using the `DATABASES` setting (either in a `PydanticSettings` sub-class or via the `DJANGO_DATABASES` environment variable: ```python +from pydantic_settings import PydanticSettings, types + + def MySettings(PydanticSettings): - DATABASES = {"default": "sqlite:///db.sqlite3"} # type: ignore + DATABASES: types.DATABASES = {"default": "sqlite:///db.sqlite3"} # type: ignore ``` It is also possible to configure additional database connections with environment variables in the same way as the default `DATABASE_URL` configuration by using a `Field` that has a `configure_database` argument that points to the database alias in the `DATABASES` dictionary. diff --git a/pydantic_settings/settings.py b/pydantic_settings/settings.py index e8a94e5..b04a24a 100644 --- a/pydantic_settings/settings.py +++ b/pydantic_settings/settings.py @@ -1,17 +1,6 @@ import inspect from pathlib import Path -from typing import ( - Any, - Callable, - Dict, - Iterable, - List, - Optional, - Pattern, - Sequence, - Tuple, - Union, -) +from typing import Any, Dict, Iterable, List, Optional, Tuple from django.conf import global_settings, settings from django.core.management.utils import get_random_secret_key @@ -25,18 +14,11 @@ validator, ) from pydantic.fields import ModelField -from pydantic.networks import EmailStr, IPvAnyAddress from pydantic.types import FilePath +from pydantic_settings import types from pydantic_settings.cache import CacheDsn from pydantic_settings.database import DatabaseDsn -from pydantic_settings.models import CacheModel, DatabaseModel, TemplateBackendModel - -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal - DEFAULT_SETTINGS_MODULE_FIELD = Field( "pydantic_settings.settings.PydanticSettings", env="DJANGO_SETTINGS_MODULE" @@ -79,251 +61,204 @@ def _get_default_setting(setting: str) -> Any: class PydanticSettings(BaseSettings): - BASE_DIR: Optional[DirectoryPath] = None + BASE_DIR: types.BASE_DIR = None - DEBUG: Optional[bool] = global_settings.DEBUG - DEBUG_PROPAGATE_EXCEPTIONS: Optional[ - bool - ] = global_settings.DEBUG_PROPAGATE_EXCEPTIONS - ADMINS: Optional[List[Tuple[str, EmailStr]]] = _get_default_setting("ADMIN") - INTERNAL_IPS: Optional[List[IPvAnyAddress]] = _get_default_setting("INTERNAL_IPS") + DEBUG: bool = global_settings.DEBUG + DEBUG_PROPAGATE_EXCEPTIONS: bool = global_settings.DEBUG_PROPAGATE_EXCEPTIONS + ADMINS: types.ADMINS = _get_default_setting("ADMIN") + INTERNAL_IPS: types.INTERNAL_IPS = _get_default_setting("INTERNAL_IPS") # Would be nice to do something like Union[Literal["*"], IPvAnyAddress, AnyUrl], but # there are a lot of different options that need to be valid and don't necessarily # fit those types. - ALLOWED_HOSTS: Optional[List[str]] = global_settings.ALLOWED_HOSTS + ALLOWED_HOSTS: List[str] = global_settings.ALLOWED_HOSTS # Validate against actual list of valid TZs? # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List - TIME_ZONE: Optional[str] = global_settings.TIME_ZONE - USE_TZ: Optional[bool] + TIME_ZONE: str = global_settings.TIME_ZONE + USE_TZ: bool = global_settings.USE_TZ # Validate LANGUAGE_CODE and LANGUAGES_BIDI against LANGUAGES. - LANGUAGE_CODE: Optional[str] = global_settings.LANGUAGE_CODE - LANGUAGES: Optional[List[Tuple[str, str]]] = global_settings.LANGUAGES - LANGUAGES_BIDI: Optional[List[str]] = global_settings.LANGUAGES_BIDI + LANGUAGE_CODE: str = global_settings.LANGUAGE_CODE + LANGUAGES: types.LANGUAGES = global_settings.LANGUAGES + LANGUAGES_BIDI: List[str] = global_settings.LANGUAGES_BIDI - USE_I18N: Optional[bool] = global_settings.USE_I18N - LOCALE_PATHS: Optional[List[DirectoryPath]] = _get_default_setting("LOCALE_PATHS") - LANGUAGE_COOKIE_NAME: Optional[str] = global_settings.LANGUAGE_COOKIE_NAME + USE_I18N: bool = global_settings.USE_I18N + LOCALE_PATHS: types.LOCALE_PATHS = _get_default_setting("LOCALE_PATHS") + LANGUAGE_COOKIE_NAME: str = global_settings.LANGUAGE_COOKIE_NAME LANGUAGE_COOKIE_AGE: Optional[int] = global_settings.LANGUAGE_COOKIE_AGE LANGUAGE_COOKIE_DOMAIN: Optional[str] = global_settings.LANGUAGE_COOKIE_DOMAIN - LANGUAGE_COOKIE_PATH: Optional[str] = global_settings.LANGUAGE_COOKIE_PATH + LANGUAGE_COOKIE_PATH: str = global_settings.LANGUAGE_COOKIE_PATH LANGUAGE_COOKIE_SECURE: Optional[bool] = _get_default_setting( "LANGUAGE_COOKIE_SECURE" ) LANGUAGE_COOKIE_HTTPONLY: Optional[bool] = _get_default_setting( "LANGUAGE_COOKIE_HTTPONLY" ) - LANGUAGE_COOKIE_SAMESITE: Optional[ - Literal["Lax", "Strict", "None"] - ] = _get_default_setting("LANGUAGE_COOKIE_SAMESITE") - USE_L10N: Optional[bool] = global_settings.USE_L10N - MANAGERS: Optional[List[Tuple[str, EmailStr]]] = _get_default_setting("MANAGERS") - DEFAULT_CHARSET: Optional[str] = global_settings.DEFAULT_CHARSET - SERVER_EMAIL: Optional[ - Union[EmailStr, Literal["root@localhost"]] - ] = global_settings.SERVER_EMAIL # type: ignore - - DATABASES: Dict[str, DatabaseModel] = global_settings.DATABASES # type: ignore - DATABASE_ROUTERS: Optional[ - List[str] - ] = global_settings.DATABASE_ROUTERS # type: ignore - EMAIL_BACKEND: Optional[str] = global_settings.EMAIL_BACKEND - EMAIL_HOST: Optional[str] = global_settings.EMAIL_HOST - EMAIL_PORT: Optional[int] = global_settings.EMAIL_PORT - EMAIL_USE_LOCALTIME: Optional[bool] = global_settings.EMAIL_USE_LOCALTIME - EMAIL_HOST_USER: Optional[str] = global_settings.EMAIL_HOST_USER - EMAIL_HOST_PASSWORD: Optional[str] = global_settings.EMAIL_HOST_PASSWORD - EMAIL_USE_TLS: Optional[bool] = global_settings.EMAIL_USE_TLS - EMAIL_USE_SSL: Optional[bool] = global_settings.EMAIL_USE_SSL - EMAIL_SSL_CERTFILE: Optional[ - FilePath - ] = global_settings.EMAIL_SSL_CERTFILE # type: ignore - EMAIL_SSL_KEYFILE: Optional[ - FilePath - ] = global_settings.EMAIL_SSL_KEYFILE # type: ignore + LANGUAGE_COOKIE_SAMESITE: types.LANGUAGE_COOKIE_SAMESITE = _get_default_setting( + "LANGUAGE_COOKIE_SAMESITE" + ) + USE_L10N: bool = global_settings.USE_L10N + MANAGERS: types.MANAGERS = _get_default_setting("MANAGERS") + DEFAULT_CHARSET: str = global_settings.DEFAULT_CHARSET + SERVER_EMAIL: types.SERVER_EMAIL = global_settings.SERVER_EMAIL + + DATABASES: types.DATABASES = global_settings.DATABASES # type: ignore + DATABASE_ROUTERS: List[str] = global_settings.DATABASE_ROUTERS + EMAIL_BACKEND: str = global_settings.EMAIL_BACKEND + EMAIL_HOST: str = global_settings.EMAIL_HOST + EMAIL_PORT: int = global_settings.EMAIL_PORT + EMAIL_USE_LOCALTIME: bool = global_settings.EMAIL_USE_LOCALTIME + EMAIL_HOST_USER: str = global_settings.EMAIL_HOST_USER + EMAIL_HOST_PASSWORD: str = global_settings.EMAIL_HOST_PASSWORD + EMAIL_USE_TLS: bool = global_settings.EMAIL_USE_TLS + EMAIL_USE_SSL: bool = global_settings.EMAIL_USE_SSL + EMAIL_SSL_CERTFILE: Optional[FilePath] = global_settings.EMAIL_SSL_CERTFILE + EMAIL_SSL_KEYFILE: Optional[FilePath] = global_settings.EMAIL_SSL_KEYFILE EMAIL_TIMEOUT: Optional[int] = global_settings.EMAIL_TIMEOUT - INSTALLED_APPS: Optional[List[str]] = global_settings.INSTALLED_APPS - TEMPLATES: Optional[ - List[TemplateBackendModel] - ] = global_settings.TEMPLATES # type: ignore - FORM_RENDERER: Optional[str] = global_settings.FORM_RENDERER - DEFAULT_FROM_EMAIL: Optional[str] = global_settings.DEFAULT_FROM_EMAIL - EMAIL_SUBJECT_PREFIX: Optional[str] = global_settings.EMAIL_SUBJECT_PREFIX - APPEND_SLASH: Optional[bool] = global_settings.APPEND_SLASH - PREPEND_WWW: Optional[bool] = global_settings.PREPEND_WWW + INSTALLED_APPS: List[str] = global_settings.INSTALLED_APPS + TEMPLATES: types.TEMPLATES = global_settings.TEMPLATES + FORM_RENDERER: str = global_settings.FORM_RENDERER + DEFAULT_FROM_EMAIL: str = global_settings.DEFAULT_FROM_EMAIL + EMAIL_SUBJECT_PREFIX: str = global_settings.EMAIL_SUBJECT_PREFIX + APPEND_SLASH: bool = global_settings.APPEND_SLASH + PREPEND_WWW: bool = global_settings.PREPEND_WWW FORCE_SCRIPT_NAME: Optional[str] = global_settings.FORCE_SCRIPT_NAME - DISALLOWED_USER_AGENTS: Optional[ - List[Pattern] - ] = global_settings.DISALLOWED_USER_AGENTS - ABSOLUTE_URL_OVERRIDES: Optional[ - Dict[str, Callable] - ] = global_settings.ABSOLUTE_URL_OVERRIDES - IGNORABLE_404_URLS: Optional[List[Pattern]] = global_settings.IGNORABLE_404_URLS + DISALLOWED_USER_AGENTS: types.DISALLOWED_USER_AGENTS = ( + global_settings.DISALLOWED_USER_AGENTS + ) + ABSOLUTE_URL_OVERRIDES: types.ABSOLUTE_URL_OVERRIDES = ( + global_settings.ABSOLUTE_URL_OVERRIDES + ) + IGNORABLE_404_URLS: types.IGNORABLE_404_URLS = global_settings.IGNORABLE_404_URLS SECRET_KEY: str = Field(default_factory=get_random_secret_key) - DEFAULT_FILE_STORAGE: Optional[str] = global_settings.DEFAULT_FILE_STORAGE - MEDIA_ROOT: Optional[str] = global_settings.MEDIA_ROOT - MEDIA_URL: Optional[str] = global_settings.MEDIA_URL - STATIC_ROOT: Optional[DirectoryPath] = global_settings.STATIC_ROOT # type: ignore + DEFAULT_FILE_STORAGE: str = global_settings.DEFAULT_FILE_STORAGE + MEDIA_ROOT: str = global_settings.MEDIA_ROOT + MEDIA_URL: str = global_settings.MEDIA_URL + STATIC_ROOT: Optional[DirectoryPath] = global_settings.STATIC_ROOT STATIC_URL: Optional[str] = global_settings.STATIC_URL - FILE_UPLOAD_HANDLERS: Optional[List[str]] = global_settings.FILE_UPLOAD_HANDLERS - FILE_UPLOAD_MAX_MEMORY_SIZE: Optional[ - int - ] = global_settings.FILE_UPLOAD_MAX_MEMORY_SIZE - DATA_UPLOAD_MAX_MEMORY_SIZE: Optional[ - int - ] = global_settings.DATA_UPLOAD_MAX_MEMORY_SIZE + FILE_UPLOAD_HANDLERS: List[str] = global_settings.FILE_UPLOAD_HANDLERS + FILE_UPLOAD_MAX_MEMORY_SIZE: int = global_settings.FILE_UPLOAD_MAX_MEMORY_SIZE + DATA_UPLOAD_MAX_MEMORY_SIZE: int = global_settings.DATA_UPLOAD_MAX_MEMORY_SIZE DATA_UPLOAD_MAX_NUMBER_FIELDS: Optional[ int ] = global_settings.DATA_UPLOAD_MAX_NUMBER_FIELDS - FILE_UPLOAD_TEMP_DIR: Optional[ - DirectoryPath - ] = global_settings.FILE_UPLOAD_TEMP_DIR # type: ignore + FILE_UPLOAD_TEMP_DIR: types.FILE_UPLOAD_TEMP_DIR = ( + global_settings.FILE_UPLOAD_TEMP_DIR + ) FILE_UPLOAD_PERMISSIONS: Optional[int] = global_settings.FILE_UPLOAD_PERMISSIONS FILE_UPLOAD_DIRECTORY_PERMISSIONS: Optional[ int ] = global_settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS FORMAT_MODULE_PATH: Optional[str] = global_settings.FORMAT_MODULE_PATH - DATE_FORMAT: Optional[str] = global_settings.DATE_FORMAT - DATETIME_FORMAT: Optional[str] = global_settings.DATETIME_FORMAT - TIME_FORMAT: Optional[str] = global_settings.TIME_FORMAT - YEAR_MONTH_FORMAT: Optional[str] = global_settings.YEAR_MONTH_FORMAT - MONTH_DAY_FORMAT: Optional[str] = global_settings.MONTH_DAY_FORMAT - SHORT_DATE_FORMAT: Optional[str] = global_settings.SHORT_DATE_FORMAT - SHORT_DATETIME_FORMAT: Optional[str] = global_settings.SHORT_DATETIME_FORMAT - DATE_INPUT_FORMATS: Optional[List[str]] = global_settings.DATE_INPUT_FORMATS - TIME_INPUT_FORMATS: Optional[List[str]] = global_settings.TIME_INPUT_FORMATS - DATETIME_INPUT_FORMATS: Optional[List[str]] = global_settings.DATETIME_INPUT_FORMATS - FIRST_DAY_OF_WEEK: Optional[int] = global_settings.FIRST_DAY_OF_WEEK - DECIMAL_SEPARATOR: Optional[str] = global_settings.DECIMAL_SEPARATOR - USE_THOUSAND_SEPARATOR: Optional[bool] = global_settings.USE_THOUSAND_SEPARATOR - THOUSAND_SEPARATOR: Optional[str] = global_settings.THOUSAND_SEPARATOR - DEFAULT_TABLESPACE: Optional[str] = global_settings.DEFAULT_TABLESPACE - DEFAULT_INDEX_TABLESPACE: Optional[str] = global_settings.DEFAULT_INDEX_TABLESPACE - X_FRAME_OPTIONS: Optional[str] = global_settings.X_FRAME_OPTIONS - USE_X_FORWARDED_HOST: Optional[bool] = global_settings.USE_X_FORWARDED_HOST - USE_X_FORWARDED_PORT: Optional[bool] = global_settings.USE_X_FORWARDED_PORT + DATE_FORMAT: str = global_settings.DATE_FORMAT + DATETIME_FORMAT: str = global_settings.DATETIME_FORMAT + TIME_FORMAT: str = global_settings.TIME_FORMAT + YEAR_MONTH_FORMAT: str = global_settings.YEAR_MONTH_FORMAT + MONTH_DAY_FORMAT: str = global_settings.MONTH_DAY_FORMAT + SHORT_DATE_FORMAT: str = global_settings.SHORT_DATE_FORMAT + SHORT_DATETIME_FORMAT: str = global_settings.SHORT_DATETIME_FORMAT + DATE_INPUT_FORMATS: List[str] = global_settings.DATE_INPUT_FORMATS + TIME_INPUT_FORMATS: List[str] = global_settings.TIME_INPUT_FORMATS + DATETIME_INPUT_FORMATS: List[str] = global_settings.DATETIME_INPUT_FORMATS + FIRST_DAY_OF_WEEK: int = global_settings.FIRST_DAY_OF_WEEK + DECIMAL_SEPARATOR: str = global_settings.DECIMAL_SEPARATOR + USE_THOUSAND_SEPARATOR: bool = global_settings.USE_THOUSAND_SEPARATOR + THOUSAND_SEPARATOR: str = global_settings.THOUSAND_SEPARATOR + DEFAULT_TABLESPACE: str = global_settings.DEFAULT_TABLESPACE + DEFAULT_INDEX_TABLESPACE: str = global_settings.DEFAULT_INDEX_TABLESPACE + X_FRAME_OPTIONS: str = global_settings.X_FRAME_OPTIONS + USE_X_FORWARDED_HOST: bool = global_settings.USE_X_FORWARDED_HOST + USE_X_FORWARDED_PORT: bool = global_settings.USE_X_FORWARDED_PORT WSGI_APPLICATION: Optional[str] = None - SECURE_PROXY_SSL_HEADER: Optional[ - Tuple[str, str] - ] = global_settings.SECURE_PROXY_SSL_HEADER - DEFAULT_HASHING_ALGORITHM: Optional[ - Literal["sha1", "sha256"] - ] = _get_default_setting("DEFAULT_HASHING_ALGORITHM") - MIDDLEWARE: Optional[List[str]] = global_settings.MIDDLEWARE - SESSION_CACHE_ALIAS: Optional[str] = global_settings.SESSION_CACHE_ALIAS - SESSION_COOKIE_NAME: Optional[str] = global_settings.SESSION_COOKIE_NAME + SECURE_PROXY_SSL_HEADER: types.SECURE_PROXY_SSL_HEADER = ( + global_settings.SECURE_PROXY_SSL_HEADER + ) + DEFAULT_HASHING_ALGORITHM: types.DEFAULT_HASHING_ALGORITHM = _get_default_setting( + "DEFAULT_HASHING_ALGORITHM" + ) + MIDDLEWARE: List[str] = global_settings.MIDDLEWARE + SESSION_CACHE_ALIAS: str = global_settings.SESSION_CACHE_ALIAS + SESSION_COOKIE_NAME: str = global_settings.SESSION_COOKIE_NAME SESSION_COOKIE_AGE: Optional[int] = global_settings.SESSION_COOKIE_AGE SESSION_COOKIE_DOMAIN: Optional[str] = global_settings.SESSION_COOKIE_DOMAIN - SESSION_COOKIE_SECURE: Optional[bool] = global_settings.SESSION_COOKIE_SECURE - SESSION_COOKIE_PATH: Optional[str] = global_settings.SESSION_COOKIE_PATH - SESSION_COOKIE_HTTPONLY: Optional[bool] = global_settings.SESSION_COOKIE_HTTPONLY - SESSION_COOKIE_SAMESITE: Optional[ - Literal["Lax", "Strict", "None"] - ] = _get_default_setting( + SESSION_COOKIE_SECURE: bool = global_settings.SESSION_COOKIE_SECURE + SESSION_COOKIE_PATH: str = global_settings.SESSION_COOKIE_PATH + SESSION_COOKIE_HTTPONLY: bool = global_settings.SESSION_COOKIE_HTTPONLY + SESSION_COOKIE_SAMESITE: types.SESSION_COOKIE_SAMESITE = _get_default_setting( "SESSION_COOKIE_SAMESITE" - ) # type: ignore - SESSION_SAVE_EVERY_REQUEST: Optional[ - bool - ] = global_settings.SESSION_SAVE_EVERY_REQUEST - SESSION_EXPIRE_AT_BROWSER_CLOSE: Optional[ - bool - ] = global_settings.SESSION_EXPIRE_AT_BROWSER_CLOSE - SESSION_ENGINE: Optional[str] = global_settings.SESSION_ENGINE - SESSION_FILE_PATH: Optional[ - DirectoryPath - ] = global_settings.SESSION_FILE_PATH # type: ignore - SESSION_SERIALIZER: Optional[str] = global_settings.SESSION_SERIALIZER - CACHES: Dict[str, CacheModel] = global_settings.CACHES # type: ignore - CACHE_MIDDLEWARE_KEY_PREFIX: Optional[ - str - ] = global_settings.CACHE_MIDDLEWARE_KEY_PREFIX + ) + SESSION_SAVE_EVERY_REQUEST: bool = global_settings.SESSION_SAVE_EVERY_REQUEST + SESSION_EXPIRE_AT_BROWSER_CLOSE: bool = ( + global_settings.SESSION_EXPIRE_AT_BROWSER_CLOSE + ) + SESSION_ENGINE: str = global_settings.SESSION_ENGINE + SESSION_FILE_PATH: types.SESSION_FILE_PATH = global_settings.SESSION_FILE_PATH + SESSION_SERIALIZER: str = global_settings.SESSION_SERIALIZER + CACHES: types.CACHES = global_settings.CACHES # type: ignore + CACHE_MIDDLEWARE_KEY_PREFIX: str = global_settings.CACHE_MIDDLEWARE_KEY_PREFIX CACHE_MIDDLEWARE_SECONDS: Optional[int] = global_settings.CACHE_MIDDLEWARE_SECONDS - CACHE_MIDDLEWARE_ALIAS: Optional[str] = global_settings.CACHE_MIDDLEWARE_ALIAS - AUTH_USER_MODEL: Optional[str] = global_settings.AUTH_USER_MODEL - AUTHENTICATION_BACKENDS: Optional[ - Sequence[str] - ] = global_settings.AUTHENTICATION_BACKENDS - LOGIN_URL: Optional[str] = global_settings.LOGIN_URL - LOGIN_REDIRECT_URL: Optional[str] = global_settings.LOGIN_REDIRECT_URL + CACHE_MIDDLEWARE_ALIAS: str = global_settings.CACHE_MIDDLEWARE_ALIAS + AUTH_USER_MODEL: str = global_settings.AUTH_USER_MODEL + AUTHENTICATION_BACKENDS: List[str] = global_settings.AUTHENTICATION_BACKENDS + LOGIN_URL: str = global_settings.LOGIN_URL + LOGIN_REDIRECT_URL: str = global_settings.LOGIN_REDIRECT_URL PASSWORD_RESET_TIMEOUT_DAYS: Optional[int] = _get_default_setting( "PASSWORD_RESET_TIMEOUT_DAYS" ) PASSWORD_RESET_TIMEOUT: Optional[int] = _get_default_setting( "PASSWORD_RESET_TIMEOUT" ) - PASSWORD_HASHERS: Optional[List[str]] = global_settings.PASSWORD_HASHERS - AUTH_PASSWORD_VALIDATORS: Optional[ - List[dict] - ] = global_settings.AUTH_PASSWORD_VALIDATORS - SIGNING_BACKEND: Optional[str] = global_settings.SIGNING_BACKEND - CSRF_FAILURE_VIEW: Optional[str] = global_settings.CSRF_FAILURE_VIEW - CSRF_COOKIE_NAME: Optional[str] = global_settings.CSRF_COOKIE_NAME + PASSWORD_HASHERS: List[str] = global_settings.PASSWORD_HASHERS + AUTH_PASSWORD_VALIDATORS: List[dict] = global_settings.AUTH_PASSWORD_VALIDATORS + SIGNING_BACKEND: str = global_settings.SIGNING_BACKEND + CSRF_FAILURE_VIEW: str = global_settings.CSRF_FAILURE_VIEW + CSRF_COOKIE_NAME: str = global_settings.CSRF_COOKIE_NAME CSRF_COOKIE_AGE: Optional[int] = global_settings.CSRF_COOKIE_AGE CSRF_COOKIE_DOMAIN: Optional[str] = global_settings.CSRF_COOKIE_DOMAIN - CSRF_COOKIE_PATH: Optional[str] = global_settings.CSRF_COOKIE_PATH - CSRF_COOKIE_SECURE: Optional[bool] = global_settings.CSRF_COOKIE_SECURE - CSRF_COOKIE_HTTPONLY: Optional[bool] = global_settings.CSRF_COOKIE_HTTPONLY - CSRF_COOKIE_SAMESITE: Optional[ - Literal["Lax", "Strict", "None"] - ] = _get_default_setting( + CSRF_COOKIE_PATH: str = global_settings.CSRF_COOKIE_PATH + CSRF_COOKIE_SECURE: bool = global_settings.CSRF_COOKIE_SECURE + CSRF_COOKIE_HTTPONLY: bool = global_settings.CSRF_COOKIE_HTTPONLY + CSRF_COOKIE_SAMESITE: types.CSRF_COOKIE_SAMESITE = _get_default_setting( "CSRF_COOKIE_SAMESITE" - ) # type: ignore - CSRF_HEADER_NAME: Optional[str] = global_settings.CSRF_HEADER_NAME - CSRF_TRUSTED_ORIGINS: Optional[List[str]] = global_settings.CSRF_TRUSTED_ORIGINS - CSRF_USE_SESSIONS: Optional[bool] = global_settings.CSRF_USE_SESSIONS - MESSAGE_STORAGE: Optional[str] = global_settings.MESSAGE_STORAGE - LOGGING_CONFIG: Optional[str] = global_settings.LOGGING_CONFIG - LOGGING: Optional[dict] = global_settings.LOGGING + ) + CSRF_HEADER_NAME: str = global_settings.CSRF_HEADER_NAME + CSRF_TRUSTED_ORIGINS: List[str] = global_settings.CSRF_TRUSTED_ORIGINS + CSRF_USE_SESSIONS: bool = global_settings.CSRF_USE_SESSIONS + MESSAGE_STORAGE: str = global_settings.MESSAGE_STORAGE + LOGGING_CONFIG: str = global_settings.LOGGING_CONFIG + LOGGING: dict = global_settings.LOGGING DEFAULT_EXCEPTION_REPORTER: Optional[str] = _get_default_setting( "DEFAULT_EXCEPTION_REPORTER" ) - DEFAULT_EXCEPTION_REPORTER_FILTER: Optional[ - str - ] = global_settings.DEFAULT_EXCEPTION_REPORTER_FILTER - TEST_RUNNER: Optional[str] = global_settings.TEST_RUNNER - TEST_NON_SERIALIZED_APPS: Optional[ - List[str] - ] = global_settings.TEST_NON_SERIALIZED_APPS - FIXTURE_DIRS: Optional[ - List[DirectoryPath] - ] = global_settings.FIXTURE_DIRS # type: ignore - STATICFILES_DIRS: Optional[ - List[DirectoryPath] - ] = global_settings.STATICFILES_DIRS # type: ignore - STATICFILES_STORAGE: Optional[str] = global_settings.STATICFILES_STORAGE - STATICFILES_FINDERS: Optional[List[str]] = global_settings.STATICFILES_FINDERS - MIGRATION_MODULES: Optional[Dict[str, str]] = global_settings.MIGRATION_MODULES - SILENCED_SYSTEM_CHECKS: Optional[List[str]] = global_settings.SILENCED_SYSTEM_CHECKS + DEFAULT_EXCEPTION_REPORTER_FILTER: str = ( + global_settings.DEFAULT_EXCEPTION_REPORTER_FILTER + ) + TEST_RUNNER: str = global_settings.TEST_RUNNER + TEST_NON_SERIALIZED_APPS: List[str] = global_settings.TEST_NON_SERIALIZED_APPS + FIXTURE_DIRS: types.FIXTURE_DIRS = global_settings.FIXTURE_DIRS + STATICFILES_DIRS: types.STATICFILES_DIRS = global_settings.STATICFILES_DIRS + STATICFILES_STORAGE: str = global_settings.STATICFILES_STORAGE + STATICFILES_FINDERS: List[str] = global_settings.STATICFILES_FINDERS + MIGRATION_MODULES: Dict[str, str] = global_settings.MIGRATION_MODULES + SILENCED_SYSTEM_CHECKS: List[str] = global_settings.SILENCED_SYSTEM_CHECKS SECURE_BROWSER_XSS_FILTER: Optional[bool] = _get_default_setting( "SECURE_BROWSER_XSS_FILTER" ) - SECURE_CONTENT_TYPE_NOSNIFF: Optional[ - bool - ] = global_settings.SECURE_CONTENT_TYPE_NOSNIFF - SECURE_HSTS_INCLUDE_SUBDOMAINS: Optional[ - bool - ] = global_settings.SECURE_HSTS_INCLUDE_SUBDOMAINS - SECURE_HSTS_PRELOAD: Optional[bool] = global_settings.SECURE_HSTS_PRELOAD + SECURE_CONTENT_TYPE_NOSNIFF: bool = global_settings.SECURE_CONTENT_TYPE_NOSNIFF + SECURE_HSTS_INCLUDE_SUBDOMAINS: bool = ( + global_settings.SECURE_HSTS_INCLUDE_SUBDOMAINS + ) + SECURE_HSTS_PRELOAD: bool = global_settings.SECURE_HSTS_PRELOAD SECURE_HSTS_SECONDS: Optional[int] = global_settings.SECURE_HSTS_SECONDS - SECURE_REDIRECT_EXEMPT: Optional[ - List[Pattern] - ] = global_settings.SECURE_REDIRECT_EXEMPT # type: ignore - SECURE_REFERRER_POLICY: Optional[ - Literal[ - "no-referrer", - "no-referrer-when-downgrade", - "origin", - "origin-when-cross-origin", - "same-origin", - "strict-origin", - "strict-origin-when-cross-origin", - "unsafe-url", - ] - ] = _get_default_setting("SECURE_REFERRER_POLICY") + SECURE_REDIRECT_EXEMPT: types.SECURE_REDIRECT_EXEMPT = ( + global_settings.SECURE_REDIRECT_EXEMPT + ) + SECURE_REFERRER_POLICY: types.SECURE_REFERRER_POLICY = _get_default_setting( + "SECURE_REFERRER_POLICY" + ) SECURE_SSL_HOST: Optional[str] = global_settings.SECURE_SSL_HOST - SECURE_SSL_REDIRECT: Optional[bool] = global_settings.SECURE_SSL_REDIRECT + SECURE_SSL_REDIRECT: bool = global_settings.SECURE_SSL_REDIRECT ROOT_URLCONF: Optional[str] = None diff --git a/pydantic_settings/types.py b/pydantic_settings/types.py new file mode 100644 index 0000000..744ac8e --- /dev/null +++ b/pydantic_settings/types.py @@ -0,0 +1,46 @@ +from re import Pattern +from typing import Callable, Dict, List, Optional, Tuple, Union + +from pydantic import DirectoryPath, EmailStr, IPvAnyAddress +from typing_extensions import Literal + +from pydantic_settings.models import CacheModel, DatabaseModel, TemplateBackendModel + +EmailList = List[Tuple[str, EmailStr]] +ReferrerOptions = Literal[ + "no-referrer", + "no-referrer-when-downgrade", + "origin", + "origin-when-cross-origin", + "same-origin", + "strict-origin", + "strict-origin-when-cross-origin", + "unsafe-url", +] +RegexList = List[Pattern] +SameSiteOptions = Literal["Lax", "Strict", "None"] + +ABSOLUTE_URL_OVERRIDES = Dict[str, Callable] +ADMINS = Optional[EmailList] +BASE_DIR = Optional[DirectoryPath] +CACHES = Dict[str, CacheModel] +CSRF_COOKIE_SAMESITE = Optional[SameSiteOptions] +DATABASES = Dict[str, DatabaseModel] +DEFAULT_HASHING_ALGORITHM = Optional[Literal["sha1", "sha256"]] +DISALLOWED_USER_AGENTS = RegexList +FILE_UPLOAD_TEMP_DIR = Optional[DirectoryPath] +FIXTURE_DIRS = List[DirectoryPath] +IGNORABLE_404_URLS = RegexList +INTERNAL_IPS = Optional[List[IPvAnyAddress]] +LANGUAGE_COOKIE_SAMESITE = Optional[SameSiteOptions] +LANGUAGES = List[Tuple[str, str]] +LOCALE_PATHS = List[DirectoryPath] +MANAGERS = Optional[EmailList] +SECURE_PROXY_SSL_HEADER = Optional[Tuple[str, str]] +SECURE_REDIRECT_EXEMPT = RegexList +SECURE_REFERRER_POLICY = Optional[ReferrerOptions] +SERVER_EMAIL = Union[EmailStr, Literal["root@localhost"]] +SESSION_COOKIE_SAMESITE = Optional[SameSiteOptions] +SESSION_FILE_PATH = Optional[DirectoryPath] +STATICFILES_DIRS = List[DirectoryPath] +TEMPLATES = List[TemplateBackendModel] From 9b7894063c6f9cc7ddabf880e7243c35fb8a8926 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Tue, 6 Dec 2022 13:28:53 +1300 Subject: [PATCH 2/9] Fix import --- pydantic_settings/default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic_settings/default.py b/pydantic_settings/default.py index a4a3238..88b14a4 100644 --- a/pydantic_settings/default.py +++ b/pydantic_settings/default.py @@ -3,7 +3,7 @@ from pydantic import root_validator from pydantic_settings.models import DatabaseModel, TemplateBackendModel -from pydantic_settings.settings import DatabaseModel, PydanticSettings +from pydantic_settings.settings import PydanticSettings class DjangoDefaultProjectSettings(PydanticSettings): From 56161db401a3ad64ec9ff875ead1211abd9dcf00 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Tue, 6 Dec 2022 13:53:07 +1300 Subject: [PATCH 3/9] Pre-commit code tidying --- .pre-commit-config.yaml | 21 +++++++ pydantic_settings/cache.py | 5 +- pydantic_settings/database.py | 11 ++-- pydantic_settings/default.py | 17 +++--- pydantic_settings/settings.py | 112 +++++++++++++++++----------------- pydantic_settings/types.py | 7 ++- tests/settings_proj/asgi.py | 1 - tests/test_env.py | 29 +++++---- 8 files changed, 116 insertions(+), 87 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..1541d29 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,21 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + # - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + + - repo: https://github.com/asottile/pyupgrade + rev: v3.3.0 + hooks: + - id: pyupgrade + args: [--py38-plus] + + - repo: https://github.com/pycqa/flake8 + rev: "6.0.0" + hooks: + - id: flake8 diff --git a/pydantic_settings/cache.py b/pydantic_settings/cache.py index e04aa08..aa2943b 100644 --- a/pydantic_settings/cache.py +++ b/pydantic_settings/cache.py @@ -1,5 +1,6 @@ +from __future__ import annotations + import re -from typing import Dict from urllib.parse import parse_qs from django import VERSION @@ -47,7 +48,7 @@ class CacheDsn(AnyUrl): __slots__ = AnyUrl.__slots__ + ("query_args",) host_required = False - query_args: Dict[str, str] + query_args: dict[str, str] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/pydantic_settings/database.py b/pydantic_settings/database.py index 6202ef3..ad55a88 100644 --- a/pydantic_settings/database.py +++ b/pydantic_settings/database.py @@ -1,6 +1,8 @@ +from __future__ import annotations + import re import urllib.parse -from typing import Dict, Optional, Pattern, Tuple, cast +from typing import Pattern, cast from urllib.parse import quote_plus from pydantic import AnyUrl @@ -30,7 +32,8 @@ def cloud_sql_regex() -> Pattern[str]: global _cloud_sql_regex_cache if _cloud_sql_regex_cache is None: _cloud_sql_regex_cache = re.compile( - r"(?:(?P[a-z][a-z0-9+\-.]+)://)?" # scheme https://tools.ietf.org/html/rfc3986#appendix-A + # scheme https://tools.ietf.org/html/rfc3986#appendix-A + r"(?:(?P[a-z][a-z0-9+\-.]+)://)?" r"(?:(?P[^\s:/]*)(?::(?P[^\s/]*))?@)?" # user info r"(?P/[^\s?#]*)?", # path re.IGNORECASE, @@ -65,8 +68,8 @@ def validate(cls, value, field, config): @classmethod def validate_host( - cls, parts: Dict[str, str] - ) -> Tuple[Optional[str], Optional[str], str, bool]: + cls, parts: dict[str, str] + ) -> tuple[str | None, str | None, str, bool]: host = None for f in ("domain", "ipv4", "ipv6"): host = parts[f] diff --git a/pydantic_settings/default.py b/pydantic_settings/default.py index 88b14a4..2f9eb4a 100644 --- a/pydantic_settings/default.py +++ b/pydantic_settings/default.py @@ -1,4 +1,4 @@ -from typing import Dict, List +from __future__ import annotations from pydantic import root_validator @@ -12,9 +12,11 @@ class DjangoDefaultProjectSettings(PydanticSettings): generates for new projects. """ - DATABASES: Dict[str, DatabaseModel] = {"default": "sqlite:///db.sqlite3"} # type: ignore + DATABASES: dict[str, DatabaseModel] = { + "default": "sqlite:///db.sqlite3", # type: ignore + } - TEMPLATES: List[TemplateBackendModel] = [ + TEMPLATES: list[TemplateBackendModel] = [ TemplateBackendModel.parse_obj(data) for data in [ { @@ -33,7 +35,7 @@ class DjangoDefaultProjectSettings(PydanticSettings): ] ] - INSTALLED_APPS: List[str] = [ + INSTALLED_APPS: list[str] = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", @@ -42,7 +44,7 @@ class DjangoDefaultProjectSettings(PydanticSettings): "django.contrib.staticfiles", ] - MIDDLEWARE: List[str] = [ + MIDDLEWARE: list[str] = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", @@ -52,9 +54,10 @@ class DjangoDefaultProjectSettings(PydanticSettings): "django.middleware.clickjacking.XFrameOptionsMiddleware", ] - AUTH_PASSWORD_VALIDATORS: List[dict] = [ + AUTH_PASSWORD_VALIDATORS: list[dict] = [ { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + "NAME": "django.contrib.auth.password_validation" + ".UserAttributeSimilarityValidator", }, { "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", diff --git a/pydantic_settings/settings.py b/pydantic_settings/settings.py index b04a24a..de820e6 100644 --- a/pydantic_settings/settings.py +++ b/pydantic_settings/settings.py @@ -1,6 +1,8 @@ +from __future__ import annotations + import inspect from pathlib import Path -from typing import Any, Dict, Iterable, List, Optional, Tuple +from typing import Any, Iterable from django.conf import global_settings, settings from django.core.management.utils import get_random_secret_key @@ -71,7 +73,7 @@ class PydanticSettings(BaseSettings): # Would be nice to do something like Union[Literal["*"], IPvAnyAddress, AnyUrl], but # there are a lot of different options that need to be valid and don't necessarily # fit those types. - ALLOWED_HOSTS: List[str] = global_settings.ALLOWED_HOSTS + ALLOWED_HOSTS: list[str] = global_settings.ALLOWED_HOSTS # Validate against actual list of valid TZs? # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List @@ -81,18 +83,16 @@ class PydanticSettings(BaseSettings): # Validate LANGUAGE_CODE and LANGUAGES_BIDI against LANGUAGES. LANGUAGE_CODE: str = global_settings.LANGUAGE_CODE LANGUAGES: types.LANGUAGES = global_settings.LANGUAGES - LANGUAGES_BIDI: List[str] = global_settings.LANGUAGES_BIDI + LANGUAGES_BIDI: list[str] = global_settings.LANGUAGES_BIDI USE_I18N: bool = global_settings.USE_I18N LOCALE_PATHS: types.LOCALE_PATHS = _get_default_setting("LOCALE_PATHS") LANGUAGE_COOKIE_NAME: str = global_settings.LANGUAGE_COOKIE_NAME - LANGUAGE_COOKIE_AGE: Optional[int] = global_settings.LANGUAGE_COOKIE_AGE - LANGUAGE_COOKIE_DOMAIN: Optional[str] = global_settings.LANGUAGE_COOKIE_DOMAIN + LANGUAGE_COOKIE_AGE: int | None = global_settings.LANGUAGE_COOKIE_AGE + LANGUAGE_COOKIE_DOMAIN: str | None = global_settings.LANGUAGE_COOKIE_DOMAIN LANGUAGE_COOKIE_PATH: str = global_settings.LANGUAGE_COOKIE_PATH - LANGUAGE_COOKIE_SECURE: Optional[bool] = _get_default_setting( - "LANGUAGE_COOKIE_SECURE" - ) - LANGUAGE_COOKIE_HTTPONLY: Optional[bool] = _get_default_setting( + LANGUAGE_COOKIE_SECURE: bool | None = _get_default_setting("LANGUAGE_COOKIE_SECURE") + LANGUAGE_COOKIE_HTTPONLY: bool | None = _get_default_setting( "LANGUAGE_COOKIE_HTTPONLY" ) LANGUAGE_COOKIE_SAMESITE: types.LANGUAGE_COOKIE_SAMESITE = _get_default_setting( @@ -104,7 +104,7 @@ class PydanticSettings(BaseSettings): SERVER_EMAIL: types.SERVER_EMAIL = global_settings.SERVER_EMAIL DATABASES: types.DATABASES = global_settings.DATABASES # type: ignore - DATABASE_ROUTERS: List[str] = global_settings.DATABASE_ROUTERS + DATABASE_ROUTERS: list[str] = global_settings.DATABASE_ROUTERS EMAIL_BACKEND: str = global_settings.EMAIL_BACKEND EMAIL_HOST: str = global_settings.EMAIL_HOST EMAIL_PORT: int = global_settings.EMAIL_PORT @@ -113,17 +113,17 @@ class PydanticSettings(BaseSettings): EMAIL_HOST_PASSWORD: str = global_settings.EMAIL_HOST_PASSWORD EMAIL_USE_TLS: bool = global_settings.EMAIL_USE_TLS EMAIL_USE_SSL: bool = global_settings.EMAIL_USE_SSL - EMAIL_SSL_CERTFILE: Optional[FilePath] = global_settings.EMAIL_SSL_CERTFILE - EMAIL_SSL_KEYFILE: Optional[FilePath] = global_settings.EMAIL_SSL_KEYFILE - EMAIL_TIMEOUT: Optional[int] = global_settings.EMAIL_TIMEOUT - INSTALLED_APPS: List[str] = global_settings.INSTALLED_APPS + EMAIL_SSL_CERTFILE: FilePath | None = global_settings.EMAIL_SSL_CERTFILE + EMAIL_SSL_KEYFILE: FilePath | None = global_settings.EMAIL_SSL_KEYFILE + EMAIL_TIMEOUT: int | None = global_settings.EMAIL_TIMEOUT + INSTALLED_APPS: list[str] = global_settings.INSTALLED_APPS TEMPLATES: types.TEMPLATES = global_settings.TEMPLATES FORM_RENDERER: str = global_settings.FORM_RENDERER DEFAULT_FROM_EMAIL: str = global_settings.DEFAULT_FROM_EMAIL EMAIL_SUBJECT_PREFIX: str = global_settings.EMAIL_SUBJECT_PREFIX APPEND_SLASH: bool = global_settings.APPEND_SLASH PREPEND_WWW: bool = global_settings.PREPEND_WWW - FORCE_SCRIPT_NAME: Optional[str] = global_settings.FORCE_SCRIPT_NAME + FORCE_SCRIPT_NAME: str | None = global_settings.FORCE_SCRIPT_NAME DISALLOWED_USER_AGENTS: types.DISALLOWED_USER_AGENTS = ( global_settings.DISALLOWED_USER_AGENTS ) @@ -135,22 +135,22 @@ class PydanticSettings(BaseSettings): DEFAULT_FILE_STORAGE: str = global_settings.DEFAULT_FILE_STORAGE MEDIA_ROOT: str = global_settings.MEDIA_ROOT MEDIA_URL: str = global_settings.MEDIA_URL - STATIC_ROOT: Optional[DirectoryPath] = global_settings.STATIC_ROOT - STATIC_URL: Optional[str] = global_settings.STATIC_URL - FILE_UPLOAD_HANDLERS: List[str] = global_settings.FILE_UPLOAD_HANDLERS + STATIC_ROOT: DirectoryPath | None = global_settings.STATIC_ROOT + STATIC_URL: str | None = global_settings.STATIC_URL + FILE_UPLOAD_HANDLERS: list[str] = global_settings.FILE_UPLOAD_HANDLERS FILE_UPLOAD_MAX_MEMORY_SIZE: int = global_settings.FILE_UPLOAD_MAX_MEMORY_SIZE DATA_UPLOAD_MAX_MEMORY_SIZE: int = global_settings.DATA_UPLOAD_MAX_MEMORY_SIZE - DATA_UPLOAD_MAX_NUMBER_FIELDS: Optional[ + DATA_UPLOAD_MAX_NUMBER_FIELDS: None | ( int - ] = global_settings.DATA_UPLOAD_MAX_NUMBER_FIELDS + ) = global_settings.DATA_UPLOAD_MAX_NUMBER_FIELDS FILE_UPLOAD_TEMP_DIR: types.FILE_UPLOAD_TEMP_DIR = ( global_settings.FILE_UPLOAD_TEMP_DIR ) - FILE_UPLOAD_PERMISSIONS: Optional[int] = global_settings.FILE_UPLOAD_PERMISSIONS - FILE_UPLOAD_DIRECTORY_PERMISSIONS: Optional[ + FILE_UPLOAD_PERMISSIONS: int | None = global_settings.FILE_UPLOAD_PERMISSIONS + FILE_UPLOAD_DIRECTORY_PERMISSIONS: None | ( int - ] = global_settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS - FORMAT_MODULE_PATH: Optional[str] = global_settings.FORMAT_MODULE_PATH + ) = global_settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS + FORMAT_MODULE_PATH: str | None = global_settings.FORMAT_MODULE_PATH DATE_FORMAT: str = global_settings.DATE_FORMAT DATETIME_FORMAT: str = global_settings.DATETIME_FORMAT TIME_FORMAT: str = global_settings.TIME_FORMAT @@ -158,9 +158,9 @@ class PydanticSettings(BaseSettings): MONTH_DAY_FORMAT: str = global_settings.MONTH_DAY_FORMAT SHORT_DATE_FORMAT: str = global_settings.SHORT_DATE_FORMAT SHORT_DATETIME_FORMAT: str = global_settings.SHORT_DATETIME_FORMAT - DATE_INPUT_FORMATS: List[str] = global_settings.DATE_INPUT_FORMATS - TIME_INPUT_FORMATS: List[str] = global_settings.TIME_INPUT_FORMATS - DATETIME_INPUT_FORMATS: List[str] = global_settings.DATETIME_INPUT_FORMATS + DATE_INPUT_FORMATS: list[str] = global_settings.DATE_INPUT_FORMATS + TIME_INPUT_FORMATS: list[str] = global_settings.TIME_INPUT_FORMATS + DATETIME_INPUT_FORMATS: list[str] = global_settings.DATETIME_INPUT_FORMATS FIRST_DAY_OF_WEEK: int = global_settings.FIRST_DAY_OF_WEEK DECIMAL_SEPARATOR: str = global_settings.DECIMAL_SEPARATOR USE_THOUSAND_SEPARATOR: bool = global_settings.USE_THOUSAND_SEPARATOR @@ -170,18 +170,18 @@ class PydanticSettings(BaseSettings): X_FRAME_OPTIONS: str = global_settings.X_FRAME_OPTIONS USE_X_FORWARDED_HOST: bool = global_settings.USE_X_FORWARDED_HOST USE_X_FORWARDED_PORT: bool = global_settings.USE_X_FORWARDED_PORT - WSGI_APPLICATION: Optional[str] = None + WSGI_APPLICATION: str | None = None SECURE_PROXY_SSL_HEADER: types.SECURE_PROXY_SSL_HEADER = ( global_settings.SECURE_PROXY_SSL_HEADER ) DEFAULT_HASHING_ALGORITHM: types.DEFAULT_HASHING_ALGORITHM = _get_default_setting( "DEFAULT_HASHING_ALGORITHM" ) - MIDDLEWARE: List[str] = global_settings.MIDDLEWARE + MIDDLEWARE: list[str] = global_settings.MIDDLEWARE SESSION_CACHE_ALIAS: str = global_settings.SESSION_CACHE_ALIAS SESSION_COOKIE_NAME: str = global_settings.SESSION_COOKIE_NAME - SESSION_COOKIE_AGE: Optional[int] = global_settings.SESSION_COOKIE_AGE - SESSION_COOKIE_DOMAIN: Optional[str] = global_settings.SESSION_COOKIE_DOMAIN + SESSION_COOKIE_AGE: int | None = global_settings.SESSION_COOKIE_AGE + SESSION_COOKIE_DOMAIN: str | None = global_settings.SESSION_COOKIE_DOMAIN SESSION_COOKIE_SECURE: bool = global_settings.SESSION_COOKIE_SECURE SESSION_COOKIE_PATH: str = global_settings.SESSION_COOKIE_PATH SESSION_COOKIE_HTTPONLY: bool = global_settings.SESSION_COOKIE_HTTPONLY @@ -197,25 +197,23 @@ class PydanticSettings(BaseSettings): SESSION_SERIALIZER: str = global_settings.SESSION_SERIALIZER CACHES: types.CACHES = global_settings.CACHES # type: ignore CACHE_MIDDLEWARE_KEY_PREFIX: str = global_settings.CACHE_MIDDLEWARE_KEY_PREFIX - CACHE_MIDDLEWARE_SECONDS: Optional[int] = global_settings.CACHE_MIDDLEWARE_SECONDS + CACHE_MIDDLEWARE_SECONDS: int | None = global_settings.CACHE_MIDDLEWARE_SECONDS CACHE_MIDDLEWARE_ALIAS: str = global_settings.CACHE_MIDDLEWARE_ALIAS AUTH_USER_MODEL: str = global_settings.AUTH_USER_MODEL - AUTHENTICATION_BACKENDS: List[str] = global_settings.AUTHENTICATION_BACKENDS + AUTHENTICATION_BACKENDS: list[str] = global_settings.AUTHENTICATION_BACKENDS LOGIN_URL: str = global_settings.LOGIN_URL LOGIN_REDIRECT_URL: str = global_settings.LOGIN_REDIRECT_URL - PASSWORD_RESET_TIMEOUT_DAYS: Optional[int] = _get_default_setting( + PASSWORD_RESET_TIMEOUT_DAYS: int | None = _get_default_setting( "PASSWORD_RESET_TIMEOUT_DAYS" ) - PASSWORD_RESET_TIMEOUT: Optional[int] = _get_default_setting( - "PASSWORD_RESET_TIMEOUT" - ) - PASSWORD_HASHERS: List[str] = global_settings.PASSWORD_HASHERS - AUTH_PASSWORD_VALIDATORS: List[dict] = global_settings.AUTH_PASSWORD_VALIDATORS + PASSWORD_RESET_TIMEOUT: int | None = _get_default_setting("PASSWORD_RESET_TIMEOUT") + PASSWORD_HASHERS: list[str] = global_settings.PASSWORD_HASHERS + AUTH_PASSWORD_VALIDATORS: list[dict] = global_settings.AUTH_PASSWORD_VALIDATORS SIGNING_BACKEND: str = global_settings.SIGNING_BACKEND CSRF_FAILURE_VIEW: str = global_settings.CSRF_FAILURE_VIEW CSRF_COOKIE_NAME: str = global_settings.CSRF_COOKIE_NAME - CSRF_COOKIE_AGE: Optional[int] = global_settings.CSRF_COOKIE_AGE - CSRF_COOKIE_DOMAIN: Optional[str] = global_settings.CSRF_COOKIE_DOMAIN + CSRF_COOKIE_AGE: int | None = global_settings.CSRF_COOKIE_AGE + CSRF_COOKIE_DOMAIN: str | None = global_settings.CSRF_COOKIE_DOMAIN CSRF_COOKIE_PATH: str = global_settings.CSRF_COOKIE_PATH CSRF_COOKIE_SECURE: bool = global_settings.CSRF_COOKIE_SECURE CSRF_COOKIE_HTTPONLY: bool = global_settings.CSRF_COOKIE_HTTPONLY @@ -223,26 +221,26 @@ class PydanticSettings(BaseSettings): "CSRF_COOKIE_SAMESITE" ) CSRF_HEADER_NAME: str = global_settings.CSRF_HEADER_NAME - CSRF_TRUSTED_ORIGINS: List[str] = global_settings.CSRF_TRUSTED_ORIGINS + CSRF_TRUSTED_ORIGINS: list[str] = global_settings.CSRF_TRUSTED_ORIGINS CSRF_USE_SESSIONS: bool = global_settings.CSRF_USE_SESSIONS MESSAGE_STORAGE: str = global_settings.MESSAGE_STORAGE LOGGING_CONFIG: str = global_settings.LOGGING_CONFIG LOGGING: dict = global_settings.LOGGING - DEFAULT_EXCEPTION_REPORTER: Optional[str] = _get_default_setting( + DEFAULT_EXCEPTION_REPORTER: str | None = _get_default_setting( "DEFAULT_EXCEPTION_REPORTER" ) DEFAULT_EXCEPTION_REPORTER_FILTER: str = ( global_settings.DEFAULT_EXCEPTION_REPORTER_FILTER ) TEST_RUNNER: str = global_settings.TEST_RUNNER - TEST_NON_SERIALIZED_APPS: List[str] = global_settings.TEST_NON_SERIALIZED_APPS + TEST_NON_SERIALIZED_APPS: list[str] = global_settings.TEST_NON_SERIALIZED_APPS FIXTURE_DIRS: types.FIXTURE_DIRS = global_settings.FIXTURE_DIRS STATICFILES_DIRS: types.STATICFILES_DIRS = global_settings.STATICFILES_DIRS STATICFILES_STORAGE: str = global_settings.STATICFILES_STORAGE - STATICFILES_FINDERS: List[str] = global_settings.STATICFILES_FINDERS - MIGRATION_MODULES: Dict[str, str] = global_settings.MIGRATION_MODULES - SILENCED_SYSTEM_CHECKS: List[str] = global_settings.SILENCED_SYSTEM_CHECKS - SECURE_BROWSER_XSS_FILTER: Optional[bool] = _get_default_setting( + STATICFILES_FINDERS: list[str] = global_settings.STATICFILES_FINDERS + MIGRATION_MODULES: dict[str, str] = global_settings.MIGRATION_MODULES + SILENCED_SYSTEM_CHECKS: list[str] = global_settings.SILENCED_SYSTEM_CHECKS + SECURE_BROWSER_XSS_FILTER: bool | None = _get_default_setting( "SECURE_BROWSER_XSS_FILTER" ) SECURE_CONTENT_TYPE_NOSNIFF: bool = global_settings.SECURE_CONTENT_TYPE_NOSNIFF @@ -250,22 +248,22 @@ class PydanticSettings(BaseSettings): global_settings.SECURE_HSTS_INCLUDE_SUBDOMAINS ) SECURE_HSTS_PRELOAD: bool = global_settings.SECURE_HSTS_PRELOAD - SECURE_HSTS_SECONDS: Optional[int] = global_settings.SECURE_HSTS_SECONDS + SECURE_HSTS_SECONDS: int | None = global_settings.SECURE_HSTS_SECONDS SECURE_REDIRECT_EXEMPT: types.SECURE_REDIRECT_EXEMPT = ( global_settings.SECURE_REDIRECT_EXEMPT ) SECURE_REFERRER_POLICY: types.SECURE_REFERRER_POLICY = _get_default_setting( "SECURE_REFERRER_POLICY" ) - SECURE_SSL_HOST: Optional[str] = global_settings.SECURE_SSL_HOST + SECURE_SSL_HOST: str | None = global_settings.SECURE_SSL_HOST SECURE_SSL_REDIRECT: bool = global_settings.SECURE_SSL_REDIRECT - ROOT_URLCONF: Optional[str] = None + ROOT_URLCONF: str | None = None - default_database_dsn: Optional[DatabaseDsn] = Field( + default_database_dsn: DatabaseDsn | None = Field( env="DATABASE_URL", configure_database="default" ) - default_cache_dsn: Optional[CacheDsn] = Field( + default_cache_dsn: CacheDsn | None = Field( env="CACHE_URL", configure_cache="default" ) @@ -296,7 +294,7 @@ def set_default_database(cls, values: dict) -> dict: DATABASES = values["DATABASES"] for db_key, attr in cls._get_dsn_fields(field_extra="configure_database"): if not DATABASES.get(db_key): - database_dsn: Optional[DatabaseDsn] = values[attr] + database_dsn: DatabaseDsn | None = values[attr] if database_dsn: DATABASES[db_key] = database_dsn.to_settings_model() del values[attr] @@ -310,7 +308,7 @@ def set_default_cache(cls, values: dict) -> dict: """ CACHES = values.get("CACHES") or {} for cache_key, attr in cls._get_dsn_fields(field_extra="configure_cache"): - cache_dsn: Optional[CacheDsn] = values[attr] + cache_dsn: CacheDsn | None = values[attr] if cache_dsn: CACHES = values.setdefault("CACHES", {}) CACHES[cache_key] = cache_dsn.to_settings_model() @@ -318,7 +316,7 @@ def set_default_cache(cls, values: dict) -> dict: return values @classmethod - def _get_dsn_fields(cls, field_extra: str) -> Iterable[Tuple[str, str]]: + def _get_dsn_fields(cls, field_extra: str) -> Iterable[tuple[str, str]]: field: ModelField for field in cls.__fields__.values(): db_key = field.field_info.extra.get(field_extra) @@ -346,7 +344,7 @@ class is being used then ROOT_URLCONF and WSGI_APPLICATION will be prepended if not values["ROOT_URLCONF"]: values["ROOT_URLCONF"] = f"{project_module_name}.urls" - base_dir: Optional[Path] = values["BASE_DIR"] + base_dir: Path | None = values["BASE_DIR"] if not base_dir: ancestor = module.__name__.count(".") path = Path(inspect.getfile(module)).resolve() diff --git a/pydantic_settings/types.py b/pydantic_settings/types.py index 744ac8e..9ddcb31 100644 --- a/pydantic_settings/types.py +++ b/pydantic_settings/types.py @@ -1,8 +1,9 @@ +from __future__ import annotations + from re import Pattern -from typing import Callable, Dict, List, Optional, Tuple, Union +from typing import Callable, Dict, List, Literal, Optional, Tuple, Union from pydantic import DirectoryPath, EmailStr, IPvAnyAddress -from typing_extensions import Literal from pydantic_settings.models import CacheModel, DatabaseModel, TemplateBackendModel @@ -17,7 +18,7 @@ "strict-origin-when-cross-origin", "unsafe-url", ] -RegexList = List[Pattern] +RegexList = List[Pattern[str]] SameSiteOptions = Literal["Lax", "Strict", "None"] ABSOLUTE_URL_OVERRIDES = Dict[str, Callable] diff --git a/tests/settings_proj/asgi.py b/tests/settings_proj/asgi.py index 78a318f..3e8fde2 100644 --- a/tests/settings_proj/asgi.py +++ b/tests/settings_proj/asgi.py @@ -7,7 +7,6 @@ https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ """ -from django.conf import settings from django.core.asgi import get_asgi_application from pydantic_settings import SetUp diff --git a/tests/test_env.py b/tests/test_env.py index 4f705f7..e804965 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -48,8 +48,8 @@ def test_env_loaded2(configure_settings): def test_sqlite_path(configure_settings): """ - Make sure we aren't improperly stripping the leading slash from the path for SQLite databases with an - absolute path + Make sure we aren't improperly stripping the leading slash from the path for SQLite + databases with an absolute path """ configure_settings({"DATABASE_URL": "sqlite:////db/test.db"}) @@ -123,13 +123,13 @@ def test_base_dir(configure_settings): def test_base_dir_default(configure_settings): configure_settings() - assert settings.BASE_DIR == None + assert settings.BASE_DIR is None def test_dynamic_defaults(configure_settings): configure_settings() - assert settings.ROOT_URLCONF == None - assert settings.WSGI_APPLICATION == None + assert settings.ROOT_URLCONF is None + assert settings.WSGI_APPLICATION is None def test_dynamic_defaults_custom_module(configure_settings): @@ -165,7 +165,10 @@ def test_escaped_gcp_cloudsql_socket(configure_settings): configure_settings( { "DJANGO_BASE_DIR": str(tests_dir), - "DATABASE_URL": "postgres://username:password@%2Fcloudsql%2Fproject%3Aregion%3Ainstance/database", + "DATABASE_URL": ( + "postgres://username:password" + "@%2Fcloudsql%2Fproject%3Aregion%3Ainstance/database" + ), } ) @@ -179,17 +182,15 @@ def test_escaped_gcp_cloudsql_socket(configure_settings): assert default["ENGINE"] == "django.db.backends.postgresql" -from django.conf import global_settings - -gd = global_settings.DATABASES - - def test_unescaped_gcp_cloudsql_socket(configure_settings): tests_dir = Path(__file__).parent configure_settings( { "DJANGO_BASE_DIR": str(tests_dir), - "DATABASE_URL": "postgres://username:password@/cloudsql/project:region:instance/database", + "DATABASE_URL": ( + "postgres://username:password" + "@/cloudsql/project:region:instance/database" + ), } ) @@ -219,7 +220,9 @@ def test_default_db(configure_settings): def test_default_db_no_basedir(configure_settings): configure_settings( { - "DJANGO_SETTINGS_MODULE": "pydantic_settings.default.DjangoDefaultProjectSettings", + "DJANGO_SETTINGS_MODULE": ( + "pydantic_settings.default.DjangoDefaultProjectSettings" + ), } ) From ee042b17986c141d955f371ee7958cb831b1c894 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Tue, 6 Dec 2022 13:53:24 +1300 Subject: [PATCH 4/9] Change default type for SECURE_REDIRECT_EXEMPT to be a list of strings rather than regexes --- pydantic_settings/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic_settings/types.py b/pydantic_settings/types.py index 9ddcb31..eec0aa3 100644 --- a/pydantic_settings/types.py +++ b/pydantic_settings/types.py @@ -38,7 +38,7 @@ LOCALE_PATHS = List[DirectoryPath] MANAGERS = Optional[EmailList] SECURE_PROXY_SSL_HEADER = Optional[Tuple[str, str]] -SECURE_REDIRECT_EXEMPT = RegexList +SECURE_REDIRECT_EXEMPT = List[str] SECURE_REFERRER_POLICY = Optional[ReferrerOptions] SERVER_EMAIL = Union[EmailStr, Literal["root@localhost"]] SESSION_COOKIE_SAMESITE = Optional[SameSiteOptions] From 0e01704795ffc0ddc7007deec422243e328618d8 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Tue, 6 Dec 2022 14:03:49 +1300 Subject: [PATCH 5/9] Typing fixes --- pydantic_settings/cache.py | 5 +++-- pydantic_settings/database.py | 11 +++++----- pydantic_settings/sentry.py | 2 +- pydantic_settings/settings.py | 38 +++++++++++++++++++++++------------ 4 files changed, 35 insertions(+), 21 deletions(-) diff --git a/pydantic_settings/cache.py b/pydantic_settings/cache.py index aa2943b..5142c29 100644 --- a/pydantic_settings/cache.py +++ b/pydantic_settings/cache.py @@ -1,6 +1,7 @@ from __future__ import annotations import re +from typing import Any from urllib.parse import parse_qs from django import VERSION @@ -72,7 +73,7 @@ def is_redis_scheme(self) -> bool: def parse(dsn: CacheDsn) -> dict: """Parses a cache URL.""" backend = CACHE_ENGINES[dsn.scheme] - config = {"BACKEND": backend} + config: dict[str, Any] = {"BACKEND": backend} options = {} if dsn.scheme in REDIS_PARSERS: @@ -82,7 +83,7 @@ def parse(dsn: CacheDsn) -> dict: # File based if dsn.host is None: - path = dsn.path + path = dsn.path or "" if dsn.scheme in FILE_UNIX_PREFIX: path = "unix:" + path diff --git a/pydantic_settings/database.py b/pydantic_settings/database.py index ad55a88..3192002 100644 --- a/pydantic_settings/database.py +++ b/pydantic_settings/database.py @@ -2,7 +2,7 @@ import re import urllib.parse -from typing import Pattern, cast +from typing import TYPE_CHECKING, Pattern, cast from urllib.parse import quote_plus from pydantic import AnyUrl @@ -10,6 +10,9 @@ from pydantic_settings.models import DatabaseModel +if TYPE_CHECKING: + from pydantic.networks import Parts + _cloud_sql_regex_cache = None @@ -67,12 +70,10 @@ def validate(cls, value, field, config): return super().validate(value, field, config) @classmethod - def validate_host( - cls, parts: dict[str, str] - ) -> tuple[str | None, str | None, str, bool]: + def validate_host(cls, parts: Parts) -> tuple[str | None, str | None, str, bool]: host = None for f in ("domain", "ipv4", "ipv6"): - host = parts[f] + host = parts.get(f) if host: break diff --git a/pydantic_settings/sentry.py b/pydantic_settings/sentry.py index 36bfd62..e98bff3 100644 --- a/pydantic_settings/sentry.py +++ b/pydantic_settings/sentry.py @@ -2,8 +2,8 @@ import sentry_sdk from pydantic import AnyUrl +from pydantic.config import BaseConfig from pydantic.fields import ModelField -from pydantic.main import BaseConfig from sentry_sdk.integrations.django import DjangoIntegration from .settings import PydanticSettings diff --git a/pydantic_settings/settings.py b/pydantic_settings/settings.py index de820e6..583c65f 100644 --- a/pydantic_settings/settings.py +++ b/pydantic_settings/settings.py @@ -2,7 +2,7 @@ import inspect from pathlib import Path -from typing import Any, Iterable +from typing import Any, Iterable, Sequence from django.conf import global_settings, settings from django.core.management.utils import get_random_secret_key @@ -28,7 +28,9 @@ class SetUp(BaseSettings): - DJANGO_SETTINGS_MODULE: PyObject = "pydantic_settings.settings.PydanticSettings" + DJANGO_SETTINGS_MODULE: PyObject = ( + "pydantic_settings.settings.PydanticSettings" # type: ignore + ) def configure(self): if settings.configured: @@ -40,7 +42,7 @@ def configure(self): if inspect.isclass(self.DJANGO_SETTINGS_MODULE): settings_obj = self.DJANGO_SETTINGS_MODULE() else: - settings_obj = self.DJANGO_SETTINGS_MODULE + settings_obj = self.DJANGO_SETTINGS_MODULE # type: ignore settings_dict = { key: value @@ -101,7 +103,9 @@ class PydanticSettings(BaseSettings): USE_L10N: bool = global_settings.USE_L10N MANAGERS: types.MANAGERS = _get_default_setting("MANAGERS") DEFAULT_CHARSET: str = global_settings.DEFAULT_CHARSET - SERVER_EMAIL: types.SERVER_EMAIL = global_settings.SERVER_EMAIL + SERVER_EMAIL: types.SERVER_EMAIL = ( + global_settings.SERVER_EMAIL # type: ignore + ) DATABASES: types.DATABASES = global_settings.DATABASES # type: ignore DATABASE_ROUTERS: list[str] = global_settings.DATABASE_ROUTERS @@ -113,11 +117,15 @@ class PydanticSettings(BaseSettings): EMAIL_HOST_PASSWORD: str = global_settings.EMAIL_HOST_PASSWORD EMAIL_USE_TLS: bool = global_settings.EMAIL_USE_TLS EMAIL_USE_SSL: bool = global_settings.EMAIL_USE_SSL - EMAIL_SSL_CERTFILE: FilePath | None = global_settings.EMAIL_SSL_CERTFILE - EMAIL_SSL_KEYFILE: FilePath | None = global_settings.EMAIL_SSL_KEYFILE + EMAIL_SSL_CERTFILE: FilePath | None = ( + global_settings.EMAIL_SSL_CERTFILE # type: ignore + ) + EMAIL_SSL_KEYFILE: FilePath | None = ( + global_settings.EMAIL_SSL_KEYFILE # type: ignore + ) EMAIL_TIMEOUT: int | None = global_settings.EMAIL_TIMEOUT INSTALLED_APPS: list[str] = global_settings.INSTALLED_APPS - TEMPLATES: types.TEMPLATES = global_settings.TEMPLATES + TEMPLATES: types.TEMPLATES = global_settings.TEMPLATES # type: ignore FORM_RENDERER: str = global_settings.FORM_RENDERER DEFAULT_FROM_EMAIL: str = global_settings.DEFAULT_FROM_EMAIL EMAIL_SUBJECT_PREFIX: str = global_settings.EMAIL_SUBJECT_PREFIX @@ -135,7 +143,7 @@ class PydanticSettings(BaseSettings): DEFAULT_FILE_STORAGE: str = global_settings.DEFAULT_FILE_STORAGE MEDIA_ROOT: str = global_settings.MEDIA_ROOT MEDIA_URL: str = global_settings.MEDIA_URL - STATIC_ROOT: DirectoryPath | None = global_settings.STATIC_ROOT + STATIC_ROOT: DirectoryPath | None = global_settings.STATIC_ROOT # type: ignore STATIC_URL: str | None = global_settings.STATIC_URL FILE_UPLOAD_HANDLERS: list[str] = global_settings.FILE_UPLOAD_HANDLERS FILE_UPLOAD_MAX_MEMORY_SIZE: int = global_settings.FILE_UPLOAD_MAX_MEMORY_SIZE @@ -144,7 +152,7 @@ class PydanticSettings(BaseSettings): int ) = global_settings.DATA_UPLOAD_MAX_NUMBER_FIELDS FILE_UPLOAD_TEMP_DIR: types.FILE_UPLOAD_TEMP_DIR = ( - global_settings.FILE_UPLOAD_TEMP_DIR + global_settings.FILE_UPLOAD_TEMP_DIR # type: ignore ) FILE_UPLOAD_PERMISSIONS: int | None = global_settings.FILE_UPLOAD_PERMISSIONS FILE_UPLOAD_DIRECTORY_PERMISSIONS: None | ( @@ -193,14 +201,16 @@ class PydanticSettings(BaseSettings): global_settings.SESSION_EXPIRE_AT_BROWSER_CLOSE ) SESSION_ENGINE: str = global_settings.SESSION_ENGINE - SESSION_FILE_PATH: types.SESSION_FILE_PATH = global_settings.SESSION_FILE_PATH + SESSION_FILE_PATH: types.SESSION_FILE_PATH = ( + global_settings.SESSION_FILE_PATH # type: ignore + ) SESSION_SERIALIZER: str = global_settings.SESSION_SERIALIZER CACHES: types.CACHES = global_settings.CACHES # type: ignore CACHE_MIDDLEWARE_KEY_PREFIX: str = global_settings.CACHE_MIDDLEWARE_KEY_PREFIX CACHE_MIDDLEWARE_SECONDS: int | None = global_settings.CACHE_MIDDLEWARE_SECONDS CACHE_MIDDLEWARE_ALIAS: str = global_settings.CACHE_MIDDLEWARE_ALIAS AUTH_USER_MODEL: str = global_settings.AUTH_USER_MODEL - AUTHENTICATION_BACKENDS: list[str] = global_settings.AUTHENTICATION_BACKENDS + AUTHENTICATION_BACKENDS: Sequence[str] = global_settings.AUTHENTICATION_BACKENDS LOGIN_URL: str = global_settings.LOGIN_URL LOGIN_REDIRECT_URL: str = global_settings.LOGIN_REDIRECT_URL PASSWORD_RESET_TIMEOUT_DAYS: int | None = _get_default_setting( @@ -234,8 +244,10 @@ class PydanticSettings(BaseSettings): ) TEST_RUNNER: str = global_settings.TEST_RUNNER TEST_NON_SERIALIZED_APPS: list[str] = global_settings.TEST_NON_SERIALIZED_APPS - FIXTURE_DIRS: types.FIXTURE_DIRS = global_settings.FIXTURE_DIRS - STATICFILES_DIRS: types.STATICFILES_DIRS = global_settings.STATICFILES_DIRS + FIXTURE_DIRS: types.FIXTURE_DIRS = global_settings.FIXTURE_DIRS # type: ignore + STATICFILES_DIRS: types.STATICFILES_DIRS = ( + global_settings.STATICFILES_DIRS # type: ignore + ) STATICFILES_STORAGE: str = global_settings.STATICFILES_STORAGE STATICFILES_FINDERS: list[str] = global_settings.STATICFILES_FINDERS MIGRATION_MODULES: dict[str, str] = global_settings.MIGRATION_MODULES From 1b0ae962f805ab4bcc16ff3387cdaf9d90ab3d92 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Tue, 6 Dec 2022 14:28:06 +1300 Subject: [PATCH 6/9] mypy fixes --- pydantic_settings/cache.py | 2 +- pydantic_settings/database.py | 12 ++++++------ pydantic_settings/settings.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pydantic_settings/cache.py b/pydantic_settings/cache.py index 5142c29..aede8ea 100644 --- a/pydantic_settings/cache.py +++ b/pydantic_settings/cache.py @@ -75,7 +75,7 @@ def parse(dsn: CacheDsn) -> dict: backend = CACHE_ENGINES[dsn.scheme] config: dict[str, Any] = {"BACKEND": backend} - options = {} + options: dict[str, Any] = {} if dsn.scheme in REDIS_PARSERS: options["PARSER_CLASS"] = REDIS_PARSERS[dsn.scheme] diff --git a/pydantic_settings/database.py b/pydantic_settings/database.py index 3192002..3951922 100644 --- a/pydantic_settings/database.py +++ b/pydantic_settings/database.py @@ -2,7 +2,7 @@ import re import urllib.parse -from typing import TYPE_CHECKING, Pattern, cast +from typing import TYPE_CHECKING, Pattern, Union, cast from urllib.parse import quote_plus from pydantic import AnyUrl @@ -70,17 +70,17 @@ def validate(cls, value, field, config): return super().validate(value, field, config) @classmethod - def validate_host(cls, parts: Parts) -> tuple[str | None, str | None, str, bool]: - host = None + def validate_host(cls, parts: Parts) -> tuple[str, str | None, str, bool]: + host: str | None = None for f in ("domain", "ipv4", "ipv6"): - host = parts.get(f) + host = cast(Union[str, None], parts.get(f)) if host: break if host is None: - return None, None, "file", False + return None, None, "file", False # type: ignore - if host.startswith("%2F"): + if host and host.startswith("%2F"): return host, None, "socket", False return super().validate_host(parts) diff --git a/pydantic_settings/settings.py b/pydantic_settings/settings.py index 583c65f..7d151fa 100644 --- a/pydantic_settings/settings.py +++ b/pydantic_settings/settings.py @@ -108,7 +108,7 @@ class PydanticSettings(BaseSettings): ) DATABASES: types.DATABASES = global_settings.DATABASES # type: ignore - DATABASE_ROUTERS: list[str] = global_settings.DATABASE_ROUTERS + DATABASE_ROUTERS: list[str] = global_settings.DATABASE_ROUTERS # type: ignore EMAIL_BACKEND: str = global_settings.EMAIL_BACKEND EMAIL_HOST: str = global_settings.EMAIL_HOST EMAIL_PORT: int = global_settings.EMAIL_PORT From f90cf5c9dca7861d8791c5afab91eb53dc176a23 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Tue, 6 Dec 2022 14:30:08 +1300 Subject: [PATCH 7/9] Fix test warning --- tests/test_env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_env.py b/tests/test_env.py index e804965..d1d9cfa 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -15,7 +15,7 @@ def configure_settings(monkeypatch): def func(env: dict = {}): for key, value in env.items(): - monkeypatch.setenv(key, value) + monkeypatch.setenv(key, str(value)) SetUp().configure() yield func From 4850ae8a4c2b599190697928f9e902ab5e16addb Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Tue, 6 Dec 2022 15:11:06 +1300 Subject: [PATCH 8/9] Fixes for python 3.8: can't use future annotations with pydantic --- pydantic_settings/cache.py | 6 +- pydantic_settings/database.py | 8 +- pydantic_settings/default.py | 12 +-- pydantic_settings/settings.py | 136 +++++++++++++++++----------------- pydantic_settings/types.py | 2 +- 5 files changed, 82 insertions(+), 82 deletions(-) diff --git a/pydantic_settings/cache.py b/pydantic_settings/cache.py index aede8ea..4b35fec 100644 --- a/pydantic_settings/cache.py +++ b/pydantic_settings/cache.py @@ -1,7 +1,5 @@ -from __future__ import annotations - import re -from typing import Any +from typing import Any, Dict from urllib.parse import parse_qs from django import VERSION @@ -49,7 +47,7 @@ class CacheDsn(AnyUrl): __slots__ = AnyUrl.__slots__ + ("query_args",) host_required = False - query_args: dict[str, str] + query_args: Dict[str, str] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/pydantic_settings/database.py b/pydantic_settings/database.py index 3951922..fa10adf 100644 --- a/pydantic_settings/database.py +++ b/pydantic_settings/database.py @@ -1,8 +1,6 @@ -from __future__ import annotations - import re import urllib.parse -from typing import TYPE_CHECKING, Pattern, Union, cast +from typing import TYPE_CHECKING, Pattern, Tuple, Union, cast from urllib.parse import quote_plus from pydantic import AnyUrl @@ -31,7 +29,7 @@ } -def cloud_sql_regex() -> Pattern[str]: +def cloud_sql_regex() -> Pattern: global _cloud_sql_regex_cache if _cloud_sql_regex_cache is None: _cloud_sql_regex_cache = re.compile( @@ -70,7 +68,7 @@ def validate(cls, value, field, config): return super().validate(value, field, config) @classmethod - def validate_host(cls, parts: Parts) -> tuple[str, str | None, str, bool]: + def validate_host(cls, parts: "Parts") -> Tuple[str, Union[str, None], str, bool]: host: str | None = None for f in ("domain", "ipv4", "ipv6"): host = cast(Union[str, None], parts.get(f)) diff --git a/pydantic_settings/default.py b/pydantic_settings/default.py index 2f9eb4a..4446b31 100644 --- a/pydantic_settings/default.py +++ b/pydantic_settings/default.py @@ -1,4 +1,4 @@ -from __future__ import annotations +from typing import Dict, List from pydantic import root_validator @@ -12,11 +12,11 @@ class DjangoDefaultProjectSettings(PydanticSettings): generates for new projects. """ - DATABASES: dict[str, DatabaseModel] = { + DATABASES: Dict[str, DatabaseModel] = { "default": "sqlite:///db.sqlite3", # type: ignore } - TEMPLATES: list[TemplateBackendModel] = [ + TEMPLATES: List[TemplateBackendModel] = [ TemplateBackendModel.parse_obj(data) for data in [ { @@ -35,7 +35,7 @@ class DjangoDefaultProjectSettings(PydanticSettings): ] ] - INSTALLED_APPS: list[str] = [ + INSTALLED_APPS: List[str] = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", @@ -44,7 +44,7 @@ class DjangoDefaultProjectSettings(PydanticSettings): "django.contrib.staticfiles", ] - MIDDLEWARE: list[str] = [ + MIDDLEWARE: List[str] = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", @@ -54,7 +54,7 @@ class DjangoDefaultProjectSettings(PydanticSettings): "django.middleware.clickjacking.XFrameOptionsMiddleware", ] - AUTH_PASSWORD_VALIDATORS: list[dict] = [ + AUTH_PASSWORD_VALIDATORS: List[dict] = [ { "NAME": "django.contrib.auth.password_validation" ".UserAttributeSimilarityValidator", diff --git a/pydantic_settings/settings.py b/pydantic_settings/settings.py index 7d151fa..28f4b74 100644 --- a/pydantic_settings/settings.py +++ b/pydantic_settings/settings.py @@ -1,8 +1,6 @@ -from __future__ import annotations - import inspect from pathlib import Path -from typing import Any, Iterable, Sequence +from typing import Any, Dict, Iterable, List, Sequence, Tuple, Union from django.conf import global_settings, settings from django.core.management.utils import get_random_secret_key @@ -75,7 +73,7 @@ class PydanticSettings(BaseSettings): # Would be nice to do something like Union[Literal["*"], IPvAnyAddress, AnyUrl], but # there are a lot of different options that need to be valid and don't necessarily # fit those types. - ALLOWED_HOSTS: list[str] = global_settings.ALLOWED_HOSTS + ALLOWED_HOSTS: List[str] = global_settings.ALLOWED_HOSTS # Validate against actual list of valid TZs? # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List @@ -85,16 +83,18 @@ class PydanticSettings(BaseSettings): # Validate LANGUAGE_CODE and LANGUAGES_BIDI against LANGUAGES. LANGUAGE_CODE: str = global_settings.LANGUAGE_CODE LANGUAGES: types.LANGUAGES = global_settings.LANGUAGES - LANGUAGES_BIDI: list[str] = global_settings.LANGUAGES_BIDI + LANGUAGES_BIDI: List[str] = global_settings.LANGUAGES_BIDI USE_I18N: bool = global_settings.USE_I18N LOCALE_PATHS: types.LOCALE_PATHS = _get_default_setting("LOCALE_PATHS") LANGUAGE_COOKIE_NAME: str = global_settings.LANGUAGE_COOKIE_NAME - LANGUAGE_COOKIE_AGE: int | None = global_settings.LANGUAGE_COOKIE_AGE - LANGUAGE_COOKIE_DOMAIN: str | None = global_settings.LANGUAGE_COOKIE_DOMAIN + LANGUAGE_COOKIE_AGE: Union[int, None] = global_settings.LANGUAGE_COOKIE_AGE + LANGUAGE_COOKIE_DOMAIN: Union[str, None] = global_settings.LANGUAGE_COOKIE_DOMAIN LANGUAGE_COOKIE_PATH: str = global_settings.LANGUAGE_COOKIE_PATH - LANGUAGE_COOKIE_SECURE: bool | None = _get_default_setting("LANGUAGE_COOKIE_SECURE") - LANGUAGE_COOKIE_HTTPONLY: bool | None = _get_default_setting( + LANGUAGE_COOKIE_SECURE: Union[bool, None] = _get_default_setting( + "LANGUAGE_COOKIE_SECURE" + ) + LANGUAGE_COOKIE_HTTPONLY: Union[bool, None] = _get_default_setting( "LANGUAGE_COOKIE_HTTPONLY" ) LANGUAGE_COOKIE_SAMESITE: types.LANGUAGE_COOKIE_SAMESITE = _get_default_setting( @@ -103,12 +103,10 @@ class PydanticSettings(BaseSettings): USE_L10N: bool = global_settings.USE_L10N MANAGERS: types.MANAGERS = _get_default_setting("MANAGERS") DEFAULT_CHARSET: str = global_settings.DEFAULT_CHARSET - SERVER_EMAIL: types.SERVER_EMAIL = ( - global_settings.SERVER_EMAIL # type: ignore - ) + SERVER_EMAIL: types.SERVER_EMAIL = global_settings.SERVER_EMAIL # type: ignore DATABASES: types.DATABASES = global_settings.DATABASES # type: ignore - DATABASE_ROUTERS: list[str] = global_settings.DATABASE_ROUTERS # type: ignore + DATABASE_ROUTERS: List[str] = global_settings.DATABASE_ROUTERS # type: ignore EMAIL_BACKEND: str = global_settings.EMAIL_BACKEND EMAIL_HOST: str = global_settings.EMAIL_HOST EMAIL_PORT: int = global_settings.EMAIL_PORT @@ -117,21 +115,21 @@ class PydanticSettings(BaseSettings): EMAIL_HOST_PASSWORD: str = global_settings.EMAIL_HOST_PASSWORD EMAIL_USE_TLS: bool = global_settings.EMAIL_USE_TLS EMAIL_USE_SSL: bool = global_settings.EMAIL_USE_SSL - EMAIL_SSL_CERTFILE: FilePath | None = ( - global_settings.EMAIL_SSL_CERTFILE # type: ignore - ) - EMAIL_SSL_KEYFILE: FilePath | None = ( - global_settings.EMAIL_SSL_KEYFILE # type: ignore - ) - EMAIL_TIMEOUT: int | None = global_settings.EMAIL_TIMEOUT - INSTALLED_APPS: list[str] = global_settings.INSTALLED_APPS + EMAIL_SSL_CERTFILE: Union[ + FilePath, None + ] = global_settings.EMAIL_SSL_CERTFILE # type: ignore + EMAIL_SSL_KEYFILE: Union[ + FilePath, None + ] = global_settings.EMAIL_SSL_KEYFILE # type: ignore + EMAIL_TIMEOUT: Union[int, None] = global_settings.EMAIL_TIMEOUT + INSTALLED_APPS: List[str] = global_settings.INSTALLED_APPS TEMPLATES: types.TEMPLATES = global_settings.TEMPLATES # type: ignore FORM_RENDERER: str = global_settings.FORM_RENDERER DEFAULT_FROM_EMAIL: str = global_settings.DEFAULT_FROM_EMAIL EMAIL_SUBJECT_PREFIX: str = global_settings.EMAIL_SUBJECT_PREFIX APPEND_SLASH: bool = global_settings.APPEND_SLASH PREPEND_WWW: bool = global_settings.PREPEND_WWW - FORCE_SCRIPT_NAME: str | None = global_settings.FORCE_SCRIPT_NAME + FORCE_SCRIPT_NAME: Union[str, None] = global_settings.FORCE_SCRIPT_NAME DISALLOWED_USER_AGENTS: types.DISALLOWED_USER_AGENTS = ( global_settings.DISALLOWED_USER_AGENTS ) @@ -143,22 +141,24 @@ class PydanticSettings(BaseSettings): DEFAULT_FILE_STORAGE: str = global_settings.DEFAULT_FILE_STORAGE MEDIA_ROOT: str = global_settings.MEDIA_ROOT MEDIA_URL: str = global_settings.MEDIA_URL - STATIC_ROOT: DirectoryPath | None = global_settings.STATIC_ROOT # type: ignore - STATIC_URL: str | None = global_settings.STATIC_URL - FILE_UPLOAD_HANDLERS: list[str] = global_settings.FILE_UPLOAD_HANDLERS + STATIC_ROOT: Union[ + DirectoryPath, None + ] = global_settings.STATIC_ROOT # type: ignore + STATIC_URL: Union[str, None] = global_settings.STATIC_URL + FILE_UPLOAD_HANDLERS: List[str] = global_settings.FILE_UPLOAD_HANDLERS FILE_UPLOAD_MAX_MEMORY_SIZE: int = global_settings.FILE_UPLOAD_MAX_MEMORY_SIZE DATA_UPLOAD_MAX_MEMORY_SIZE: int = global_settings.DATA_UPLOAD_MAX_MEMORY_SIZE - DATA_UPLOAD_MAX_NUMBER_FIELDS: None | ( - int - ) = global_settings.DATA_UPLOAD_MAX_NUMBER_FIELDS + DATA_UPLOAD_MAX_NUMBER_FIELDS: Union[ + None, int + ] = global_settings.DATA_UPLOAD_MAX_NUMBER_FIELDS FILE_UPLOAD_TEMP_DIR: types.FILE_UPLOAD_TEMP_DIR = ( global_settings.FILE_UPLOAD_TEMP_DIR # type: ignore ) - FILE_UPLOAD_PERMISSIONS: int | None = global_settings.FILE_UPLOAD_PERMISSIONS - FILE_UPLOAD_DIRECTORY_PERMISSIONS: None | ( - int - ) = global_settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS - FORMAT_MODULE_PATH: str | None = global_settings.FORMAT_MODULE_PATH + FILE_UPLOAD_PERMISSIONS: Union[int, None] = global_settings.FILE_UPLOAD_PERMISSIONS + FILE_UPLOAD_DIRECTORY_PERMISSIONS: Union[ + None, int + ] = global_settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS + FORMAT_MODULE_PATH: Union[str, None] = global_settings.FORMAT_MODULE_PATH DATE_FORMAT: str = global_settings.DATE_FORMAT DATETIME_FORMAT: str = global_settings.DATETIME_FORMAT TIME_FORMAT: str = global_settings.TIME_FORMAT @@ -166,9 +166,9 @@ class PydanticSettings(BaseSettings): MONTH_DAY_FORMAT: str = global_settings.MONTH_DAY_FORMAT SHORT_DATE_FORMAT: str = global_settings.SHORT_DATE_FORMAT SHORT_DATETIME_FORMAT: str = global_settings.SHORT_DATETIME_FORMAT - DATE_INPUT_FORMATS: list[str] = global_settings.DATE_INPUT_FORMATS - TIME_INPUT_FORMATS: list[str] = global_settings.TIME_INPUT_FORMATS - DATETIME_INPUT_FORMATS: list[str] = global_settings.DATETIME_INPUT_FORMATS + DATE_INPUT_FORMATS: List[str] = global_settings.DATE_INPUT_FORMATS + TIME_INPUT_FORMATS: List[str] = global_settings.TIME_INPUT_FORMATS + DATETIME_INPUT_FORMATS: List[str] = global_settings.DATETIME_INPUT_FORMATS FIRST_DAY_OF_WEEK: int = global_settings.FIRST_DAY_OF_WEEK DECIMAL_SEPARATOR: str = global_settings.DECIMAL_SEPARATOR USE_THOUSAND_SEPARATOR: bool = global_settings.USE_THOUSAND_SEPARATOR @@ -178,18 +178,18 @@ class PydanticSettings(BaseSettings): X_FRAME_OPTIONS: str = global_settings.X_FRAME_OPTIONS USE_X_FORWARDED_HOST: bool = global_settings.USE_X_FORWARDED_HOST USE_X_FORWARDED_PORT: bool = global_settings.USE_X_FORWARDED_PORT - WSGI_APPLICATION: str | None = None + WSGI_APPLICATION: Union[str, None] = None SECURE_PROXY_SSL_HEADER: types.SECURE_PROXY_SSL_HEADER = ( global_settings.SECURE_PROXY_SSL_HEADER ) DEFAULT_HASHING_ALGORITHM: types.DEFAULT_HASHING_ALGORITHM = _get_default_setting( "DEFAULT_HASHING_ALGORITHM" ) - MIDDLEWARE: list[str] = global_settings.MIDDLEWARE + MIDDLEWARE: List[str] = global_settings.MIDDLEWARE SESSION_CACHE_ALIAS: str = global_settings.SESSION_CACHE_ALIAS SESSION_COOKIE_NAME: str = global_settings.SESSION_COOKIE_NAME - SESSION_COOKIE_AGE: int | None = global_settings.SESSION_COOKIE_AGE - SESSION_COOKIE_DOMAIN: str | None = global_settings.SESSION_COOKIE_DOMAIN + SESSION_COOKIE_AGE: Union[int, None] = global_settings.SESSION_COOKIE_AGE + SESSION_COOKIE_DOMAIN: Union[str, None] = global_settings.SESSION_COOKIE_DOMAIN SESSION_COOKIE_SECURE: bool = global_settings.SESSION_COOKIE_SECURE SESSION_COOKIE_PATH: str = global_settings.SESSION_COOKIE_PATH SESSION_COOKIE_HTTPONLY: bool = global_settings.SESSION_COOKIE_HTTPONLY @@ -207,23 +207,27 @@ class PydanticSettings(BaseSettings): SESSION_SERIALIZER: str = global_settings.SESSION_SERIALIZER CACHES: types.CACHES = global_settings.CACHES # type: ignore CACHE_MIDDLEWARE_KEY_PREFIX: str = global_settings.CACHE_MIDDLEWARE_KEY_PREFIX - CACHE_MIDDLEWARE_SECONDS: int | None = global_settings.CACHE_MIDDLEWARE_SECONDS + CACHE_MIDDLEWARE_SECONDS: Union[ + int, None + ] = global_settings.CACHE_MIDDLEWARE_SECONDS CACHE_MIDDLEWARE_ALIAS: str = global_settings.CACHE_MIDDLEWARE_ALIAS AUTH_USER_MODEL: str = global_settings.AUTH_USER_MODEL AUTHENTICATION_BACKENDS: Sequence[str] = global_settings.AUTHENTICATION_BACKENDS LOGIN_URL: str = global_settings.LOGIN_URL LOGIN_REDIRECT_URL: str = global_settings.LOGIN_REDIRECT_URL - PASSWORD_RESET_TIMEOUT_DAYS: int | None = _get_default_setting( + PASSWORD_RESET_TIMEOUT_DAYS: Union[int, None] = _get_default_setting( "PASSWORD_RESET_TIMEOUT_DAYS" ) - PASSWORD_RESET_TIMEOUT: int | None = _get_default_setting("PASSWORD_RESET_TIMEOUT") - PASSWORD_HASHERS: list[str] = global_settings.PASSWORD_HASHERS - AUTH_PASSWORD_VALIDATORS: list[dict] = global_settings.AUTH_PASSWORD_VALIDATORS + PASSWORD_RESET_TIMEOUT: Union[int, None] = _get_default_setting( + "PASSWORD_RESET_TIMEOUT" + ) + PASSWORD_HASHERS: List[str] = global_settings.PASSWORD_HASHERS + AUTH_PASSWORD_VALIDATORS: List[dict] = global_settings.AUTH_PASSWORD_VALIDATORS SIGNING_BACKEND: str = global_settings.SIGNING_BACKEND CSRF_FAILURE_VIEW: str = global_settings.CSRF_FAILURE_VIEW CSRF_COOKIE_NAME: str = global_settings.CSRF_COOKIE_NAME - CSRF_COOKIE_AGE: int | None = global_settings.CSRF_COOKIE_AGE - CSRF_COOKIE_DOMAIN: str | None = global_settings.CSRF_COOKIE_DOMAIN + CSRF_COOKIE_AGE: Union[int, None] = global_settings.CSRF_COOKIE_AGE + CSRF_COOKIE_DOMAIN: Union[str, None] = global_settings.CSRF_COOKIE_DOMAIN CSRF_COOKIE_PATH: str = global_settings.CSRF_COOKIE_PATH CSRF_COOKIE_SECURE: bool = global_settings.CSRF_COOKIE_SECURE CSRF_COOKIE_HTTPONLY: bool = global_settings.CSRF_COOKIE_HTTPONLY @@ -231,28 +235,28 @@ class PydanticSettings(BaseSettings): "CSRF_COOKIE_SAMESITE" ) CSRF_HEADER_NAME: str = global_settings.CSRF_HEADER_NAME - CSRF_TRUSTED_ORIGINS: list[str] = global_settings.CSRF_TRUSTED_ORIGINS + CSRF_TRUSTED_ORIGINS: List[str] = global_settings.CSRF_TRUSTED_ORIGINS CSRF_USE_SESSIONS: bool = global_settings.CSRF_USE_SESSIONS MESSAGE_STORAGE: str = global_settings.MESSAGE_STORAGE LOGGING_CONFIG: str = global_settings.LOGGING_CONFIG LOGGING: dict = global_settings.LOGGING - DEFAULT_EXCEPTION_REPORTER: str | None = _get_default_setting( + DEFAULT_EXCEPTION_REPORTER: Union[str, None] = _get_default_setting( "DEFAULT_EXCEPTION_REPORTER" ) DEFAULT_EXCEPTION_REPORTER_FILTER: str = ( global_settings.DEFAULT_EXCEPTION_REPORTER_FILTER ) TEST_RUNNER: str = global_settings.TEST_RUNNER - TEST_NON_SERIALIZED_APPS: list[str] = global_settings.TEST_NON_SERIALIZED_APPS + TEST_NON_SERIALIZED_APPS: List[str] = global_settings.TEST_NON_SERIALIZED_APPS FIXTURE_DIRS: types.FIXTURE_DIRS = global_settings.FIXTURE_DIRS # type: ignore STATICFILES_DIRS: types.STATICFILES_DIRS = ( global_settings.STATICFILES_DIRS # type: ignore ) STATICFILES_STORAGE: str = global_settings.STATICFILES_STORAGE - STATICFILES_FINDERS: list[str] = global_settings.STATICFILES_FINDERS - MIGRATION_MODULES: dict[str, str] = global_settings.MIGRATION_MODULES - SILENCED_SYSTEM_CHECKS: list[str] = global_settings.SILENCED_SYSTEM_CHECKS - SECURE_BROWSER_XSS_FILTER: bool | None = _get_default_setting( + STATICFILES_FINDERS: List[str] = global_settings.STATICFILES_FINDERS + MIGRATION_MODULES: Dict[str, str] = global_settings.MIGRATION_MODULES + SILENCED_SYSTEM_CHECKS: List[str] = global_settings.SILENCED_SYSTEM_CHECKS + SECURE_BROWSER_XSS_FILTER: Union[bool, None] = _get_default_setting( "SECURE_BROWSER_XSS_FILTER" ) SECURE_CONTENT_TYPE_NOSNIFF: bool = global_settings.SECURE_CONTENT_TYPE_NOSNIFF @@ -260,29 +264,29 @@ class PydanticSettings(BaseSettings): global_settings.SECURE_HSTS_INCLUDE_SUBDOMAINS ) SECURE_HSTS_PRELOAD: bool = global_settings.SECURE_HSTS_PRELOAD - SECURE_HSTS_SECONDS: int | None = global_settings.SECURE_HSTS_SECONDS + SECURE_HSTS_SECONDS: Union[int, None] = global_settings.SECURE_HSTS_SECONDS SECURE_REDIRECT_EXEMPT: types.SECURE_REDIRECT_EXEMPT = ( global_settings.SECURE_REDIRECT_EXEMPT ) SECURE_REFERRER_POLICY: types.SECURE_REFERRER_POLICY = _get_default_setting( "SECURE_REFERRER_POLICY" ) - SECURE_SSL_HOST: str | None = global_settings.SECURE_SSL_HOST + SECURE_SSL_HOST: Union[str, None] = global_settings.SECURE_SSL_HOST SECURE_SSL_REDIRECT: bool = global_settings.SECURE_SSL_REDIRECT - ROOT_URLCONF: str | None = None + ROOT_URLCONF: Union[str, None] = None - default_database_dsn: DatabaseDsn | None = Field( + default_database_dsn: Union[DatabaseDsn, None] = Field( env="DATABASE_URL", configure_database="default" ) - default_cache_dsn: CacheDsn | None = Field( + default_cache_dsn: Union[CacheDsn, None] = Field( env="CACHE_URL", configure_cache="default" ) class Config: env_prefix = "DJANGO_" - @validator("DATABASES", pre=True) + @validator("DATABASES", pre=True, allow_reuse=True) def parse_databases(cls, databases: dict) -> dict: """ Parse any databases specified as DSNs into DatabaseModel objects. @@ -297,7 +301,7 @@ def parse_databases(cls, databases: dict) -> dict: parsed_databases[key] = value return parsed_databases - @root_validator + @root_validator(allow_reuse=True) def set_default_database(cls, values: dict) -> dict: """ Set the default database if it is not already set and is provided by @@ -306,13 +310,13 @@ def set_default_database(cls, values: dict) -> dict: DATABASES = values["DATABASES"] for db_key, attr in cls._get_dsn_fields(field_extra="configure_database"): if not DATABASES.get(db_key): - database_dsn: DatabaseDsn | None = values[attr] + database_dsn: Union[DatabaseDsn, None] = values[attr] if database_dsn: DATABASES[db_key] = database_dsn.to_settings_model() del values[attr] return values - @root_validator + @root_validator(allow_reuse=True) def set_default_cache(cls, values: dict) -> dict: """ Set the default cache if it is not already set and is provided by @@ -320,7 +324,7 @@ def set_default_cache(cls, values: dict) -> dict: """ CACHES = values.get("CACHES") or {} for cache_key, attr in cls._get_dsn_fields(field_extra="configure_cache"): - cache_dsn: CacheDsn | None = values[attr] + cache_dsn: Union[CacheDsn, None] = values[attr] if cache_dsn: CACHES = values.setdefault("CACHES", {}) CACHES[cache_key] = cache_dsn.to_settings_model() @@ -328,7 +332,7 @@ def set_default_cache(cls, values: dict) -> dict: return values @classmethod - def _get_dsn_fields(cls, field_extra: str) -> Iterable[tuple[str, str]]: + def _get_dsn_fields(cls, field_extra: str) -> Iterable[Tuple[str, str]]: field: ModelField for field in cls.__fields__.values(): db_key = field.field_info.extra.get(field_extra) @@ -356,7 +360,7 @@ class is being used then ROOT_URLCONF and WSGI_APPLICATION will be prepended if not values["ROOT_URLCONF"]: values["ROOT_URLCONF"] = f"{project_module_name}.urls" - base_dir: Path | None = values["BASE_DIR"] + base_dir: Union[Path, None] = values["BASE_DIR"] if not base_dir: ancestor = module.__name__.count(".") path = Path(inspect.getfile(module)).resolve() diff --git a/pydantic_settings/types.py b/pydantic_settings/types.py index eec0aa3..7d585ee 100644 --- a/pydantic_settings/types.py +++ b/pydantic_settings/types.py @@ -18,7 +18,7 @@ "strict-origin-when-cross-origin", "unsafe-url", ] -RegexList = List[Pattern[str]] +RegexList = List[Pattern] SameSiteOptions = Literal["Lax", "Strict", "None"] ABSOLUTE_URL_OVERRIDES = Dict[str, Callable] From 10d612332bf990099aae306aca95cc1d4d00e614 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Tue, 6 Dec 2022 15:29:17 +1300 Subject: [PATCH 9/9] LOGGING_CONFIG can be None, as mentioned in #142 --- pydantic_settings/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic_settings/settings.py b/pydantic_settings/settings.py index 28f4b74..95f37e4 100644 --- a/pydantic_settings/settings.py +++ b/pydantic_settings/settings.py @@ -238,7 +238,7 @@ class PydanticSettings(BaseSettings): CSRF_TRUSTED_ORIGINS: List[str] = global_settings.CSRF_TRUSTED_ORIGINS CSRF_USE_SESSIONS: bool = global_settings.CSRF_USE_SESSIONS MESSAGE_STORAGE: str = global_settings.MESSAGE_STORAGE - LOGGING_CONFIG: str = global_settings.LOGGING_CONFIG + LOGGING_CONFIG: Union[str, None] = global_settings.LOGGING_CONFIG LOGGING: dict = global_settings.LOGGING DEFAULT_EXCEPTION_REPORTER: Union[str, None] = _get_default_setting( "DEFAULT_EXCEPTION_REPORTER"