Skip to content

Add stubs for django-environ #14573

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

EmCeeEs
Copy link

@EmCeeEs EmCeeEs commented Aug 13, 2025

Types for django-environ.

This comment has been minimized.

Copy link
Contributor

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

@donBarbos
Copy link
Contributor

Thanks! I just want to note that the project doesn’t seem to be very actively maintained (the last commit was 7 months ago), so I wouldn’t expect type hints to be added to the library itself anytime soon.

FTR: here’s a recent issue essentially complaining about the lack of type hints: joke2k/django-environ#567, and another one joke2k/django-environ#365 that might give some insight into the maintainer’s stance on adding them.

Copy link
Contributor

@donBarbos donBarbos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Just few moments:

Comment on lines +4 to +11
__copyright__: str
__version__: str
__license__: str
__author_email__: str
__maintainer__: str
__maintainer_email__: str
__url__: str
__description__: str
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these variables can be marked as Final (but need to import Final from typing)

Suggested change
__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]

Comment on lines +7 to +9
REDIS_DRIVER: str
DJANGO_POSTGRES: str
PYMEMCACHE_DRIVER: str
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
REDIS_DRIVER: str
DJANGO_POSTGRES: str
PYMEMCACHE_DRIVER: str
REDIS_DRIVER: Final[str]
DJANGO_POSTGRES: Final[str]
PYMEMCACHE_DRIVER: Final[str]

Comment on lines +3 to +5
def choose_rediscache_driver(): ...
def choose_postgres_driver(): ...
def choose_pymemcache_driver(): ...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def choose_rediscache_driver(): ...
def choose_postgres_driver(): ...
def choose_pymemcache_driver(): ...
def choose_rediscache_driver() -> str: ...
def choose_postgres_driver() -> str: ...
def choose_pymemcache_driver() -> str: ...

Comment on lines +16 to +23
_Str = str
_Bytes = bytes
_Bool = bool
_Int = int
_Float = float
_List = list
_Tuple = tuple
_Dict = dict
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like internal types, I think it's okay to miss them

Suggested change
_Str = str
_Bytes = bytes
_Bool = bool
_Int = int
_Float = float
_List = list
_Tuple = tuple
_Dict = dict

Comment on lines +31 to +37
ENVIRON: MutableMapping[_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]]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to use aliases for builtin types, it's more confusing

Suggested change
ENVIRON: MutableMapping[_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]]
ENVIRON: MutableMapping[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]]

I didn't change everything, but you get the idea :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work because the Env class defines methods def str(...), def list(...), etc. overwriting the builtin types. That was the reason for the aliases. ;) Alternatively, I could explicitly use builtins.str, builtins.list, etc. everywhere in the Env class. What do you think?

Copy link
Member

@brianschubert brianschubert Aug 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that makes sense now :)

I think the typical approach for dealing with conflicts like that is to import the builtins symbols under an alias, e.g. from builtins import list as _list. Please also add a comment mentioning the conflict

Copy link
Member

@brianschubert brianschubert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Looks good overall, see remarks below

Comment on lines +4 to +11
__copyright__: str
__version__: str
__license__: str
__author_email__: str
__maintainer__: str
__maintainer_email__: str
__url__: str
__description__: str
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These look like they can be marked as Final

@@ -0,0 +1,9 @@
class ImproperlyConfigured(Exception): ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This re-exports ImproperlyConfigured from django, so I think you could use

Suggested change
class ImproperlyConfigured(Exception): ...
from django.core.exceptions import ImproperlyConfigured as ImproperlyConfigured

You'll need to add a django-stubs requirement to METADATA.toml. See some of the other django stubs for examples

Comment on lines +3 to +9
def choose_rediscache_driver(): ...
def choose_postgres_driver(): ...
def choose_pymemcache_driver(): ...

REDIS_DRIVER: str
DJANGO_POSTGRES: str
PYMEMCACHE_DRIVER: str
Copy link
Member

@brianschubert brianschubert Aug 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def choose_rediscache_driver(): ...
def choose_postgres_driver(): ...
def choose_pymemcache_driver(): ...
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]

Comment on lines +15 to +23
# Some type aliases to make our life easier
_Str = str
_Bytes = bytes
_Bool = bool
_Int = int
_Float = float
_List = list
_Tuple = tuple
_Dict = dict
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I see the value of these aliases. Can you switch to using the builtin types directly?

_T = TypeVar("_T")
_Cast: TypeAlias = Callable[[_Str], _T]
_SchemeValue: TypeAlias = _Cast[Any] | tuple[_Cast[Any], Any]
_BooleanTrueStrings: TypeAlias = tuple[str, ...]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this alias is only used once, so I'm not sure it really pulls its weight. I'd suggest inlining it below

_Dict = dict

_T = TypeVar("_T")
_Cast: TypeAlias = Callable[[_Str], _T]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment on parse_value

Comment on lines +92 to +100
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: ...
@classmethod
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: ...
@classmethod
def search_url_config(cls, url: _Str | ParseResult, engine: _Str | None = None) -> _Dict: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could return TypedDicts since the keys appear to be fixed

@classmethod
def read_env(
cls,
env_file: _Str | os.PathLike[_Str] | None = None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could use StrPath (import from _typeshed)

class FileAwareEnv(Env):
ENVIRON: FileAwareMapping

class _PathKwargs(TypedDict, total=False):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class _PathKwargs(TypedDict, total=False):
@type_check_only
class _PathKwargs(TypedDict, total=False):

Comment on lines +4 to +7
env: dict[str, str]
cache: bool
files_cache: dict[str, str]
def __init__(self, env: dict[str, str] | None = None, cache: bool = True) -> None: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like env could be relaxed to MutableMapping[str, str]

@brianschubert brianschubert changed the title Feature/django environ types Add stubs for django-environ Aug 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants