From a7151c64ffdd881448e318332702a6e118bae2ee Mon Sep 17 00:00:00 2001 From: marcus Date: Thu, 7 Aug 2025 19:38:35 +0200 Subject: [PATCH 01/13] [django-environ] Run create_baseline_stubs.py script. --- pyrightconfig.stricter.json | 1 + stubs/django-environ/METADATA.toml | 2 + stubs/django-environ/environ/__init__.pyi | 8 ++ stubs/django-environ/environ/compat.pyi | 13 +++ stubs/django-environ/environ/environ.pyi | 98 +++++++++++++++++++ .../environ/fileaware_mapping.pyi | 13 +++ 6 files changed, 135 insertions(+) create mode 100644 stubs/django-environ/METADATA.toml create mode 100644 stubs/django-environ/environ/__init__.pyi create mode 100644 stubs/django-environ/environ/compat.pyi create mode 100644 stubs/django-environ/environ/environ.pyi create mode 100644 stubs/django-environ/environ/fileaware_mapping.pyi diff --git a/pyrightconfig.stricter.json b/pyrightconfig.stricter.json index a3f072faea14..8a4589b24804 100644 --- a/pyrightconfig.stricter.json +++ b/pyrightconfig.stricter.json @@ -33,6 +33,7 @@ "stubs/cffi", "stubs/dateparser", "stubs/defusedxml", + "stubs/django-environ", "stubs/docker", "stubs/docutils", "stubs/Flask-SocketIO", diff --git a/stubs/django-environ/METADATA.toml b/stubs/django-environ/METADATA.toml new file mode 100644 index 000000000000..a5b672314efb --- /dev/null +++ b/stubs/django-environ/METADATA.toml @@ -0,0 +1,2 @@ +version = "0.12.*" +upstream_repository = "https://github.com/joke2k/django-environ" diff --git a/stubs/django-environ/environ/__init__.pyi b/stubs/django-environ/environ/__init__.pyi new file mode 100644 index 000000000000..ef9595f7b8ba --- /dev/null +++ b/stubs/django-environ/environ/__init__.pyi @@ -0,0 +1,8 @@ +from .environ import * + +__version__: str +__author_email__: str +__maintainer__: str +__maintainer_email__: str +__url__: str +__description__: str diff --git a/stubs/django-environ/environ/compat.pyi b/stubs/django-environ/environ/compat.pyi new file mode 100644 index 000000000000..2597463c849e --- /dev/null +++ b/stubs/django-environ/environ/compat.pyi @@ -0,0 +1,13 @@ +from _typeshed import Incomplete + +from django.core.exceptions import ImproperlyConfigured as ImproperlyConfigured + +class ImproperlyConfigured(Exception): ... + +def choose_rediscache_driver(): ... +def choose_postgres_driver(): ... +def choose_pymemcache_driver(): ... + +REDIS_DRIVER: Incomplete +DJANGO_POSTGRES: Incomplete +PYMEMCACHE_DRIVER: Incomplete diff --git a/stubs/django-environ/environ/environ.pyi b/stubs/django-environ/environ/environ.pyi new file mode 100644 index 000000000000..e4d287ec3902 --- /dev/null +++ b/stubs/django-environ/environ/environ.pyi @@ -0,0 +1,98 @@ +from _typeshed import Incomplete +from urllib.parse import ParseResult + +Openable: Incomplete +logger: Incomplete + +class NoValue: ... + +class Env: + ENVIRON: Incomplete + NOTSET: Incomplete + BOOLEAN_TRUE_STRINGS: Incomplete + URL_CLASS = ParseResult + POSTGRES_FAMILY: Incomplete + DEFAULT_DATABASE_ENV: str + DB_SCHEMES: Incomplete + DEFAULT_CACHE_ENV: str + CACHE_SCHEMES: Incomplete + DEFAULT_EMAIL_ENV: str + EMAIL_SCHEMES: Incomplete + DEFAULT_SEARCH_ENV: str + SEARCH_SCHEMES: Incomplete + ELASTICSEARCH_FAMILY: Incomplete + CLOUDSQL: str + DEFAULT_CHANNELS_ENV: str + CHANNELS_SCHEMES: Incomplete + smart_cast: bool + escape_proxy: bool + prefix: str + scheme: Incomplete + def __init__(self, **scheme) -> None: ... + def __call__(self, var, cast: Incomplete | None = None, default=..., parse_default: bool = False): ... + def __contains__(self, var) -> bool: ... + def str(self, var, default=..., multiline: bool = False): ... + def bytes(self, var, default=..., encoding: str = "utf8"): ... + def bool(self, var, default=...): ... + def int(self, var, default=...): ... + def float(self, var, default=...): ... + def json(self, var, default=...): ... + def list(self, var, cast: Incomplete | None = None, default=...): ... + def tuple(self, var, cast: Incomplete | None = None, default=...): ... + def dict(self, var, cast=..., default=...): ... + def url(self, var, default=...): ... + def db_url(self, var="DATABASE_URL", default=..., engine: Incomplete | None = None): ... + db = db_url + def cache_url(self, var="CACHE_URL", default=..., backend: Incomplete | None = None): ... + cache = cache_url + def email_url(self, var="EMAIL_URL", default=..., backend: Incomplete | None = None): ... + email = email_url + def search_url(self, var="SEARCH_URL", default=..., engine: Incomplete | None = None): ... + def channels_url(self, var="CHANNELS_URL", default=..., backend: Incomplete | None = None): ... + channels = channels_url + def path(self, var, default=..., **kwargs): ... + def get_value(self, var, cast: Incomplete | None = None, default=..., parse_default: bool = False): ... + @classmethod + def parse_value(cls, value, cast): ... + @classmethod + def db_url_config(cls, url, engine: Incomplete | None = None): ... + @classmethod + def cache_url_config(cls, url, backend: Incomplete | None = None): ... + @classmethod + def email_url_config(cls, url, backend: Incomplete | None = None): ... + @classmethod + def channels_url_config(cls, url, backend: Incomplete | None = None): ... + @classmethod + def search_url_config(cls, url, engine: Incomplete | None = None): ... + @classmethod + def read_env( + cls, + env_file: Incomplete | None = None, + overwrite: bool = False, + parse_comments: bool = False, + encoding: str = "utf8", + **overrides, + ): ... + +class FileAwareEnv(Env): + ENVIRON: Incomplete + +class Path: + def path(self, *paths, **kwargs): ... + def file(self, name, *args, **kwargs): ... + @property + def root(self): ... + __root__: Incomplete + def __init__(self, start: str = "", *paths, **kwargs) -> None: ... + def __call__(self, *paths, **kwargs): ... + def __eq__(self, other): ... + def __ne__(self, other): ... + def __add__(self, other): ... + def __sub__(self, other): ... + def __invert__(self): ... + def __contains__(self, item) -> bool: ... + def __unicode__(self): ... + def __getitem__(self, *args, **kwargs): ... + def __fspath__(self): ... + def rfind(self, *args, **kwargs): ... + def find(self, *args, **kwargs): ... diff --git a/stubs/django-environ/environ/fileaware_mapping.pyi b/stubs/django-environ/environ/fileaware_mapping.pyi new file mode 100644 index 000000000000..7a3450501a8a --- /dev/null +++ b/stubs/django-environ/environ/fileaware_mapping.pyi @@ -0,0 +1,13 @@ +from _typeshed import Incomplete +from collections.abc import MutableMapping + +class FileAwareMapping(MutableMapping): + env: Incomplete + cache: Incomplete + files_cache: Incomplete + def __init__(self, env: Incomplete | None = None, cache: bool = True) -> None: ... + def __getitem__(self, key): ... + def __iter__(self): ... + def __len__(self) -> int: ... + def __setitem__(self, key, value) -> None: ... + def __delitem__(self, key) -> None: ... From 40c0da3edfae9ee592c2bd579e78dc1069461e78 Mon Sep 17 00:00:00 2001 From: marcus Date: Thu, 7 Aug 2025 20:15:56 +0200 Subject: [PATCH 02/13] [django-environ] Fix incomplete types. --- stubs/django-environ/environ/compat.pyi | 10 +- stubs/django-environ/environ/environ.pyi | 160 +++++++++++------- .../environ/fileaware_mapping.pyi | 19 +-- 3 files changed, 114 insertions(+), 75 deletions(-) diff --git a/stubs/django-environ/environ/compat.pyi b/stubs/django-environ/environ/compat.pyi index 2597463c849e..61f6a6290382 100644 --- a/stubs/django-environ/environ/compat.pyi +++ b/stubs/django-environ/environ/compat.pyi @@ -1,13 +1,9 @@ -from _typeshed import Incomplete - -from django.core.exceptions import ImproperlyConfigured as ImproperlyConfigured - class ImproperlyConfigured(Exception): ... def choose_rediscache_driver(): ... def choose_postgres_driver(): ... def choose_pymemcache_driver(): ... -REDIS_DRIVER: Incomplete -DJANGO_POSTGRES: Incomplete -PYMEMCACHE_DRIVER: Incomplete +REDIS_DRIVER: str +DJANGO_POSTGRES: str +PYMEMCACHE_DRIVER: str diff --git a/stubs/django-environ/environ/environ.pyi b/stubs/django-environ/environ/environ.pyi index e4d287ec3902..50a47b21df29 100644 --- a/stubs/django-environ/environ/environ.pyi +++ b/stubs/django-environ/environ/environ.pyi @@ -1,89 +1,133 @@ -from _typeshed import Incomplete +import os +from collections.abc import Callable +from logging import Logger +from typing import Any from urllib.parse import ParseResult -Openable: Incomplete -logger: Incomplete +from .fileaware_mapping import FileAwareMapping + +Openable = ... +logger: Logger class NoValue: ... +# Some type aliases to make our life easier +_Str = str +_Bytes = bytes +_Bool = bool +_Int = int +_Float = float +_List = list +_Tuple = tuple +_Dict = dict + +_Cast = Callable[[_Str], Any] +_Scheme = _Dict[_Str, _Cast | tuple[_Cast, Any]] +_BooleanTrueStrings = tuple[str, ...] + class Env: - ENVIRON: Incomplete - NOTSET: Incomplete - BOOLEAN_TRUE_STRINGS: Incomplete + ENVIRON: _Dict[_Str, _Str] + NOTSET: NoValue + BOOLEAN_TRUE_STRINGS: _BooleanTrueStrings URL_CLASS = ParseResult - POSTGRES_FAMILY: Incomplete - DEFAULT_DATABASE_ENV: str - DB_SCHEMES: Incomplete - DEFAULT_CACHE_ENV: str - CACHE_SCHEMES: Incomplete - DEFAULT_EMAIL_ENV: str - EMAIL_SCHEMES: Incomplete - DEFAULT_SEARCH_ENV: str - SEARCH_SCHEMES: Incomplete - ELASTICSEARCH_FAMILY: Incomplete - CLOUDSQL: str - DEFAULT_CHANNELS_ENV: str - CHANNELS_SCHEMES: Incomplete - smart_cast: bool - escape_proxy: bool - prefix: str - scheme: Incomplete - def __init__(self, **scheme) -> None: ... - def __call__(self, var, cast: Incomplete | None = None, default=..., parse_default: bool = False): ... - def __contains__(self, var) -> bool: ... - def str(self, var, default=..., multiline: bool = False): ... - def bytes(self, var, default=..., encoding: str = "utf8"): ... - def bool(self, var, default=...): ... - def int(self, var, default=...): ... - def float(self, var, default=...): ... - def json(self, var, default=...): ... - def list(self, var, cast: Incomplete | None = None, default=...): ... - def tuple(self, var, cast: Incomplete | None = None, default=...): ... - def dict(self, var, cast=..., default=...): ... - def url(self, var, default=...): ... - def db_url(self, var="DATABASE_URL", default=..., engine: Incomplete | None = None): ... + POSTGRES_FAMILY: _List[_Str] + DEFAULT_DATABASE_ENV: _Str = "DATABASE_URL" + DB_SCHEMES: _Dict[_Str, _Str] + DEFAULT_CACHE_ENV: _Str = "CACHE_URL" + CACHE_SCHEMES: _Dict[_Str, _Str] + DEFAULT_EMAIL_ENV: _Str = "EMAIL_URL" + EMAIL_SCHEMES: _Dict[_Str, _Str] + DEFAULT_SEARCH_ENV: _Str = "SEARCH_URL" + SEARCH_SCHEMES: _Dict[_Str, _Str] + ELASTICSEARCH_FAMILY: _List[_Str] + CLOUDSQL: _Str + DEFAULT_CHANNELS_ENV: _Str = "CHANNELS_URL" + CHANNELS_SCHEMES: _Dict[_Str, _Str] + smart_cast: _Bool + escape_proxy: _Bool + prefix: _Str + scheme: _Scheme + + def __init__(self, **scheme: _Scheme) -> None: ... + def __call__( + self, var: _Str, cast: _Cast | None = None, default: Any | NoValue = NOTSET, parse_default: _Bool = False + ) -> Any: ... + def __contains__(self, var: _Str) -> _Bool: ... + def str(self, var: _Str, default: _Str | NoValue = NOTSET, multiline: _Bool = False) -> _Str: ... + def bytes(self, var: _Str, default: _Bytes | NoValue = NOTSET, encoding: _Str = "utf8") -> _Bytes: ... + def bool(self, var: _Str, default: _Bool | NoValue = NOTSET) -> _Bool: ... + def int(self, var: _Str, default: _Int | NoValue = NOTSET) -> _Int: ... + def float(self, var: _Str, default: _Float | NoValue = NOTSET) -> _Float: ... + def json(self, var: _Str, default: Any | NoValue = NOTSET) -> Any: ... + def list(self, var: _Str, cast: _Cast | None = None, default: _List | NoValue = NOTSET) -> _List: ... + def tuple(self, var: _Str, cast: _Cast | None = None, default: _Tuple | NoValue = NOTSET) -> _Tuple: ... + def dict(self, var: _Str, cast: _Cast | None = None, default: _Dict | NoValue = NOTSET) -> _Dict: ... + def url(self, var: _Str, default: _Str | NoValue = NOTSET) -> _Str: ... + def db_url(self, var: _Str = DEFAULT_DATABASE_ENV, default: _Str | NoValue = NOTSET, engine: _Str | None = None) -> _Dict: ... + db = db_url - def cache_url(self, var="CACHE_URL", default=..., backend: Incomplete | None = None): ... + + def cache_url( + self, var: _Str = DEFAULT_CACHE_ENV, default: _Str | NoValue = NOTSET, backend: _Str | None = None + ) -> _Dict: ... + cache = cache_url - def email_url(self, var="EMAIL_URL", default=..., backend: Incomplete | None = None): ... + + def email_url( + self, var: _Str = DEFAULT_EMAIL_ENV, default: _Str | NoValue = NOTSET, backend: _Str | None = None + ) -> _Dict: ... + email = email_url - def search_url(self, var="SEARCH_URL", default=..., engine: Incomplete | None = None): ... - def channels_url(self, var="CHANNELS_URL", default=..., backend: Incomplete | None = None): ... + + def search_url( + self, var: _Str = DEFAULT_SEARCH_ENV, default: _Str | NoValue = NOTSET, engine: _Str | None = None + ) -> _Dict: ... + def channels_url( + self, var: _Str = DEFAULT_CHANNELS_ENV, default: _Str | NoValue = NOTSET, backend: _Str | None = None + ) -> _Dict: ... + channels = channels_url - def path(self, var, default=..., **kwargs): ... - def get_value(self, var, cast: Incomplete | None = None, default=..., parse_default: bool = False): ... + + def path(self, var: _Str, default: _Str | NoValue = NOTSET, **kwargs) -> Path: ... + def get_value( + self, var: _Str, cast: _Cast | None = None, default: Any | NoValue = NOTSET, parse_default: _Bool = False + ) -> Any: ... @classmethod - def parse_value(cls, value, cast): ... + def parse_value(cls, value: _Str, cast: _Cast) -> Any: ... @classmethod - def db_url_config(cls, url, engine: Incomplete | None = None): ... + def db_url_config(cls, url: _Str | ParseResult, engine: _Str | None = None) -> _Dict: ... @classmethod - def cache_url_config(cls, url, backend: Incomplete | None = None): ... + def cache_url_config(cls, url: _Str | ParseResult, backend: _Str | None = None) -> _Dict: ... @classmethod - def email_url_config(cls, url, backend: Incomplete | None = None): ... + def email_url_config(cls, url: _Str | ParseResult, backend: _Str | None = None) -> _Dict: ... @classmethod - def channels_url_config(cls, url, backend: Incomplete | None = None): ... + def channels_url_config(cls, url: _Str | ParseResult, backend: _Str | None = None) -> _Dict: ... @classmethod - def search_url_config(cls, url, engine: Incomplete | None = None): ... + def search_url_config(cls, url: _Str | ParseResult, engine: _Str | None = None) -> _Dict: ... @classmethod def read_env( cls, - env_file: Incomplete | None = None, - overwrite: bool = False, - parse_comments: bool = False, - encoding: str = "utf8", - **overrides, - ): ... + env_file: _Str | os.PathLike[_Str] | None = None, + overwrite: _Bool = False, + parse_comments: _Bool = False, + encoding: _Str = "utf8", + **overrides: _Dict[_Str, _Str], + ) -> None: ... class FileAwareEnv(Env): - ENVIRON: Incomplete + ENVIRON: FileAwareMapping + +_AnyStr = str | bytes class Path: def path(self, *paths, **kwargs): ... def file(self, name, *args, **kwargs): ... @property def root(self): ... - __root__: Incomplete - def __init__(self, start: str = "", *paths, **kwargs) -> None: ... + + __root__: _AnyStr + + def __init__(self, start="", *paths, **kwargs) -> None: ... def __call__(self, *paths, **kwargs): ... def __eq__(self, other): ... def __ne__(self, other): ... diff --git a/stubs/django-environ/environ/fileaware_mapping.pyi b/stubs/django-environ/environ/fileaware_mapping.pyi index 7a3450501a8a..cfb84331d7f9 100644 --- a/stubs/django-environ/environ/fileaware_mapping.pyi +++ b/stubs/django-environ/environ/fileaware_mapping.pyi @@ -1,13 +1,12 @@ -from _typeshed import Incomplete -from collections.abc import MutableMapping +from collections.abc import Iterator, MutableMapping class FileAwareMapping(MutableMapping): - env: Incomplete - cache: Incomplete - files_cache: Incomplete - def __init__(self, env: Incomplete | None = None, cache: bool = True) -> None: ... - def __getitem__(self, key): ... - def __iter__(self): ... + env: dict[str, str] + cache: bool + files_cache: dict[str, str] + def __init__(self, env: dict[str, str] | None = None, cache: bool = True) -> None: ... + def __getitem__(self, key: str) -> str: ... + def __iter__(self) -> Iterator[str]: ... def __len__(self) -> int: ... - def __setitem__(self, key, value) -> None: ... - def __delitem__(self, key) -> None: ... + def __setitem__(self, key: str, value: str) -> None: ... + def __delitem__(self, key: str) -> None: ... From a08916ee38438e3f271d67633d73dd361567900e Mon Sep 17 00:00:00 2001 From: marcus Date: Wed, 13 Aug 2025 16:09:34 +0200 Subject: [PATCH 03/13] [django-environ] Use generic types where applicable. --- stubs/django-environ/environ/environ.pyi | 30 +++++++++++++----------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/stubs/django-environ/environ/environ.pyi b/stubs/django-environ/environ/environ.pyi index 50a47b21df29..e63646b54e68 100644 --- a/stubs/django-environ/environ/environ.pyi +++ b/stubs/django-environ/environ/environ.pyi @@ -1,7 +1,8 @@ import os -from collections.abc import Callable +from collections.abc import Callable, Mapping from logging import Logger -from typing import Any +from typing import Any, TypeVar +from typing_extensions import TypeAlias from urllib.parse import ParseResult from .fileaware_mapping import FileAwareMapping @@ -21,8 +22,9 @@ _List = list _Tuple = tuple _Dict = dict -_Cast = Callable[[_Str], Any] -_Scheme = _Dict[_Str, _Cast | tuple[_Cast, Any]] +_T = TypeVar("_T") +_Cast: TypeAlias = Callable[[_Str], _T] +_SchemeValue: TypeAlias = _Cast[Any] | tuple[_Cast[Any], Any] _BooleanTrueStrings = tuple[str, ...] class Env: @@ -46,12 +48,12 @@ class Env: smart_cast: _Bool escape_proxy: _Bool prefix: _Str - scheme: _Scheme + scheme: Mapping[_Str, _SchemeValue] - def __init__(self, **scheme: _Scheme) -> None: ... + def __init__(self, **scheme: _SchemeValue) -> None: ... def __call__( - self, var: _Str, cast: _Cast | None = None, default: Any | NoValue = NOTSET, parse_default: _Bool = False - ) -> Any: ... + self, var: _Str, cast: _Cast[_T] | None = None, default: _T | NoValue = NOTSET, parse_default: _Bool = False + ) -> _T: ... def __contains__(self, var: _Str) -> _Bool: ... def str(self, var: _Str, default: _Str | NoValue = NOTSET, multiline: _Bool = False) -> _Str: ... def bytes(self, var: _Str, default: _Bytes | NoValue = NOTSET, encoding: _Str = "utf8") -> _Bytes: ... @@ -59,9 +61,9 @@ class Env: def int(self, var: _Str, default: _Int | NoValue = NOTSET) -> _Int: ... def float(self, var: _Str, default: _Float | NoValue = NOTSET) -> _Float: ... def json(self, var: _Str, default: Any | NoValue = NOTSET) -> Any: ... - def list(self, var: _Str, cast: _Cast | None = None, default: _List | NoValue = NOTSET) -> _List: ... - def tuple(self, var: _Str, cast: _Cast | None = None, default: _Tuple | NoValue = NOTSET) -> _Tuple: ... - def dict(self, var: _Str, cast: _Cast | None = None, default: _Dict | NoValue = NOTSET) -> _Dict: ... + def list(self, var: _Str, cast: _Cast[_List] | None = None, default: _List | NoValue = NOTSET) -> _List: ... + def tuple(self, var: _Str, cast: _Cast[_Tuple] | None = None, default: _Tuple | NoValue = NOTSET) -> _Tuple: ... + def dict(self, var: _Str, cast: _Cast[_Dict] | None = None, default: _Dict | NoValue = NOTSET) -> _Dict: ... def url(self, var: _Str, default: _Str | NoValue = NOTSET) -> _Str: ... def db_url(self, var: _Str = DEFAULT_DATABASE_ENV, default: _Str | NoValue = NOTSET, engine: _Str | None = None) -> _Dict: ... @@ -90,10 +92,10 @@ class Env: def path(self, var: _Str, default: _Str | NoValue = NOTSET, **kwargs) -> Path: ... def get_value( - self, var: _Str, cast: _Cast | None = None, default: Any | NoValue = NOTSET, parse_default: _Bool = False - ) -> Any: ... + self, var: _Str, cast: _Cast[_T] | None = None, default: _T | NoValue = NOTSET, parse_default: _Bool = False + ) -> _T: ... @classmethod - def parse_value(cls, value: _Str, cast: _Cast) -> Any: ... + def parse_value(cls, value: _Str, cast: _Cast[_T]) -> _T: ... @classmethod def db_url_config(cls, url: _Str | ParseResult, engine: _Str | None = None) -> _Dict: ... @classmethod From f090e9f3c7b144e2f9adfdae14dfecc8379832f8 Mon Sep 17 00:00:00 2001 From: marcus Date: Wed, 13 Aug 2025 16:43:17 +0200 Subject: [PATCH 04/13] [django-environ] Fix tests. --- stubs/django-environ/environ/environ.pyi | 54 ++++++++----------- .../environ/fileaware_mapping.pyi | 2 +- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/stubs/django-environ/environ/environ.pyi b/stubs/django-environ/environ/environ.pyi index e63646b54e68..a8e85222876b 100644 --- a/stubs/django-environ/environ/environ.pyi +++ b/stubs/django-environ/environ/environ.pyi @@ -1,5 +1,5 @@ import os -from collections.abc import Callable, Mapping +from collections.abc import Callable, Mapping, MutableMapping from logging import Logger from typing import Any, TypeVar from typing_extensions import TypeAlias @@ -25,10 +25,10 @@ _Dict = dict _T = TypeVar("_T") _Cast: TypeAlias = Callable[[_Str], _T] _SchemeValue: TypeAlias = _Cast[Any] | tuple[_Cast[Any], Any] -_BooleanTrueStrings = tuple[str, ...] +_BooleanTrueStrings: TypeAlias = tuple[str, ...] class Env: - ENVIRON: _Dict[_Str, _Str] + ENVIRON: MutableMapping[_Str, _Str] NOTSET: NoValue BOOLEAN_TRUE_STRINGS: _BooleanTrueStrings URL_CLASS = ParseResult @@ -52,47 +52,39 @@ class Env: def __init__(self, **scheme: _SchemeValue) -> None: ... def __call__( - self, var: _Str, cast: _Cast[_T] | None = None, default: _T | NoValue = NOTSET, parse_default: _Bool = False + self, var: _Str, cast: _Cast[_T] | None = None, default: _T | NoValue = ..., parse_default: _Bool = False ) -> _T: ... def __contains__(self, var: _Str) -> _Bool: ... - def str(self, var: _Str, default: _Str | NoValue = NOTSET, multiline: _Bool = False) -> _Str: ... - def bytes(self, var: _Str, default: _Bytes | NoValue = NOTSET, encoding: _Str = "utf8") -> _Bytes: ... - def bool(self, var: _Str, default: _Bool | NoValue = NOTSET) -> _Bool: ... - def int(self, var: _Str, default: _Int | NoValue = NOTSET) -> _Int: ... - def float(self, var: _Str, default: _Float | NoValue = NOTSET) -> _Float: ... - def json(self, var: _Str, default: Any | NoValue = NOTSET) -> Any: ... - def list(self, var: _Str, cast: _Cast[_List] | None = None, default: _List | NoValue = NOTSET) -> _List: ... - def tuple(self, var: _Str, cast: _Cast[_Tuple] | None = None, default: _Tuple | NoValue = NOTSET) -> _Tuple: ... - def dict(self, var: _Str, cast: _Cast[_Dict] | None = None, default: _Dict | NoValue = NOTSET) -> _Dict: ... - def url(self, var: _Str, default: _Str | NoValue = NOTSET) -> _Str: ... - def db_url(self, var: _Str = DEFAULT_DATABASE_ENV, default: _Str | NoValue = NOTSET, engine: _Str | None = None) -> _Dict: ... + def str(self, var: _Str, default: _Str | NoValue = ..., multiline: _Bool = False) -> _Str: ... + def bytes(self, var: _Str, default: _Bytes | NoValue = ..., encoding: _Str = "utf8") -> _Bytes: ... + def bool(self, var: _Str, default: _Bool | NoValue = ...) -> _Bool: ... + def int(self, var: _Str, default: _Int | NoValue = ...) -> _Int: ... + def float(self, var: _Str, default: _Float | NoValue = ...) -> _Float: ... + def json(self, var: _Str, default: Any | NoValue = ...) -> Any: ... + def list(self, var: _Str, cast: _Cast[_List] | None = None, default: _List | NoValue = ...) -> _List: ... + def tuple(self, var: _Str, cast: _Cast[_Tuple] | None = None, default: _Tuple | NoValue = ...) -> _Tuple: ... + def dict(self, var: _Str, cast: _Cast[_Dict] | None = None, default: _Dict | NoValue = ...) -> _Dict: ... + def url(self, var: _Str, default: _Str | NoValue = ...) -> _Str: ... + def db_url(self, var: _Str = ..., default: _Str | NoValue = ..., engine: _Str | None = None) -> _Dict: ... db = db_url - def cache_url( - self, var: _Str = DEFAULT_CACHE_ENV, default: _Str | NoValue = NOTSET, backend: _Str | None = None - ) -> _Dict: ... + def cache_url(self, var: _Str = ..., default: _Str | NoValue = ..., backend: _Str | None = None) -> _Dict: ... cache = cache_url - def email_url( - self, var: _Str = DEFAULT_EMAIL_ENV, default: _Str | NoValue = NOTSET, backend: _Str | None = None - ) -> _Dict: ... + def email_url(self, var: _Str = ..., default: _Str | NoValue = ..., backend: _Str | None = None) -> _Dict: ... email = email_url - def search_url( - self, var: _Str = DEFAULT_SEARCH_ENV, default: _Str | NoValue = NOTSET, engine: _Str | None = None - ) -> _Dict: ... - def channels_url( - self, var: _Str = DEFAULT_CHANNELS_ENV, default: _Str | NoValue = NOTSET, backend: _Str | None = None - ) -> _Dict: ... + def search_url(self, var: _Str = ..., default: _Str | NoValue = ..., engine: _Str | None = None) -> _Dict: ... + def channels_url(self, var: _Str = ..., default: _Str | NoValue = ..., backend: _Str | None = None) -> _Dict: ... channels = channels_url - def path(self, var: _Str, default: _Str | NoValue = NOTSET, **kwargs) -> Path: ... + def path(self, var: _Str, default: _Str | NoValue = ..., **kwargs) -> Path: ... def get_value( - self, var: _Str, cast: _Cast[_T] | None = None, default: _T | NoValue = NOTSET, parse_default: _Bool = False + self, var: _Str, cast: _Cast[_T] | None = None, default: _T | NoValue = ..., parse_default: _Bool = False ) -> _T: ... @classmethod def parse_value(cls, value: _Str, cast: _Cast[_T]) -> _T: ... @@ -119,15 +111,13 @@ class Env: class FileAwareEnv(Env): ENVIRON: FileAwareMapping -_AnyStr = str | bytes - class Path: def path(self, *paths, **kwargs): ... def file(self, name, *args, **kwargs): ... @property def root(self): ... - __root__: _AnyStr + __root__: str def __init__(self, start="", *paths, **kwargs) -> None: ... def __call__(self, *paths, **kwargs): ... diff --git a/stubs/django-environ/environ/fileaware_mapping.pyi b/stubs/django-environ/environ/fileaware_mapping.pyi index cfb84331d7f9..4fa28de5ec94 100644 --- a/stubs/django-environ/environ/fileaware_mapping.pyi +++ b/stubs/django-environ/environ/fileaware_mapping.pyi @@ -1,6 +1,6 @@ from collections.abc import Iterator, MutableMapping -class FileAwareMapping(MutableMapping): +class FileAwareMapping(MutableMapping[str, str]): env: dict[str, str] cache: bool files_cache: dict[str, str] From 11da833ce4c062d5a9ad3b47ed199392632e7273 Mon Sep 17 00:00:00 2001 From: marcus Date: Wed, 13 Aug 2025 17:08:02 +0200 Subject: [PATCH 05/13] [django-environ] Improve typing for Path. --- stubs/django-environ/environ/environ.pyi | 45 ++++++++++++++---------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/stubs/django-environ/environ/environ.pyi b/stubs/django-environ/environ/environ.pyi index a8e85222876b..2632dacce982 100644 --- a/stubs/django-environ/environ/environ.pyi +++ b/stubs/django-environ/environ/environ.pyi @@ -1,8 +1,8 @@ import os from collections.abc import Callable, Mapping, MutableMapping from logging import Logger -from typing import Any, TypeVar -from typing_extensions import TypeAlias +from typing import IO, Any, SupportsIndex, TypedDict, TypeVar, overload +from typing_extensions import TypeAlias, Unpack from urllib.parse import ParseResult from .fileaware_mapping import FileAwareMapping @@ -82,7 +82,7 @@ class Env: channels = channels_url - def path(self, var: _Str, default: _Str | NoValue = ..., **kwargs) -> Path: ... + def path(self, var: _Str, default: _Str | NoValue = ..., **kwargs: Unpack[_PathKwargs]) -> Path: ... def get_value( self, var: _Str, cast: _Cast[_T] | None = None, default: _T | NoValue = ..., parse_default: _Bool = False ) -> _T: ... @@ -111,24 +111,31 @@ class Env: class FileAwareEnv(Env): ENVIRON: FileAwareMapping +class _PathKwargs(TypedDict, total=False): + required: bool + is_file: bool + class Path: - def path(self, *paths, **kwargs): ... - def file(self, name, *args, **kwargs): ... + def path(self, *paths: str, **kwargs: Unpack[_PathKwargs]) -> Path: ... + def file(self, name: str, *args, **kwargs) -> IO[str]: ... @property - def root(self): ... + def root(self) -> str: ... __root__: str - def __init__(self, start="", *paths, **kwargs) -> None: ... - def __call__(self, *paths, **kwargs): ... - def __eq__(self, other): ... - def __ne__(self, other): ... - def __add__(self, other): ... - def __sub__(self, other): ... - def __invert__(self): ... - def __contains__(self, item) -> bool: ... - def __unicode__(self): ... - def __getitem__(self, *args, **kwargs): ... - def __fspath__(self): ... - def rfind(self, *args, **kwargs): ... - def find(self, *args, **kwargs): ... + def __init__(self, start: str = "", *paths: str, **kwargs: Unpack[_PathKwargs]) -> None: ... + def __call__(self, *paths: str, **kwargs: Unpack[_PathKwargs]) -> str: ... + def __eq__(self, other: object | Path) -> bool: ... + def __ne__(self, other: object | Path) -> bool: ... + def __add__(self, other: object | Path) -> Path: ... + def __sub__(self, other: int | str) -> Path: ... + def __invert__(self) -> Path: ... + def __contains__(self, item: Path) -> bool: ... + def __unicode__(self) -> str: ... + @overload + def __getitem__(self, key: SupportsIndex, /) -> str: ... + @overload + def __getitem__(self, key: slice, /) -> str: ... + def __fspath__(self) -> str: ... + def rfind(self, s: str, sub: str, start: int = 0, end: int = ...) -> int: ... + def find(self, s: str, sub: str, start: int = 0, end: int = ...) -> int: ... From 5b5a7bb092264d81808ad6febd637ec96e56f690 Mon Sep 17 00:00:00 2001 From: marcus Date: Wed, 13 Aug 2025 18:11:17 +0200 Subject: [PATCH 06/13] [django-environ] Fix stubtests. --- stubs/django-environ/environ/__init__.pyi | 3 ++ stubs/django-environ/environ/environ.pyi | 38 +++++++++++------------ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/stubs/django-environ/environ/__init__.pyi b/stubs/django-environ/environ/__init__.pyi index ef9595f7b8ba..9e3db7f7d2ad 100644 --- a/stubs/django-environ/environ/__init__.pyi +++ b/stubs/django-environ/environ/__init__.pyi @@ -1,6 +1,9 @@ +from .compat import DJANGO_POSTGRES as DJANGO_POSTGRES, PYMEMCACHE_DRIVER as PYMEMCACHE_DRIVER, REDIS_DRIVER as REDIS_DRIVER from .environ import * +__copyright__: str __version__: str +__license__: str __author_email__: str __maintainer__: str __maintainer_email__: str diff --git a/stubs/django-environ/environ/environ.pyi b/stubs/django-environ/environ/environ.pyi index 2632dacce982..7334e854391b 100644 --- a/stubs/django-environ/environ/environ.pyi +++ b/stubs/django-environ/environ/environ.pyi @@ -1,13 +1,13 @@ import os from collections.abc import Callable, Mapping, MutableMapping from logging import Logger -from typing import IO, Any, SupportsIndex, TypedDict, TypeVar, overload +from typing import IO, Any, ClassVar, SupportsIndex, TypedDict, TypeVar, overload from typing_extensions import TypeAlias, Unpack from urllib.parse import ParseResult from .fileaware_mapping import FileAwareMapping -Openable = ... +Openable: tuple[type, ...] logger: Logger class NoValue: ... @@ -29,22 +29,22 @@ _BooleanTrueStrings: TypeAlias = tuple[str, ...] class Env: ENVIRON: MutableMapping[_Str, _Str] - NOTSET: NoValue - BOOLEAN_TRUE_STRINGS: _BooleanTrueStrings - URL_CLASS = ParseResult - POSTGRES_FAMILY: _List[_Str] - DEFAULT_DATABASE_ENV: _Str = "DATABASE_URL" - DB_SCHEMES: _Dict[_Str, _Str] - DEFAULT_CACHE_ENV: _Str = "CACHE_URL" - CACHE_SCHEMES: _Dict[_Str, _Str] - DEFAULT_EMAIL_ENV: _Str = "EMAIL_URL" - EMAIL_SCHEMES: _Dict[_Str, _Str] - DEFAULT_SEARCH_ENV: _Str = "SEARCH_URL" - SEARCH_SCHEMES: _Dict[_Str, _Str] - ELASTICSEARCH_FAMILY: _List[_Str] - CLOUDSQL: _Str - DEFAULT_CHANNELS_ENV: _Str = "CHANNELS_URL" - CHANNELS_SCHEMES: _Dict[_Str, _Str] + NOTSET: ClassVar[NoValue] + BOOLEAN_TRUE_STRINGS: ClassVar[_BooleanTrueStrings] + URL_CLASS: ClassVar[type[ParseResult]] + POSTGRES_FAMILY: ClassVar[_List[_Str]] + DEFAULT_DATABASE_ENV: ClassVar[_Str] = "DATABASE_URL" + DB_SCHEMES: ClassVar[_Dict[_Str, _Str]] + DEFAULT_CACHE_ENV: ClassVar[_Str] = "CACHE_URL" + CACHE_SCHEMES: ClassVar[_Dict[_Str, _Str]] + DEFAULT_EMAIL_ENV: ClassVar[_Str] = "EMAIL_URL" + EMAIL_SCHEMES: ClassVar[_Dict[_Str, _Str]] + DEFAULT_SEARCH_ENV: ClassVar[_Str] = "SEARCH_URL" + SEARCH_SCHEMES: ClassVar[_Dict[_Str, _Str]] + ELASTICSEARCH_FAMILY: ClassVar[_List[_Str]] + CLOUDSQL: ClassVar[_Str] + DEFAULT_CHANNELS_ENV: ClassVar[_Str] = "CHANNELS_URL" + CHANNELS_SCHEMES: ClassVar[_Dict[_Str, _Str]] smart_cast: _Bool escape_proxy: _Bool prefix: _Str @@ -63,7 +63,7 @@ class Env: def json(self, var: _Str, default: Any | NoValue = ...) -> Any: ... def list(self, var: _Str, cast: _Cast[_List] | None = None, default: _List | NoValue = ...) -> _List: ... def tuple(self, var: _Str, cast: _Cast[_Tuple] | None = None, default: _Tuple | NoValue = ...) -> _Tuple: ... - def dict(self, var: _Str, cast: _Cast[_Dict] | None = None, default: _Dict | NoValue = ...) -> _Dict: ... + def dict(self, var: _Str, cast: _Cast[_Dict] | None = ..., default: _Dict | NoValue = ...) -> _Dict: ... def url(self, var: _Str, default: _Str | NoValue = ...) -> _Str: ... def db_url(self, var: _Str = ..., default: _Str | NoValue = ..., engine: _Str | None = None) -> _Dict: ... From 88ae28a368b7bdba64ae02d834a3d57fc5d3f634 Mon Sep 17 00:00:00 2001 From: marcus Date: Mon, 18 Aug 2025 16:54:24 +0200 Subject: [PATCH 07/13] [django-environ] Apply suggestions. --- stubs/django-environ/METADATA.toml | 1 + stubs/django-environ/environ/__init__.pyi | 18 +-- stubs/django-environ/environ/compat.pyi | 16 ++- stubs/django-environ/environ/environ.pyi | 131 +++++++++++++++--- .../environ/fileaware_mapping.pyi | 4 +- 5 files changed, 136 insertions(+), 34 deletions(-) diff --git a/stubs/django-environ/METADATA.toml b/stubs/django-environ/METADATA.toml index a5b672314efb..0855b8dd696e 100644 --- a/stubs/django-environ/METADATA.toml +++ b/stubs/django-environ/METADATA.toml @@ -1,2 +1,3 @@ version = "0.12.*" upstream_repository = "https://github.com/joke2k/django-environ" +requires = ["django-stubs"] diff --git a/stubs/django-environ/environ/__init__.pyi b/stubs/django-environ/environ/__init__.pyi index 9e3db7f7d2ad..6c6ed9823e69 100644 --- a/stubs/django-environ/environ/__init__.pyi +++ b/stubs/django-environ/environ/__init__.pyi @@ -1,11 +1,13 @@ +from typing import Final + from .compat import DJANGO_POSTGRES as DJANGO_POSTGRES, PYMEMCACHE_DRIVER as PYMEMCACHE_DRIVER, REDIS_DRIVER as REDIS_DRIVER from .environ import * -__copyright__: str -__version__: str -__license__: str -__author_email__: str -__maintainer__: str -__maintainer_email__: str -__url__: str -__description__: str +__copyright__: Final[str] +__version__: Final[str] +__license__: Final[str] +__author_email__: Final[str] +__maintainer__: Final[str] +__maintainer_email__: Final[str] +__url__: Final[str] +__description__: Final[str] diff --git a/stubs/django-environ/environ/compat.pyi b/stubs/django-environ/environ/compat.pyi index 61f6a6290382..f07be669bd10 100644 --- a/stubs/django-environ/environ/compat.pyi +++ b/stubs/django-environ/environ/compat.pyi @@ -1,9 +1,11 @@ -class ImproperlyConfigured(Exception): ... +from typing import Final -def choose_rediscache_driver(): ... -def choose_postgres_driver(): ... -def choose_pymemcache_driver(): ... +from django.core.exceptions import ImproperlyConfigured as ImproperlyConfigured -REDIS_DRIVER: str -DJANGO_POSTGRES: str -PYMEMCACHE_DRIVER: str +def choose_rediscache_driver() -> str: ... +def choose_postgres_driver() -> str: ... +def choose_pymemcache_driver() -> str: ... + +REDIS_DRIVER: Final[str] +DJANGO_POSTGRES: Final[str] +PYMEMCACHE_DRIVER: Final[str] diff --git a/stubs/django-environ/environ/environ.pyi b/stubs/django-environ/environ/environ.pyi index 7334e854391b..d37059618215 100644 --- a/stubs/django-environ/environ/environ.pyi +++ b/stubs/django-environ/environ/environ.pyi @@ -1,8 +1,8 @@ -import os +from _typeshed import StrPath from collections.abc import Callable, Mapping, MutableMapping from logging import Logger -from typing import IO, Any, ClassVar, SupportsIndex, TypedDict, TypeVar, overload -from typing_extensions import TypeAlias, Unpack +from typing import IO, Any, ClassVar, SupportsIndex, TypedDict, TypeVar, overload, type_check_only +from typing_extensions import Required, TypeAlias, Unpack from urllib.parse import ParseResult from .fileaware_mapping import FileAwareMapping @@ -27,6 +27,101 @@ _Cast: TypeAlias = Callable[[_Str], _T] _SchemeValue: TypeAlias = _Cast[Any] | tuple[_Cast[Any], Any] _BooleanTrueStrings: TypeAlias = tuple[str, ...] +_EmptyDict: TypeAlias = dict[object, object] # stands for {} + +@type_check_only +class PathKwargs(TypedDict, total=False): + required: bool + is_file: bool + +# https://docs.djangoproject.com/en/5.2/ref/settings/#std-setting-DATABASES +@type_check_only +class MemoryDbConfig(TypedDict): + ENGINE: str + NAME: str + +@type_check_only +class DbConfig(MemoryDbConfig, total=False): + USER: Required[str] + PASSWORD: Required[str] + HOST: Required[str] + PORT: Required[int | str] + # Additional base options read from queryParams + CONN_MAX_AGE: int + CONN_HEALTH_CHECKS: bool + ATOMIC_REQUESTS: bool + AUTOCOMMIT: bool + DISABLE_SERVER_SIDE_CURSORS: bool + # Remaining options read from queryParams + OPTIONS: dict[str, Any] + +@type_check_only +class EmailConfig(TypedDict, total=False): + EMAIL_FILE_PATH: Required[str] + EMAIL_HOST_USER: Required[str] + EMAIL_HOST_PASSWORD: Required[str] + EMAIL_HOST: Required[str] + EMAIL_PORT: Required[int] + EMAIL_BACKEND: Required[str] + # Additional base options read from queryParams + EMAIL_USE_TLS: bool + EMAIL_USE_SSL: bool + # Remaining options read from queryParams + OPTIONS: dict[str, Any] + +# https://github.com/django/channels +@type_check_only +class ChannelsConfig(TypedDict, total=False): + BACKEND: Required[str] + CONFIG: dict[str, Any] + +# https://github.com/django-haystack/django-haystack +@type_check_only +class SimpleSearchConfig(TypedDict, total=False): + ENGINE: Required[str] + # Common search params + EXCLUDED_INDEXES: list[str] + INCLUDE_SPELLING: bool + BATCH_SIZE: int + +@type_check_only +class SolrSearchConfig(SimpleSearchConfig, total=False): + URL: Required[str] + TIMEOUT: int + KWARGS: dict[str, Any] + +@type_check_only +class ElasticsearchSearchConfig(SimpleSearchConfig, total=False): + URL: Required[str] + TIMEOUT: int + KWARGS: dict[str, Any] + INDEX_NAME: str + +@type_check_only +class WhooshSearchConfig(SimpleSearchConfig, total=False): + PATH: Required[str] + STORAGE: str + POST_LIMIT: int + +@type_check_only +class XapianSearchConfig(SimpleSearchConfig, total=False): + PATH: Required[str] + FLAGS: int + +# https://docs.djangoproject.com/en/5.2/ref/settings/#std-setting-CACHES +@type_check_only +class CacheConfig(TypedDict, total=False): + BACKEND: Required[str] + LOCATION: Required[str] + # Additional base options read from queryParams + KEY_FUNCTION: str + KEY_PREFIX: str + BINARY: str + TIMEOUT: int + VERSION: int + # Remaining options read from queryParams + OPTIONS: dict[str, Any] + class Env: ENVIRON: MutableMapping[_Str, _Str] NOTSET: ClassVar[NoValue] @@ -65,24 +160,30 @@ class Env: def tuple(self, var: _Str, cast: _Cast[_Tuple] | None = None, default: _Tuple | NoValue = ...) -> _Tuple: ... def dict(self, var: _Str, cast: _Cast[_Dict] | None = ..., default: _Dict | NoValue = ...) -> _Dict: ... def url(self, var: _Str, default: _Str | NoValue = ...) -> _Str: ... - def db_url(self, var: _Str = ..., default: _Str | NoValue = ..., engine: _Str | None = None) -> _Dict: ... + def db_url( + self, var: _Str = ..., default: _Str | NoValue = ..., engine: _Str | None = None + ) -> MemoryDbConfig | DbConfig | _EmptyDict: ... db = db_url - def cache_url(self, var: _Str = ..., default: _Str | NoValue = ..., backend: _Str | None = None) -> _Dict: ... + def cache_url( + self, var: _Str = ..., default: _Str | NoValue = ..., backend: _Str | None = None + ) -> CacheConfig | _EmptyDict: ... cache = cache_url - def email_url(self, var: _Str = ..., default: _Str | NoValue = ..., backend: _Str | None = None) -> _Dict: ... + def email_url(self, var: _Str = ..., default: _Str | NoValue = ..., backend: _Str | None = None) -> EmailConfig: ... email = email_url - def search_url(self, var: _Str = ..., default: _Str | NoValue = ..., engine: _Str | None = None) -> _Dict: ... - def channels_url(self, var: _Str = ..., default: _Str | NoValue = ..., backend: _Str | None = None) -> _Dict: ... + def search_url( + self, var: _Str = ..., default: _Str | NoValue = ..., engine: _Str | None = None + ) -> SimpleSearchConfig | SolrSearchConfig | ElasticsearchSearchConfig | WhooshSearchConfig | XapianSearchConfig: ... + def channels_url(self, var: _Str = ..., default: _Str | NoValue = ..., backend: _Str | None = None) -> ChannelsConfig: ... channels = channels_url - def path(self, var: _Str, default: _Str | NoValue = ..., **kwargs: Unpack[_PathKwargs]) -> Path: ... + def path(self, var: _Str, default: _Str | NoValue = ..., **kwargs: Unpack[PathKwargs]) -> Path: ... def get_value( self, var: _Str, cast: _Cast[_T] | None = None, default: _T | NoValue = ..., parse_default: _Bool = False ) -> _T: ... @@ -101,7 +202,7 @@ class Env: @classmethod def read_env( cls, - env_file: _Str | os.PathLike[_Str] | None = None, + env_file: StrPath | None = None, overwrite: _Bool = False, parse_comments: _Bool = False, encoding: _Str = "utf8", @@ -111,20 +212,16 @@ class Env: class FileAwareEnv(Env): ENVIRON: FileAwareMapping -class _PathKwargs(TypedDict, total=False): - required: bool - is_file: bool - class Path: - def path(self, *paths: str, **kwargs: Unpack[_PathKwargs]) -> Path: ... + def path(self, *paths: str, **kwargs: Unpack[PathKwargs]) -> Path: ... def file(self, name: str, *args, **kwargs) -> IO[str]: ... @property def root(self) -> str: ... __root__: str - def __init__(self, start: str = "", *paths: str, **kwargs: Unpack[_PathKwargs]) -> None: ... - def __call__(self, *paths: str, **kwargs: Unpack[_PathKwargs]) -> str: ... + def __init__(self, start: str = "", *paths: str, **kwargs: Unpack[PathKwargs]) -> None: ... + def __call__(self, *paths: str, **kwargs: Unpack[PathKwargs]) -> str: ... def __eq__(self, other: object | Path) -> bool: ... def __ne__(self, other: object | Path) -> bool: ... def __add__(self, other: object | Path) -> Path: ... diff --git a/stubs/django-environ/environ/fileaware_mapping.pyi b/stubs/django-environ/environ/fileaware_mapping.pyi index 4fa28de5ec94..308bedda320c 100644 --- a/stubs/django-environ/environ/fileaware_mapping.pyi +++ b/stubs/django-environ/environ/fileaware_mapping.pyi @@ -1,10 +1,10 @@ from collections.abc import Iterator, MutableMapping class FileAwareMapping(MutableMapping[str, str]): - env: dict[str, str] + env: MutableMapping[str, str] cache: bool files_cache: dict[str, str] - def __init__(self, env: dict[str, str] | None = None, cache: bool = True) -> None: ... + def __init__(self, env: MutableMapping[str, str] | None = None, cache: bool = True) -> None: ... def __getitem__(self, key: str) -> str: ... def __iter__(self) -> Iterator[str]: ... def __len__(self) -> int: ... From e0977d69b5d422669f8300f601b3c5b31fd8712d Mon Sep 17 00:00:00 2001 From: marcus Date: Mon, 18 Aug 2025 17:16:53 +0200 Subject: [PATCH 08/13] [django-environ] Use import aliases for builtins. --- stubs/django-environ/environ/environ.pyi | 122 +++++++++++------------ 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/stubs/django-environ/environ/environ.pyi b/stubs/django-environ/environ/environ.pyi index d37059618215..0a959a5cd511 100644 --- a/stubs/django-environ/environ/environ.pyi +++ b/stubs/django-environ/environ/environ.pyi @@ -1,4 +1,16 @@ from _typeshed import StrPath + +# Use aliases to avoid name conflicts with Path methods +from builtins import ( + bool as _bool, + bytes as _bytes, + dict as _dict, + float as _float, + int as _int, + list as _list, + str as _str, + tuple as _tuple, +) from collections.abc import Callable, Mapping, MutableMapping from logging import Logger from typing import IO, Any, ClassVar, SupportsIndex, TypedDict, TypeVar, overload, type_check_only @@ -12,21 +24,9 @@ logger: Logger class NoValue: ... -# Some type aliases to make our life easier -_Str = str -_Bytes = bytes -_Bool = bool -_Int = int -_Float = float -_List = list -_Tuple = tuple -_Dict = dict - _T = TypeVar("_T") -_Cast: TypeAlias = Callable[[_Str], _T] +_Cast: TypeAlias = Callable[[str], _T] _SchemeValue: TypeAlias = _Cast[Any] | tuple[_Cast[Any], Any] -_BooleanTrueStrings: TypeAlias = tuple[str, ...] - _EmptyDict: TypeAlias = dict[object, object] # stands for {} @type_check_only @@ -123,90 +123,90 @@ class CacheConfig(TypedDict, total=False): OPTIONS: dict[str, Any] class Env: - ENVIRON: MutableMapping[_Str, _Str] + ENVIRON: MutableMapping[_str, _str] NOTSET: ClassVar[NoValue] - BOOLEAN_TRUE_STRINGS: ClassVar[_BooleanTrueStrings] + BOOLEAN_TRUE_STRINGS: ClassVar[_tuple[str, ...]] URL_CLASS: ClassVar[type[ParseResult]] - POSTGRES_FAMILY: ClassVar[_List[_Str]] - DEFAULT_DATABASE_ENV: ClassVar[_Str] = "DATABASE_URL" - DB_SCHEMES: ClassVar[_Dict[_Str, _Str]] - DEFAULT_CACHE_ENV: ClassVar[_Str] = "CACHE_URL" - CACHE_SCHEMES: ClassVar[_Dict[_Str, _Str]] - DEFAULT_EMAIL_ENV: ClassVar[_Str] = "EMAIL_URL" - EMAIL_SCHEMES: ClassVar[_Dict[_Str, _Str]] - DEFAULT_SEARCH_ENV: ClassVar[_Str] = "SEARCH_URL" - SEARCH_SCHEMES: ClassVar[_Dict[_Str, _Str]] - ELASTICSEARCH_FAMILY: ClassVar[_List[_Str]] - CLOUDSQL: ClassVar[_Str] - DEFAULT_CHANNELS_ENV: ClassVar[_Str] = "CHANNELS_URL" - CHANNELS_SCHEMES: ClassVar[_Dict[_Str, _Str]] - smart_cast: _Bool - escape_proxy: _Bool - prefix: _Str - scheme: Mapping[_Str, _SchemeValue] + POSTGRES_FAMILY: ClassVar[_list[_str]] + DEFAULT_DATABASE_ENV: ClassVar[_str] = "DATABASE_URL" + DB_SCHEMES: ClassVar[_dict[_str, _str]] + DEFAULT_CACHE_ENV: ClassVar[_str] = "CACHE_URL" + CACHE_SCHEMES: ClassVar[_dict[_str, _str]] + DEFAULT_EMAIL_ENV: ClassVar[_str] = "EMAIL_URL" + EMAIL_SCHEMES: ClassVar[_dict[_str, _str]] + DEFAULT_SEARCH_ENV: ClassVar[_str] = "SEARCH_URL" + SEARCH_SCHEMES: ClassVar[_dict[_str, _str]] + ELASTICSEARCH_FAMILY: ClassVar[_list[_str]] + CLOUDSQL: ClassVar[_str] + DEFAULT_CHANNELS_ENV: ClassVar[_str] = "CHANNELS_URL" + CHANNELS_SCHEMES: ClassVar[_dict[_str, _str]] + smart_cast: _bool + escape_proxy: _bool + prefix: _str + scheme: Mapping[_str, _SchemeValue] def __init__(self, **scheme: _SchemeValue) -> None: ... def __call__( - self, var: _Str, cast: _Cast[_T] | None = None, default: _T | NoValue = ..., parse_default: _Bool = False + self, var: _str, cast: _Cast[_T] | None = None, default: _T | NoValue = ..., parse_default: _bool = False ) -> _T: ... - def __contains__(self, var: _Str) -> _Bool: ... - def str(self, var: _Str, default: _Str | NoValue = ..., multiline: _Bool = False) -> _Str: ... - def bytes(self, var: _Str, default: _Bytes | NoValue = ..., encoding: _Str = "utf8") -> _Bytes: ... - def bool(self, var: _Str, default: _Bool | NoValue = ...) -> _Bool: ... - def int(self, var: _Str, default: _Int | NoValue = ...) -> _Int: ... - def float(self, var: _Str, default: _Float | NoValue = ...) -> _Float: ... - def json(self, var: _Str, default: Any | NoValue = ...) -> Any: ... - def list(self, var: _Str, cast: _Cast[_List] | None = None, default: _List | NoValue = ...) -> _List: ... - def tuple(self, var: _Str, cast: _Cast[_Tuple] | None = None, default: _Tuple | NoValue = ...) -> _Tuple: ... - def dict(self, var: _Str, cast: _Cast[_Dict] | None = ..., default: _Dict | NoValue = ...) -> _Dict: ... - def url(self, var: _Str, default: _Str | NoValue = ...) -> _Str: ... + def __contains__(self, var: _str) -> _bool: ... + def str(self, var: _str, default: _str | NoValue = ..., multiline: _bool = False) -> _str: ... + def bytes(self, var: _str, default: _bytes | NoValue = ..., encoding: _str = "utf8") -> _bytes: ... + def bool(self, var: _str, default: _bool | NoValue = ...) -> _bool: ... + def int(self, var: _str, default: _int | NoValue = ...) -> _int: ... + def float(self, var: _str, default: _float | NoValue = ...) -> _float: ... + def json(self, var: _str, default: Any | NoValue = ...) -> Any: ... + def list(self, var: _str, cast: _Cast[_list] | None = None, default: _list | NoValue = ...) -> _list: ... + def tuple(self, var: _str, cast: _Cast[_tuple] | None = None, default: _tuple | NoValue = ...) -> _tuple: ... + def dict(self, var: _str, cast: _Cast[_dict] | None = ..., default: _dict | NoValue = ...) -> _dict: ... + def url(self, var: _str, default: _str | NoValue = ...) -> _str: ... def db_url( - self, var: _Str = ..., default: _Str | NoValue = ..., engine: _Str | None = None + self, var: _str = ..., default: _str | NoValue = ..., engine: _str | None = None ) -> MemoryDbConfig | DbConfig | _EmptyDict: ... db = db_url def cache_url( - self, var: _Str = ..., default: _Str | NoValue = ..., backend: _Str | None = None + self, var: _str = ..., default: _str | NoValue = ..., backend: _str | None = None ) -> CacheConfig | _EmptyDict: ... cache = cache_url - def email_url(self, var: _Str = ..., default: _Str | NoValue = ..., backend: _Str | None = None) -> EmailConfig: ... + def email_url(self, var: _str = ..., default: _str | NoValue = ..., backend: _str | None = None) -> EmailConfig: ... email = email_url def search_url( - self, var: _Str = ..., default: _Str | NoValue = ..., engine: _Str | None = None + self, var: _str = ..., default: _str | NoValue = ..., engine: _str | None = None ) -> SimpleSearchConfig | SolrSearchConfig | ElasticsearchSearchConfig | WhooshSearchConfig | XapianSearchConfig: ... - def channels_url(self, var: _Str = ..., default: _Str | NoValue = ..., backend: _Str | None = None) -> ChannelsConfig: ... + def channels_url(self, var: _str = ..., default: _str | NoValue = ..., backend: _str | None = None) -> ChannelsConfig: ... channels = channels_url - def path(self, var: _Str, default: _Str | NoValue = ..., **kwargs: Unpack[PathKwargs]) -> Path: ... + def path(self, var: _str, default: _str | NoValue = ..., **kwargs: Unpack[PathKwargs]) -> Path: ... def get_value( - self, var: _Str, cast: _Cast[_T] | None = None, default: _T | NoValue = ..., parse_default: _Bool = False + self, var: _str, cast: _Cast[_T] | None = None, default: _T | NoValue = ..., parse_default: _bool = False ) -> _T: ... @classmethod - def parse_value(cls, value: _Str, cast: _Cast[_T]) -> _T: ... + def parse_value(cls, value: _str, cast: _Cast[_T]) -> _T: ... @classmethod - def db_url_config(cls, url: _Str | ParseResult, engine: _Str | None = None) -> _Dict: ... + def db_url_config(cls, url: _str | ParseResult, engine: _str | None = None) -> _dict: ... @classmethod - def cache_url_config(cls, url: _Str | ParseResult, backend: _Str | None = None) -> _Dict: ... + def cache_url_config(cls, url: _str | ParseResult, backend: _str | None = None) -> _dict: ... @classmethod - def email_url_config(cls, url: _Str | ParseResult, backend: _Str | None = None) -> _Dict: ... + def email_url_config(cls, url: _str | ParseResult, backend: _str | None = None) -> _dict: ... @classmethod - def channels_url_config(cls, url: _Str | ParseResult, backend: _Str | None = None) -> _Dict: ... + def channels_url_config(cls, url: _str | ParseResult, backend: _str | None = None) -> _dict: ... @classmethod - def search_url_config(cls, url: _Str | ParseResult, engine: _Str | None = None) -> _Dict: ... + def search_url_config(cls, url: _str | ParseResult, engine: _str | None = None) -> _dict: ... @classmethod def read_env( cls, env_file: StrPath | None = None, - overwrite: _Bool = False, - parse_comments: _Bool = False, - encoding: _Str = "utf8", - **overrides: _Dict[_Str, _Str], + overwrite: _bool = False, + parse_comments: _bool = False, + encoding: _str = "utf8", + **overrides: _dict[_str, _str], ) -> None: ... class FileAwareEnv(Env): From fe8b2f4d871ce41253c132dd04ba0384b2bba68c Mon Sep 17 00:00:00 2001 From: marcus Date: Mon, 18 Aug 2025 18:36:15 +0200 Subject: [PATCH 09/13] [django-environ] Add overloads for various cast arguments types. --- stubs/django-environ/environ/environ.pyi | 84 +++++++++++++++++++++++- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/stubs/django-environ/environ/environ.pyi b/stubs/django-environ/environ/environ.pyi index 0a959a5cd511..87f529c84d4c 100644 --- a/stubs/django-environ/environ/environ.pyi +++ b/stubs/django-environ/environ/environ.pyi @@ -29,6 +29,12 @@ _Cast: TypeAlias = Callable[[str], _T] _SchemeValue: TypeAlias = _Cast[Any] | tuple[_Cast[Any], Any] _EmptyDict: TypeAlias = dict[object, object] # stands for {} +@type_check_only +class CastDict(TypedDict, total=False): + key: _Cast[Any] + value: _Cast[Any] + cast: dict[str, _Cast[Any]] + @type_check_only class PathKwargs(TypedDict, total=False): required: bool @@ -146,9 +152,34 @@ class Env: scheme: Mapping[_str, _SchemeValue] def __init__(self, **scheme: _SchemeValue) -> None: ... + @overload def __call__( self, var: _str, cast: _Cast[_T] | None = None, default: _T | NoValue = ..., parse_default: _bool = False ) -> _T: ... + @overload + def __call__( + self, var: _str, cast: _list[_Cast[_T]], default: _list[_T] | NoValue = ..., parse_default: _bool = False + ) -> _list[_T]: ... + @overload + def __call__( + self, var: _str, cast: _tuple[_Cast[_T]], default: _tuple[_T] | NoValue = ..., parse_default: _bool = False + ) -> _tuple[_T]: ... + @overload + def __call__( + self, var: _str, cast: CastDict, default: _dict | NoValue = ..., parse_default: _bool = False + ) -> _dict[Any, Any]: ... + @overload + def __call__( + self, var: _str, cast: type[_list], default: _list[_str] | NoValue = ..., parse_default: _bool = False + ) -> _list[_str]: ... + @overload + def __call__( + self, var: _str, cast: type[_tuple], default: _tuple[_str, ...] | NoValue = ..., parse_default: _bool = False + ) -> _tuple[_str, ...]: ... + @overload + def __call__( + self, var: _str, cast: type[_dict], default: _dict[_str, _str] | NoValue = ..., parse_default: _bool = False + ) -> _dict[_str, _str]: ... def __contains__(self, var: _str) -> _bool: ... def str(self, var: _str, default: _str | NoValue = ..., multiline: _bool = False) -> _str: ... def bytes(self, var: _str, default: _bytes | NoValue = ..., encoding: _str = "utf8") -> _bytes: ... @@ -156,9 +187,9 @@ class Env: def int(self, var: _str, default: _int | NoValue = ...) -> _int: ... def float(self, var: _str, default: _float | NoValue = ...) -> _float: ... def json(self, var: _str, default: Any | NoValue = ...) -> Any: ... - def list(self, var: _str, cast: _Cast[_list] | None = None, default: _list | NoValue = ...) -> _list: ... - def tuple(self, var: _str, cast: _Cast[_tuple] | None = None, default: _tuple | NoValue = ...) -> _tuple: ... - def dict(self, var: _str, cast: _Cast[_dict] | None = ..., default: _dict | NoValue = ...) -> _dict: ... + def list(self, var: _str, cast: _Cast[_T] | None = None, default: _list[_T] | NoValue = ...) -> _list[_T]: ... + def tuple(self, var: _str, cast: _Cast[_T] | None = None, default: _tuple[_T] | NoValue = ...) -> _tuple[_T]: ... + def dict(self, var: _str, cast: _Cast[_T] | None = ..., default: _dict[Any, _T] | NoValue = ...) -> _dict[Any, _T]: ... def url(self, var: _str, default: _str | NoValue = ...) -> _str: ... def db_url( self, var: _str = ..., default: _str | NoValue = ..., engine: _str | None = None @@ -184,12 +215,59 @@ class Env: channels = channels_url def path(self, var: _str, default: _str | NoValue = ..., **kwargs: Unpack[PathKwargs]) -> Path: ... + @overload def get_value( self, var: _str, cast: _Cast[_T] | None = None, default: _T | NoValue = ..., parse_default: _bool = False ) -> _T: ... + @overload + def get_value( + self, var: _str, cast: _list[_Cast[_T]], default: _list[_T] | NoValue = ..., parse_default: _bool = False + ) -> _list[_T]: ... + @overload + def get_value( + self, var: _str, cast: _tuple[_Cast[_T]], default: _tuple[_T] | NoValue = ..., parse_default: _bool = False + ) -> _tuple[_T]: ... + @overload + def get_value( + self, var: _str, cast: CastDict, default: _dict | NoValue = ..., parse_default: _bool = False + ) -> _dict[Any, Any]: ... + @overload + def get_value( + self, var: _str, cast: type[_list], default: _list[_str] | NoValue = ..., parse_default: _bool = False + ) -> _list[_str]: ... + @overload + def get_value( + self, var: _str, cast: type[_tuple], default: _tuple[_str, ...] | NoValue = ..., parse_default: _bool = False + ) -> _tuple[_str, ...]: ... + @overload + def get_value( + self, var: _str, cast: type[_dict], default: _dict[_str, _str] | NoValue = ..., parse_default: _bool = False + ) -> _dict[_str, _str]: ... @classmethod + @overload + def parse_value(cls, value: _str, cast: None) -> _str: ... + @classmethod + @overload def parse_value(cls, value: _str, cast: _Cast[_T]) -> _T: ... @classmethod + @overload + def parse_value(cls, value: _str, cast: _list[_Cast[_T]]) -> _list[_T]: ... + @classmethod + @overload + def parse_value(cls, value: _str, cast: _tuple[_Cast[_T], ...]) -> _tuple[_T]: ... + @classmethod + @overload + def parse_value(cls, value: _str, cast: CastDict) -> _dict[Any, Any]: ... + @classmethod + @overload + def parse_value(cls, value: _str, cast: type[_list]) -> _list[_str]: ... + @classmethod + @overload + def parse_value(cls, value: _str, cast: type[_tuple]) -> _tuple[_str]: ... + @classmethod + @overload + def parse_value(cls, value: _str, cast: type[_dict]) -> _dict[_str, _str]: ... + @classmethod def db_url_config(cls, url: _str | ParseResult, engine: _str | None = None) -> _dict: ... @classmethod def cache_url_config(cls, url: _str | ParseResult, backend: _str | None = None) -> _dict: ... From 73b40124c39b92a93a57f03039118cab411a6062 Mon Sep 17 00:00:00 2001 From: marcus Date: Mon, 18 Aug 2025 19:03:30 +0200 Subject: [PATCH 10/13] [django-environ] Add missing type arguments (pyright). --- stubs/django-environ/environ/environ.pyi | 39 +++++++++++++----------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/stubs/django-environ/environ/environ.pyi b/stubs/django-environ/environ/environ.pyi index 87f529c84d4c..b38eb03d11dc 100644 --- a/stubs/django-environ/environ/environ.pyi +++ b/stubs/django-environ/environ/environ.pyi @@ -166,19 +166,19 @@ class Env: ) -> _tuple[_T]: ... @overload def __call__( - self, var: _str, cast: CastDict, default: _dict | NoValue = ..., parse_default: _bool = False + self, var: _str, cast: CastDict, default: _dict[Any, Any] | NoValue = ..., parse_default: _bool = False ) -> _dict[Any, Any]: ... @overload def __call__( - self, var: _str, cast: type[_list], default: _list[_str] | NoValue = ..., parse_default: _bool = False + self, var: _str, cast: type[_list[Any]], default: _list[_str] | NoValue = ..., parse_default: _bool = False ) -> _list[_str]: ... @overload def __call__( - self, var: _str, cast: type[_tuple], default: _tuple[_str, ...] | NoValue = ..., parse_default: _bool = False + self, var: _str, cast: type[_tuple[Any, ...]], default: _tuple[_str, ...] | NoValue = ..., parse_default: _bool = False ) -> _tuple[_str, ...]: ... @overload def __call__( - self, var: _str, cast: type[_dict], default: _dict[_str, _str] | NoValue = ..., parse_default: _bool = False + self, var: _str, cast: type[_dict[Any, Any]], default: _dict[_str, _str] | NoValue = ..., parse_default: _bool = False ) -> _dict[_str, _str]: ... def __contains__(self, var: _str) -> _bool: ... def str(self, var: _str, default: _str | NoValue = ..., multiline: _bool = False) -> _str: ... @@ -189,7 +189,10 @@ class Env: def json(self, var: _str, default: Any | NoValue = ...) -> Any: ... def list(self, var: _str, cast: _Cast[_T] | None = None, default: _list[_T] | NoValue = ...) -> _list[_T]: ... def tuple(self, var: _str, cast: _Cast[_T] | None = None, default: _tuple[_T] | NoValue = ...) -> _tuple[_T]: ... - def dict(self, var: _str, cast: _Cast[_T] | None = ..., default: _dict[Any, _T] | NoValue = ...) -> _dict[Any, _T]: ... + @overload + def dict(self, var: _str, cast: None = ..., default: _dict[_str, Any] | NoValue = ...) -> _dict[_str, Any]: ... + @overload + def dict(self, var: _str, cast: _Cast[_T], default: _dict[Any, _T] | NoValue = ...) -> _dict[Any, _T]: ... def url(self, var: _str, default: _str | NoValue = ...) -> _str: ... def db_url( self, var: _str = ..., default: _str | NoValue = ..., engine: _str | None = None @@ -229,19 +232,19 @@ class Env: ) -> _tuple[_T]: ... @overload def get_value( - self, var: _str, cast: CastDict, default: _dict | NoValue = ..., parse_default: _bool = False + self, var: _str, cast: CastDict, default: _dict[Any, Any] | NoValue = ..., parse_default: _bool = False ) -> _dict[Any, Any]: ... @overload def get_value( - self, var: _str, cast: type[_list], default: _list[_str] | NoValue = ..., parse_default: _bool = False + self, var: _str, cast: type[_list[Any]], default: _list[_str] | NoValue = ..., parse_default: _bool = False ) -> _list[_str]: ... @overload def get_value( - self, var: _str, cast: type[_tuple], default: _tuple[_str, ...] | NoValue = ..., parse_default: _bool = False + self, var: _str, cast: type[_tuple[Any, ...]], default: _tuple[_str, ...] | NoValue = ..., parse_default: _bool = False ) -> _tuple[_str, ...]: ... @overload def get_value( - self, var: _str, cast: type[_dict], default: _dict[_str, _str] | NoValue = ..., parse_default: _bool = False + self, var: _str, cast: type[_dict[Any, Any]], default: _dict[_str, _str] | NoValue = ..., parse_default: _bool = False ) -> _dict[_str, _str]: ... @classmethod @overload @@ -260,23 +263,25 @@ class Env: def parse_value(cls, value: _str, cast: CastDict) -> _dict[Any, Any]: ... @classmethod @overload - def parse_value(cls, value: _str, cast: type[_list]) -> _list[_str]: ... + def parse_value(cls, value: _str, cast: type[_list[Any]]) -> _list[_str]: ... @classmethod @overload - def parse_value(cls, value: _str, cast: type[_tuple]) -> _tuple[_str]: ... + def parse_value(cls, value: _str, cast: type[_tuple[Any, ...]]) -> _tuple[_str]: ... @classmethod @overload - def parse_value(cls, value: _str, cast: type[_dict]) -> _dict[_str, _str]: ... + def parse_value(cls, value: _str, cast: type[_dict[Any, Any]]) -> _dict[_str, _str]: ... @classmethod - def db_url_config(cls, url: _str | ParseResult, engine: _str | None = None) -> _dict: ... + def db_url_config(cls, url: _str | ParseResult, engine: _str | None = None) -> MemoryDbConfig | DbConfig | _EmptyDict: ... @classmethod - def cache_url_config(cls, url: _str | ParseResult, backend: _str | None = None) -> _dict: ... + def cache_url_config(cls, url: _str | ParseResult, backend: _str | None = None) -> CacheConfig | _EmptyDict: ... @classmethod - def email_url_config(cls, url: _str | ParseResult, backend: _str | None = None) -> _dict: ... + def email_url_config(cls, url: _str | ParseResult, backend: _str | None = None) -> EmailConfig: ... @classmethod - def channels_url_config(cls, url: _str | ParseResult, backend: _str | None = None) -> _dict: ... + def channels_url_config(cls, url: _str | ParseResult, backend: _str | None = None) -> ChannelsConfig: ... @classmethod - def search_url_config(cls, url: _str | ParseResult, engine: _str | None = None) -> _dict: ... + def search_url_config( + cls, url: _str | ParseResult, engine: _str | None = None + ) -> SimpleSearchConfig | SolrSearchConfig | ElasticsearchSearchConfig | WhooshSearchConfig | XapianSearchConfig: ... @classmethod def read_env( cls, From 1326a7af8410b1f6064e2909a199cff96a89c21d Mon Sep 17 00:00:00 2001 From: marcus Date: Wed, 20 Aug 2025 11:15:00 +0200 Subject: [PATCH 11/13] [django-environ] Remove django-stubs dependency. --- stubs/django-environ/METADATA.toml | 1 - stubs/django-environ/environ/compat.pyi | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stubs/django-environ/METADATA.toml b/stubs/django-environ/METADATA.toml index 0855b8dd696e..a5b672314efb 100644 --- a/stubs/django-environ/METADATA.toml +++ b/stubs/django-environ/METADATA.toml @@ -1,3 +1,2 @@ version = "0.12.*" upstream_repository = "https://github.com/joke2k/django-environ" -requires = ["django-stubs"] diff --git a/stubs/django-environ/environ/compat.pyi b/stubs/django-environ/environ/compat.pyi index f07be669bd10..0eef96d3db51 100644 --- a/stubs/django-environ/environ/compat.pyi +++ b/stubs/django-environ/environ/compat.pyi @@ -1,6 +1,7 @@ from typing import Final -from django.core.exceptions import ImproperlyConfigured as ImproperlyConfigured +# reexports ImproperlyConfigured from django.core.exceptions if available +class ImproperlyConfigured(Exception): ... def choose_rediscache_driver() -> str: ... def choose_postgres_driver() -> str: ... From f8a7a3833c079774b08fc7fa9ea16552f76c61b7 Mon Sep 17 00:00:00 2001 From: marcus Date: Mon, 25 Aug 2025 17:10:25 +0200 Subject: [PATCH 12/13] [django-environ] Use generics for CastDict. Comment on usage of Any type. --- stubs/django-environ/environ/environ.pyi | 66 +++++++++++++----------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/stubs/django-environ/environ/environ.pyi b/stubs/django-environ/environ/environ.pyi index b38eb03d11dc..d5645f7e4a11 100644 --- a/stubs/django-environ/environ/environ.pyi +++ b/stubs/django-environ/environ/environ.pyi @@ -13,7 +13,7 @@ from builtins import ( ) from collections.abc import Callable, Mapping, MutableMapping from logging import Logger -from typing import IO, Any, ClassVar, SupportsIndex, TypedDict, TypeVar, overload, type_check_only +from typing import IO, Any, ClassVar, Generic, SupportsIndex, TypedDict, TypeVar, overload, type_check_only from typing_extensions import Required, TypeAlias, Unpack from urllib.parse import ParseResult @@ -24,16 +24,17 @@ logger: Logger class NoValue: ... -_T = TypeVar("_T") -_Cast: TypeAlias = Callable[[str], _T] +_T1 = TypeVar("_T1") +_T2 = TypeVar("_T2") +_Cast: TypeAlias = Callable[[str], _T1] _SchemeValue: TypeAlias = _Cast[Any] | tuple[_Cast[Any], Any] _EmptyDict: TypeAlias = dict[object, object] # stands for {} @type_check_only -class CastDict(TypedDict, total=False): - key: _Cast[Any] - value: _Cast[Any] - cast: dict[str, _Cast[Any]] +class CastDict(TypedDict, Generic[_T1, _T2], total=False): + key: _Cast[_T1] + value: _Cast[_T2] + cast: dict[str, _Cast[Any]] # value cast by key @type_check_only class PathKwargs(TypedDict, total=False): @@ -154,21 +155,22 @@ class Env: def __init__(self, **scheme: _SchemeValue) -> None: ... @overload def __call__( - self, var: _str, cast: _Cast[_T] | None = None, default: _T | NoValue = ..., parse_default: _bool = False - ) -> _T: ... + self, var: _str, cast: _Cast[_T1] | None = None, default: _T1 | NoValue = ..., parse_default: _bool = False + ) -> _T1: ... @overload def __call__( - self, var: _str, cast: _list[_Cast[_T]], default: _list[_T] | NoValue = ..., parse_default: _bool = False - ) -> _list[_T]: ... + self, var: _str, cast: _list[_Cast[_T1]], default: _list[_T1] | NoValue = ..., parse_default: _bool = False + ) -> _list[_T1]: ... @overload def __call__( - self, var: _str, cast: _tuple[_Cast[_T]], default: _tuple[_T] | NoValue = ..., parse_default: _bool = False - ) -> _tuple[_T]: ... + self, var: _str, cast: _tuple[_Cast[_T1]], default: _tuple[_T1] | NoValue = ..., parse_default: _bool = False + ) -> _tuple[_T1]: ... @overload def __call__( - self, var: _str, cast: CastDict, default: _dict[Any, Any] | NoValue = ..., parse_default: _bool = False - ) -> _dict[Any, Any]: ... + self, var: _str, cast: CastDict[_T1, _T2], default: _dict[_T1, _T2] | NoValue = ..., parse_default: _bool = False + ) -> _dict[_T1, _T2]: ... @overload + # Any (subclass of) list/tuple/dict builtin types are valid for cast. def __call__( self, var: _str, cast: type[_list[Any]], default: _list[_str] | NoValue = ..., parse_default: _bool = False ) -> _list[_str]: ... @@ -187,12 +189,12 @@ class Env: def int(self, var: _str, default: _int | NoValue = ...) -> _int: ... def float(self, var: _str, default: _float | NoValue = ...) -> _float: ... def json(self, var: _str, default: Any | NoValue = ...) -> Any: ... - def list(self, var: _str, cast: _Cast[_T] | None = None, default: _list[_T] | NoValue = ...) -> _list[_T]: ... - def tuple(self, var: _str, cast: _Cast[_T] | None = None, default: _tuple[_T] | NoValue = ...) -> _tuple[_T]: ... + def list(self, var: _str, cast: _Cast[_T1] | None = None, default: _list[_T1] | NoValue = ...) -> _list[_T1]: ... + def tuple(self, var: _str, cast: _Cast[_T1] | None = None, default: _tuple[_T1] | NoValue = ...) -> _tuple[_T1]: ... @overload def dict(self, var: _str, cast: None = ..., default: _dict[_str, Any] | NoValue = ...) -> _dict[_str, Any]: ... @overload - def dict(self, var: _str, cast: _Cast[_T], default: _dict[Any, _T] | NoValue = ...) -> _dict[Any, _T]: ... + def dict(self, var: _str, cast: _Cast[_T1], default: _dict[Any, _T1] | NoValue = ...) -> _dict[Any, _T1]: ... def url(self, var: _str, default: _str | NoValue = ...) -> _str: ... def db_url( self, var: _str = ..., default: _str | NoValue = ..., engine: _str | None = None @@ -220,21 +222,22 @@ class Env: def path(self, var: _str, default: _str | NoValue = ..., **kwargs: Unpack[PathKwargs]) -> Path: ... @overload def get_value( - self, var: _str, cast: _Cast[_T] | None = None, default: _T | NoValue = ..., parse_default: _bool = False - ) -> _T: ... + self, var: _str, cast: _Cast[_T1] | None = None, default: _T1 | NoValue = ..., parse_default: _bool = False + ) -> _T1: ... @overload def get_value( - self, var: _str, cast: _list[_Cast[_T]], default: _list[_T] | NoValue = ..., parse_default: _bool = False - ) -> _list[_T]: ... + self, var: _str, cast: _list[_Cast[_T1]], default: _list[_T1] | NoValue = ..., parse_default: _bool = False + ) -> _list[_T1]: ... @overload def get_value( - self, var: _str, cast: _tuple[_Cast[_T]], default: _tuple[_T] | NoValue = ..., parse_default: _bool = False - ) -> _tuple[_T]: ... + self, var: _str, cast: _tuple[_Cast[_T1]], default: _tuple[_T1] | NoValue = ..., parse_default: _bool = False + ) -> _tuple[_T1]: ... @overload def get_value( - self, var: _str, cast: CastDict, default: _dict[Any, Any] | NoValue = ..., parse_default: _bool = False - ) -> _dict[Any, Any]: ... + self, var: _str, cast: CastDict[_T1, _T2], default: _dict[_T1, _T2] | NoValue = ..., parse_default: _bool = False + ) -> _dict[_T1, _T2]: ... @overload + # Any (subclass of) list/tuple/dict builtin types are valid for cast. def get_value( self, var: _str, cast: type[_list[Any]], default: _list[_str] | NoValue = ..., parse_default: _bool = False ) -> _list[_str]: ... @@ -251,22 +254,23 @@ class Env: def parse_value(cls, value: _str, cast: None) -> _str: ... @classmethod @overload - def parse_value(cls, value: _str, cast: _Cast[_T]) -> _T: ... + def parse_value(cls, value: _str, cast: _Cast[_T1]) -> _T1: ... @classmethod @overload - def parse_value(cls, value: _str, cast: _list[_Cast[_T]]) -> _list[_T]: ... + def parse_value(cls, value: _str, cast: _list[_Cast[_T1]]) -> _list[_T1]: ... @classmethod @overload - def parse_value(cls, value: _str, cast: _tuple[_Cast[_T], ...]) -> _tuple[_T]: ... + def parse_value(cls, value: _str, cast: _tuple[_Cast[_T1], ...]) -> _tuple[_T1]: ... @classmethod @overload - def parse_value(cls, value: _str, cast: CastDict) -> _dict[Any, Any]: ... + def parse_value(cls, value: _str, cast: CastDict[_T1, _T2]) -> _dict[_T1, _T2]: ... @classmethod @overload + # Any (subclass of) list/tuple/dict builtin types are valid for cast. def parse_value(cls, value: _str, cast: type[_list[Any]]) -> _list[_str]: ... @classmethod @overload - def parse_value(cls, value: _str, cast: type[_tuple[Any, ...]]) -> _tuple[_str]: ... + def parse_value(cls, value: _str, cast: type[_tuple[Any, ...]]) -> _tuple[_str, ...]: ... @classmethod @overload def parse_value(cls, value: _str, cast: type[_dict[Any, Any]]) -> _dict[_str, _str]: ... From 076c8e28fcf6c4889fd1e440841b2823156c6043 Mon Sep 17 00:00:00 2001 From: marcus Date: Tue, 26 Aug 2025 15:52:01 +0200 Subject: [PATCH 13/13] [django-environ] Add tests for parse_value and fix/extend/improve some types. --- .../@tests/test_cases/check_parse_value.py | 64 +++++++ stubs/django-environ/environ/environ.pyi | 162 +++++++++++++----- 2 files changed, 184 insertions(+), 42 deletions(-) create mode 100644 stubs/django-environ/@tests/test_cases/check_parse_value.py diff --git a/stubs/django-environ/@tests/test_cases/check_parse_value.py b/stubs/django-environ/@tests/test_cases/check_parse_value.py new file mode 100644 index 000000000000..8b6c40838ebb --- /dev/null +++ b/stubs/django-environ/@tests/test_cases/check_parse_value.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from typing import Union +from typing_extensions import TypedDict, assert_type + +import environ + +env = environ.Env() + +assert_type(env.parse_value("just-a-value123", None), str) + +# builtin types +assert_type(env.parse_value("string", str), str) +assert_type(env.parse_value("TRUE", bool), bool) +assert_type(env.parse_value("2000", int), int) +assert_type(env.parse_value("-500.01", float), float) +assert_type(env.parse_value("first,second,", list), list[str]) +assert_type(env.parse_value("(first,second)", tuple), tuple[str, ...]) +assert_type(env.parse_value("a=first,b=second", dict), dict[str, str]) + + +# cast list values (first list element is used) +assert_type(env.parse_value("20.5,-0.2", [str]), list[str]) +assert_type(env.parse_value("20.5,-0.2", [bool]), list[bool]) +assert_type(env.parse_value("20.5,-0.2", [int]), list[int]) +assert_type(env.parse_value("20.5,-0.2", [float]), list[float]) + +# cast tuple values (first tuple element is used) +assert_type(env.parse_value("(20.5,-0.2)", (str,)), tuple[str, ...]) +assert_type(env.parse_value("(20.5,-0.2)", (bool,)), tuple[bool, ...]) +assert_type(env.parse_value("(20.5,-0.2)", (int,)), tuple[int, ...]) +assert_type(env.parse_value("(20.5,-0.2)", (float,)), tuple[float, ...]) + +# cast dict values +assert_type(env.parse_value("0=TRUE,99=FALSE", {}), dict[str, str]) +assert_type(env.parse_value("0=TRUE,99=FALSE", {"cast": {}}), dict[str, Union[str, object]]) +assert_type(env.parse_value("0=TRUE,99=FALSE", {"value": bool}), dict[str, bool]) +assert_type(env.parse_value("0=TRUE,99=FALSE", {"value": bool, "cast": {}}), dict[str, Union[bool, object]]) +assert_type(env.parse_value("0=TRUE,99=FALSE", {"key": int}), dict[int, str]) +assert_type(env.parse_value("0=TRUE,99=FALSE", {"key": int, "cast": {}}), dict[int, Union[str, object]]) +assert_type(env.parse_value("0=TRUE,99=FALSE", {"key": int, "value": bool}), dict[int, bool]) +assert_type(env.parse_value("0=TRUE,99=FALSE", {"key": int, "value": bool, "cast": {}}), dict[int, Union[bool, object]]) + + +# custom cast functions +def cast_float(x: str) -> float: + return float(x) + + +assert_type(env.parse_value("20.5", cast_float), float) + + +class Person(TypedDict): + first_name: str + last_name: str + age: int + + +def cast_person(v: str) -> Person: + parts = v.split(",") + return {"first_name": parts[0], "last_name": parts[1], "age": int(parts[2])} + + +assert_type(env.parse_value("Bob,Riveira,30", cast_person), Person) diff --git a/stubs/django-environ/environ/environ.pyi b/stubs/django-environ/environ/environ.pyi index d5645f7e4a11..5e4ce1d71ab1 100644 --- a/stubs/django-environ/environ/environ.pyi +++ b/stubs/django-environ/environ/environ.pyi @@ -1,4 +1,4 @@ -from _typeshed import StrPath +from _typeshed import Incomplete, StrPath # Use aliases to avoid name conflicts with Path methods from builtins import ( @@ -13,8 +13,8 @@ from builtins import ( ) from collections.abc import Callable, Mapping, MutableMapping from logging import Logger -from typing import IO, Any, ClassVar, Generic, SupportsIndex, TypedDict, TypeVar, overload, type_check_only -from typing_extensions import Required, TypeAlias, Unpack +from typing import IO, Any, ClassVar, Generic, Literal, SupportsIndex, TypedDict, TypeVar, overload, type_check_only +from typing_extensions import NotRequired, Required, TypeAlias, Unpack from urllib.parse import ParseResult from .fileaware_mapping import FileAwareMapping @@ -24,17 +24,66 @@ logger: Logger class NoValue: ... -_T1 = TypeVar("_T1") -_T2 = TypeVar("_T2") -_Cast: TypeAlias = Callable[[str], _T1] -_SchemeValue: TypeAlias = _Cast[Any] | tuple[_Cast[Any], Any] +_T = TypeVar("_T") +_KT = TypeVar("_KT") +_VT = TypeVar("_VT") + +_Cast: TypeAlias = Callable[[str], _T] +_SchemeValue: TypeAlias = _Cast[object] | tuple[_Cast[object], object] _EmptyDict: TypeAlias = dict[object, object] # stands for {} @type_check_only -class CastDict(TypedDict, Generic[_T1, _T2], total=False): - key: _Cast[_T1] - value: _Cast[_T2] - cast: dict[str, _Cast[Any]] # value cast by key +class CastDict(TypedDict, Generic[_KT, _VT], total=False): + key: _Cast[_KT] + value: _Cast[_VT] + # key-specific value casts + cast: dict[str, _Cast[object]] + +# One CastDict for each combination of 'key', 'value' and 'cast' (8 in total). +# Use auxiliary '_type' to make them distinguishable for type checkers. +@type_check_only +class CastDict000(TypedDict): + _type: NotRequired[Literal["000"]] + +@type_check_only +class CastDict001(TypedDict): + _type: NotRequired[Literal["001"]] + cast: dict[str, _Cast[object]] + +@type_check_only +class CastDict010(TypedDict, Generic[_VT]): + _type: NotRequired[Literal["010"]] + value: _Cast[_VT] + +@type_check_only +class CastDict011(TypedDict, Generic[_VT]): + _type: NotRequired[Literal["011"]] + value: _Cast[_VT] + cast: dict[str, _Cast[object]] + +@type_check_only +class CastDict100(TypedDict, Generic[_KT]): + _type: NotRequired[Literal["100"]] + key: _Cast[_KT] + +@type_check_only +class CastDict101(TypedDict, Generic[_KT]): + _type: NotRequired[Literal["101"]] + key: _Cast[_KT] + cast: dict[str, _Cast[object]] + +@type_check_only +class CastDict110(TypedDict, Generic[_KT, _VT]): + _type: NotRequired[Literal["110"]] + key: _Cast[_KT] + value: _Cast[_VT] + +@type_check_only +class CastDict111(TypedDict, Generic[_KT, _VT]): + _type: NotRequired[Literal["111"]] + key: _Cast[_KT] + value: _Cast[_VT] + cast: dict[str, _Cast[object]] @type_check_only class PathKwargs(TypedDict, total=False): @@ -60,7 +109,7 @@ class DbConfig(MemoryDbConfig, total=False): AUTOCOMMIT: bool DISABLE_SERVER_SIDE_CURSORS: bool # Remaining options read from queryParams - OPTIONS: dict[str, Any] + OPTIONS: dict[str, Incomplete] @type_check_only class EmailConfig(TypedDict, total=False): @@ -74,13 +123,13 @@ class EmailConfig(TypedDict, total=False): EMAIL_USE_TLS: bool EMAIL_USE_SSL: bool # Remaining options read from queryParams - OPTIONS: dict[str, Any] + OPTIONS: dict[str, Incomplete] # https://github.com/django/channels @type_check_only class ChannelsConfig(TypedDict, total=False): BACKEND: Required[str] - CONFIG: dict[str, Any] + CONFIG: dict[str, Incomplete] # https://github.com/django-haystack/django-haystack @type_check_only @@ -95,13 +144,13 @@ class SimpleSearchConfig(TypedDict, total=False): class SolrSearchConfig(SimpleSearchConfig, total=False): URL: Required[str] TIMEOUT: int - KWARGS: dict[str, Any] + KWARGS: dict[str, Incomplete] @type_check_only class ElasticsearchSearchConfig(SimpleSearchConfig, total=False): URL: Required[str] TIMEOUT: int - KWARGS: dict[str, Any] + KWARGS: dict[str, Incomplete] INDEX_NAME: str @type_check_only @@ -127,7 +176,7 @@ class CacheConfig(TypedDict, total=False): TIMEOUT: int VERSION: int # Remaining options read from queryParams - OPTIONS: dict[str, Any] + OPTIONS: dict[str, Incomplete] class Env: ENVIRON: MutableMapping[_str, _str] @@ -155,20 +204,20 @@ class Env: def __init__(self, **scheme: _SchemeValue) -> None: ... @overload def __call__( - self, var: _str, cast: _Cast[_T1] | None = None, default: _T1 | NoValue = ..., parse_default: _bool = False - ) -> _T1: ... + self, var: _str, cast: _Cast[_T] | None = None, default: _T | NoValue = ..., parse_default: _bool = False + ) -> _T: ... @overload def __call__( - self, var: _str, cast: _list[_Cast[_T1]], default: _list[_T1] | NoValue = ..., parse_default: _bool = False - ) -> _list[_T1]: ... + self, var: _str, cast: _list[_Cast[_T]], default: _list[_T] | NoValue = ..., parse_default: _bool = False + ) -> _list[_T]: ... @overload def __call__( - self, var: _str, cast: _tuple[_Cast[_T1]], default: _tuple[_T1] | NoValue = ..., parse_default: _bool = False - ) -> _tuple[_T1]: ... + self, var: _str, cast: _tuple[_Cast[_T], ...], default: _tuple[_T, ...] | NoValue = ..., parse_default: _bool = False + ) -> _tuple[_T, ...]: ... @overload def __call__( - self, var: _str, cast: CastDict[_T1, _T2], default: _dict[_T1, _T2] | NoValue = ..., parse_default: _bool = False - ) -> _dict[_T1, _T2]: ... + self, var: _str, cast: CastDict[_KT, _VT], default: _dict[_KT, _VT] | NoValue = ..., parse_default: _bool = False + ) -> _dict[_KT, _VT | object]: ... @overload # Any (subclass of) list/tuple/dict builtin types are valid for cast. def __call__( @@ -188,13 +237,18 @@ class Env: def bool(self, var: _str, default: _bool | NoValue = ...) -> _bool: ... def int(self, var: _str, default: _int | NoValue = ...) -> _int: ... def float(self, var: _str, default: _float | NoValue = ...) -> _float: ... - def json(self, var: _str, default: Any | NoValue = ...) -> Any: ... - def list(self, var: _str, cast: _Cast[_T1] | None = None, default: _list[_T1] | NoValue = ...) -> _list[_T1]: ... - def tuple(self, var: _str, cast: _Cast[_T1] | None = None, default: _tuple[_T1] | NoValue = ...) -> _tuple[_T1]: ... @overload - def dict(self, var: _str, cast: None = ..., default: _dict[_str, Any] | NoValue = ...) -> _dict[_str, Any]: ... + def json(self, var: _str, default: NoValue = ...) -> Any: ... + @overload + def json(self, var: _str, default: _T) -> _T: ... + def list(self, var: _str, cast: _Cast[_T] | None = None, default: _list[_T] | NoValue = ...) -> _list[_T]: ... + def tuple(self, var: _str, cast: _Cast[_T] | None = None, default: _tuple[_T, ...] | NoValue = ...) -> _tuple[_T, ...]: ... @overload - def dict(self, var: _str, cast: _Cast[_T1], default: _dict[Any, _T1] | NoValue = ...) -> _dict[Any, _T1]: ... + def dict( + self, var: _str, cast: CastDict[_KT, _VT] | None = None, default: _dict[_KT, _VT] | NoValue = ... + ) -> _dict[_KT, _VT | object]: ... + @overload + def dict(self, var: _str, cast: _Cast[_T], default: _T | NoValue = ...) -> _T: ... def url(self, var: _str, default: _str | NoValue = ...) -> _str: ... def db_url( self, var: _str = ..., default: _str | NoValue = ..., engine: _str | None = None @@ -222,20 +276,20 @@ class Env: def path(self, var: _str, default: _str | NoValue = ..., **kwargs: Unpack[PathKwargs]) -> Path: ... @overload def get_value( - self, var: _str, cast: _Cast[_T1] | None = None, default: _T1 | NoValue = ..., parse_default: _bool = False - ) -> _T1: ... + self, var: _str, cast: _Cast[_T] | None = None, default: _T | NoValue = ..., parse_default: _bool = False + ) -> _T: ... @overload def get_value( - self, var: _str, cast: _list[_Cast[_T1]], default: _list[_T1] | NoValue = ..., parse_default: _bool = False - ) -> _list[_T1]: ... + self, var: _str, cast: _list[_Cast[_T]], default: _list[_T] | NoValue = ..., parse_default: _bool = False + ) -> _list[_T]: ... @overload def get_value( - self, var: _str, cast: _tuple[_Cast[_T1]], default: _tuple[_T1] | NoValue = ..., parse_default: _bool = False - ) -> _tuple[_T1]: ... + self, var: _str, cast: _tuple[_Cast[_T], ...], default: _tuple[_T, ...] | NoValue = ..., parse_default: _bool = False + ) -> _tuple[_T, ...]: ... @overload def get_value( - self, var: _str, cast: CastDict[_T1, _T2], default: _dict[_T1, _T2] | NoValue = ..., parse_default: _bool = False - ) -> _dict[_T1, _T2]: ... + self, var: _str, cast: CastDict[_KT, _VT], default: _dict[_KT, _VT] | NoValue = ..., parse_default: _bool = False + ) -> _dict[_KT, _VT | object]: ... @overload # Any (subclass of) list/tuple/dict builtin types are valid for cast. def get_value( @@ -254,16 +308,40 @@ class Env: def parse_value(cls, value: _str, cast: None) -> _str: ... @classmethod @overload - def parse_value(cls, value: _str, cast: _Cast[_T1]) -> _T1: ... + def parse_value(cls, value: _str, cast: _Cast[_T]) -> _T: ... + @classmethod + @overload + def parse_value(cls, value: _str, cast: _list[_Cast[_T]]) -> _list[_T]: ... + @classmethod + @overload + def parse_value(cls, value: _str, cast: _tuple[_Cast[_T], ...]) -> _tuple[_T, ...]: ... + @classmethod + @overload + def parse_value(cls, value: _str, cast: CastDict000) -> _dict[_str, _str]: ... + @classmethod + @overload + def parse_value(cls, value: _str, cast: CastDict001) -> _dict[_str, _str | object]: ... + @classmethod + @overload + def parse_value(cls, value: _str, cast: CastDict010[_VT]) -> _dict[_str, _VT]: ... + @classmethod + @overload + def parse_value(cls, value: _str, cast: CastDict011[_VT]) -> _dict[_str, _VT | object]: ... + @classmethod + @overload + def parse_value(cls, value: _str, cast: CastDict100[_KT]) -> _dict[_KT, _str]: ... + @classmethod + @overload + def parse_value(cls, value: _str, cast: CastDict101[_KT]) -> _dict[_KT, _str | object]: ... @classmethod @overload - def parse_value(cls, value: _str, cast: _list[_Cast[_T1]]) -> _list[_T1]: ... + def parse_value(cls, value: _str, cast: CastDict110[_KT, _VT]) -> _dict[_KT, _VT]: ... @classmethod @overload - def parse_value(cls, value: _str, cast: _tuple[_Cast[_T1], ...]) -> _tuple[_T1]: ... + def parse_value(cls, value: _str, cast: CastDict111[_KT, _VT]) -> _dict[_KT, _VT | object]: ... @classmethod @overload - def parse_value(cls, value: _str, cast: CastDict[_T1, _T2]) -> _dict[_T1, _T2]: ... + def parse_value(cls, value: _str, cast: CastDict[_KT, _VT]) -> _dict[_KT, _VT | object]: ... @classmethod @overload # Any (subclass of) list/tuple/dict builtin types are valid for cast.