2018-12-05 11:53:38 +00:00
|
|
|
import os
|
2024-02-04 18:33:06 +00:00
|
|
|
import typing
|
2019-12-17 11:14:57 +00:00
|
|
|
from pathlib import Path
|
2022-07-10 13:00:23 +00:00
|
|
|
from typing import Any, Optional
|
2018-12-05 11:53:38 +00:00
|
|
|
|
|
|
|
import pytest
|
2022-07-10 13:00:23 +00:00
|
|
|
from typing_extensions import assert_type
|
2018-12-05 11:53:38 +00:00
|
|
|
|
2018-12-05 12:28:18 +00:00
|
|
|
from starlette.config import Config, Environ, EnvironError
|
2019-02-19 12:54:57 +00:00
|
|
|
from starlette.datastructures import URL, Secret
|
2018-12-05 11:53:38 +00:00
|
|
|
|
|
|
|
|
2022-07-10 13:00:23 +00:00
|
|
|
def test_config_types() -> None:
|
|
|
|
"""
|
|
|
|
We use `assert_type` to test the types returned by Config via mypy.
|
|
|
|
"""
|
2024-09-01 13:11:01 +00:00
|
|
|
config = Config(environ={"STR": "some_str_value", "STR_CAST": "some_str_value", "BOOL": "true"})
|
2022-07-10 13:00:23 +00:00
|
|
|
|
|
|
|
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")
|
|
|
|
|
|
|
|
|
2024-02-04 18:33:06 +00:00
|
|
|
def test_config(tmpdir: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
2018-12-05 11:53:38 +00:00
|
|
|
path = os.path.join(tmpdir, ".env")
|
|
|
|
with open(path, "w") as file:
|
|
|
|
file.write("# Do not commit to source control\n")
|
2019-03-08 19:28:08 +00:00
|
|
|
file.write("DATABASE_URL=postgres://user:pass@localhost/dbname\n")
|
2018-12-05 11:53:38 +00:00
|
|
|
file.write("REQUEST_HOSTNAME=example.com\n")
|
2018-12-05 12:38:54 +00:00
|
|
|
file.write("SECRET_KEY=12345\n")
|
2019-03-08 19:28:08 +00:00
|
|
|
file.write("BOOL_AS_INT=0\n")
|
2018-12-05 11:53:38 +00:00
|
|
|
file.write("\n")
|
|
|
|
file.write("\n")
|
|
|
|
|
|
|
|
config = Config(path, environ={"DEBUG": "true"})
|
|
|
|
|
2024-02-04 18:33:06 +00:00
|
|
|
def cast_to_int(v: typing.Any) -> int:
|
2022-02-08 10:51:52 +00:00
|
|
|
return int(v)
|
|
|
|
|
2018-12-05 16:38:45 +00:00
|
|
|
DEBUG = config("DEBUG", cast=bool)
|
2019-02-19 12:54:57 +00:00
|
|
|
DATABASE_URL = config("DATABASE_URL", cast=URL)
|
2018-12-05 16:38:45 +00:00
|
|
|
REQUEST_TIMEOUT = config("REQUEST_TIMEOUT", cast=int, default=10)
|
|
|
|
REQUEST_HOSTNAME = config("REQUEST_HOSTNAME")
|
2022-07-10 13:00:23 +00:00
|
|
|
MAIL_HOSTNAME = config("MAIL_HOSTNAME", default=None)
|
2018-12-05 16:38:45 +00:00
|
|
|
SECRET_KEY = config("SECRET_KEY", cast=Secret)
|
2022-05-05 22:01:28 +00:00
|
|
|
UNSET_SECRET = config("UNSET_SECRET", cast=Secret, default=None)
|
|
|
|
EMPTY_SECRET = config("EMPTY_SECRET", cast=Secret, default="")
|
2019-03-08 19:28:08 +00:00
|
|
|
assert config("BOOL_AS_INT", cast=bool) is False
|
2022-02-08 10:51:52 +00:00
|
|
|
assert config("BOOL_AS_INT", cast=cast_to_int) == 0
|
|
|
|
assert config("DEFAULTED_BOOL", cast=cast_to_int, default=True) == 1
|
2018-12-05 11:53:38 +00:00
|
|
|
|
|
|
|
assert DEBUG is True
|
2019-03-08 19:28:08 +00:00
|
|
|
assert DATABASE_URL.path == "/dbname"
|
|
|
|
assert DATABASE_URL.password == "pass"
|
|
|
|
assert DATABASE_URL.username == "user"
|
2018-12-05 11:53:38 +00:00
|
|
|
assert REQUEST_TIMEOUT == 10
|
|
|
|
assert REQUEST_HOSTNAME == "example.com"
|
2022-07-10 13:00:23 +00:00
|
|
|
assert MAIL_HOSTNAME is None
|
2018-12-05 12:38:54 +00:00
|
|
|
assert repr(SECRET_KEY) == "Secret('**********')"
|
|
|
|
assert str(SECRET_KEY) == "12345"
|
2022-05-05 22:01:28 +00:00
|
|
|
assert bool(SECRET_KEY)
|
|
|
|
assert not bool(EMPTY_SECRET)
|
|
|
|
assert not bool(UNSET_SECRET)
|
2018-12-05 11:53:38 +00:00
|
|
|
|
|
|
|
with pytest.raises(KeyError):
|
|
|
|
config.get("MISSING")
|
|
|
|
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
config.get("DEBUG", cast=int)
|
|
|
|
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
config.get("REQUEST_HOSTNAME", cast=bool)
|
|
|
|
|
2019-12-17 09:57:02 +00:00
|
|
|
config = Config(Path(path))
|
2019-12-17 11:14:57 +00:00
|
|
|
REQUEST_HOSTNAME = config("REQUEST_HOSTNAME")
|
2019-12-17 09:57:02 +00:00
|
|
|
assert REQUEST_HOSTNAME == "example.com"
|
|
|
|
|
2018-12-05 11:53:38 +00:00
|
|
|
config = Config()
|
2019-03-08 19:28:08 +00:00
|
|
|
monkeypatch.setenv("STARLETTE_EXAMPLE_TEST", "123")
|
|
|
|
monkeypatch.setenv("BOOL_AS_INT", "1")
|
2018-12-05 11:53:38 +00:00
|
|
|
assert config.get("STARLETTE_EXAMPLE_TEST", cast=int) == 123
|
2019-03-08 19:28:08 +00:00
|
|
|
assert config.get("BOOL_AS_INT", cast=bool) is True
|
|
|
|
|
|
|
|
monkeypatch.setenv("BOOL_AS_INT", "2")
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
config.get("BOOL_AS_INT", cast=bool)
|
2018-12-05 12:28:18 +00:00
|
|
|
|
2019-12-17 11:14:57 +00:00
|
|
|
|
2024-02-04 18:33:06 +00:00
|
|
|
def test_missing_env_file_raises(tmpdir: Path) -> None:
|
2024-01-20 12:06:50 +00:00
|
|
|
path = os.path.join(tmpdir, ".env")
|
|
|
|
|
2024-02-06 21:58:35 +00:00
|
|
|
with pytest.warns(UserWarning, match=f"Config file '{path}' not found."):
|
2024-01-20 12:06:50 +00:00
|
|
|
Config(path)
|
|
|
|
|
|
|
|
|
2024-02-04 18:33:06 +00:00
|
|
|
def test_environ() -> None:
|
2018-12-05 12:28:18 +00:00
|
|
|
environ = Environ()
|
|
|
|
|
|
|
|
# We can mutate the environ at this point.
|
|
|
|
environ["TESTING"] = "True"
|
|
|
|
environ["GONE"] = "123"
|
|
|
|
del environ["GONE"]
|
|
|
|
|
|
|
|
# We can read the environ.
|
|
|
|
assert environ["TESTING"] == "True"
|
|
|
|
assert "GONE" not in environ
|
|
|
|
|
|
|
|
# We cannot mutate these keys now that we've read them.
|
|
|
|
with pytest.raises(EnvironError):
|
|
|
|
environ["TESTING"] = "False"
|
|
|
|
|
|
|
|
with pytest.raises(EnvironError):
|
|
|
|
del environ["GONE"]
|
|
|
|
|
|
|
|
# Test coverage of abstract methods for MutableMapping.
|
|
|
|
environ = Environ()
|
|
|
|
assert list(iter(environ)) == list(iter(os.environ))
|
|
|
|
assert len(environ) == len(os.environ)
|
2023-01-22 21:24:35 +00:00
|
|
|
|
|
|
|
|
2024-02-04 18:33:06 +00:00
|
|
|
def test_config_with_env_prefix(tmpdir: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
2024-09-01 13:11:01 +00:00
|
|
|
config = Config(environ={"APP_DEBUG": "value", "ENVIRONMENT": "dev"}, env_prefix="APP_")
|
2023-01-22 21:24:35 +00:00
|
|
|
assert config.get("DEBUG") == "value"
|
|
|
|
|
|
|
|
with pytest.raises(KeyError):
|
|
|
|
config.get("ENVIRONMENT")
|