type config with `None` default as `str | None` instead of `Any` (#1732)

* fix type annotations for config

* wip

* fix simple case

* wip

* isort

* remove mypy config change

* fix test

* fix coverage

* use assert_type

* format

* CR

* Update tests/test_config.py

Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
This commit is contained in:
Christopher Dignam 2022-07-10 09:00:23 -04:00 committed by GitHub
parent 643c3f20e3
commit 584f22e355
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 41 additions and 0 deletions

View File

@ -9,6 +9,7 @@ databases[sqlite]==0.5.5
flake8==3.9.2
isort==5.10.1
mypy==0.961
typing_extensions==4.2.0
types-requests==2.26.3
types-contextvars==2.4.7
types-PyYAML==6.0.4

View File

@ -60,6 +60,12 @@ class Config:
if env_file is not None and os.path.isfile(env_file):
self.file_values = self._read_file(env_file)
@typing.overload
def __call__(
self, key: str, *, default: None
) -> typing.Optional[str]: # pragma: no cover
...
@typing.overload
def __call__(
self, key: str, cast: typing.Type[T], default: T = ...

View File

@ -1,12 +1,44 @@
import os
from pathlib import Path
from typing import Any, Optional
import pytest
from typing_extensions import assert_type
from starlette.config import Config, Environ, EnvironError
from starlette.datastructures import URL, Secret
def test_config_types() -> None:
"""
We use `assert_type` to test the types returned by Config via mypy.
"""
config = Config(
environ={"STR": "some_str_value", "STR_CAST": "some_str_value", "BOOL": "true"}
)
assert_type(config("STR"), str)
assert_type(config("STR_DEFAULT", default=""), str)
assert_type(config("STR_CAST", cast=str), str)
assert_type(config("STR_NONE", default=None), Optional[str])
assert_type(config("STR_CAST_NONE", cast=str, default=None), Optional[str])
assert_type(config("STR_CAST_STR", cast=str, default=""), str)
assert_type(config("BOOL", cast=bool), bool)
assert_type(config("BOOL_DEFAULT", cast=bool, default=False), bool)
assert_type(config("BOOL_NONE", cast=bool, default=None), Optional[bool])
def cast_to_int(v: Any) -> int:
return int(v)
# our type annotations allow these `cast` and `default` configurations, but
# the code will error at runtime.
with pytest.raises(ValueError):
config("INT_CAST_DEFAULT_STR", cast=cast_to_int, default="true")
with pytest.raises(ValueError):
config("INT_DEFAULT_STR", cast=int, default="true")
def test_config(tmpdir, monkeypatch):
path = os.path.join(tmpdir, ".env")
with open(path, "w") as file:
@ -27,6 +59,7 @@ def test_config(tmpdir, monkeypatch):
DATABASE_URL = config("DATABASE_URL", cast=URL)
REQUEST_TIMEOUT = config("REQUEST_TIMEOUT", cast=int, default=10)
REQUEST_HOSTNAME = config("REQUEST_HOSTNAME")
MAIL_HOSTNAME = config("MAIL_HOSTNAME", default=None)
SECRET_KEY = config("SECRET_KEY", cast=Secret)
UNSET_SECRET = config("UNSET_SECRET", cast=Secret, default=None)
EMPTY_SECRET = config("EMPTY_SECRET", cast=Secret, default="")
@ -40,6 +73,7 @@ def test_config(tmpdir, monkeypatch):
assert DATABASE_URL.username == "user"
assert REQUEST_TIMEOUT == 10
assert REQUEST_HOSTNAME == "example.com"
assert MAIL_HOSTNAME is None
assert repr(SECRET_KEY) == "Secret('**********')"
assert str(SECRET_KEY) == "12345"
assert bool(SECRET_KEY)