diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/starlette/config.py')
-rw-r--r-- | .venv/lib/python3.12/site-packages/starlette/config.py | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/starlette/config.py b/.venv/lib/python3.12/site-packages/starlette/config.py new file mode 100644 index 00000000..ca15c564 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/starlette/config.py @@ -0,0 +1,138 @@ +from __future__ import annotations + +import os +import typing +import warnings +from pathlib import Path + + +class undefined: + pass + + +class EnvironError(Exception): + pass + + +class Environ(typing.MutableMapping[str, str]): + def __init__(self, environ: typing.MutableMapping[str, str] = os.environ): + self._environ = environ + self._has_been_read: set[str] = set() + + def __getitem__(self, key: str) -> str: + self._has_been_read.add(key) + return self._environ.__getitem__(key) + + def __setitem__(self, key: str, value: str) -> None: + if key in self._has_been_read: + raise EnvironError(f"Attempting to set environ['{key}'], but the value has already been read.") + self._environ.__setitem__(key, value) + + def __delitem__(self, key: str) -> None: + if key in self._has_been_read: + raise EnvironError(f"Attempting to delete environ['{key}'], but the value has already been read.") + self._environ.__delitem__(key) + + def __iter__(self) -> typing.Iterator[str]: + return iter(self._environ) + + def __len__(self) -> int: + return len(self._environ) + + +environ = Environ() + +T = typing.TypeVar("T") + + +class Config: + def __init__( + self, + env_file: str | Path | None = None, + environ: typing.Mapping[str, str] = environ, + env_prefix: str = "", + ) -> None: + self.environ = environ + self.env_prefix = env_prefix + self.file_values: dict[str, str] = {} + if env_file is not None: + if not os.path.isfile(env_file): + warnings.warn(f"Config file '{env_file}' not found.") + else: + self.file_values = self._read_file(env_file) + + @typing.overload + def __call__(self, key: str, *, default: None) -> str | None: ... + + @typing.overload + def __call__(self, key: str, cast: type[T], default: T = ...) -> T: ... + + @typing.overload + def __call__(self, key: str, cast: type[str] = ..., default: str = ...) -> str: ... + + @typing.overload + def __call__( + self, + key: str, + cast: typing.Callable[[typing.Any], T] = ..., + default: typing.Any = ..., + ) -> T: ... + + @typing.overload + def __call__(self, key: str, cast: type[str] = ..., default: T = ...) -> T | str: ... + + def __call__( + self, + key: str, + cast: typing.Callable[[typing.Any], typing.Any] | None = None, + default: typing.Any = undefined, + ) -> typing.Any: + return self.get(key, cast, default) + + def get( + self, + key: str, + cast: typing.Callable[[typing.Any], typing.Any] | None = None, + default: typing.Any = undefined, + ) -> typing.Any: + key = self.env_prefix + key + if key in self.environ: + value = self.environ[key] + return self._perform_cast(key, value, cast) + if key in self.file_values: + value = self.file_values[key] + return self._perform_cast(key, value, cast) + if default is not undefined: + return self._perform_cast(key, default, cast) + raise KeyError(f"Config '{key}' is missing, and has no default.") + + def _read_file(self, file_name: str | Path) -> dict[str, str]: + file_values: dict[str, str] = {} + with open(file_name) as input_file: + for line in input_file.readlines(): + line = line.strip() + if "=" in line and not line.startswith("#"): + key, value = line.split("=", 1) + key = key.strip() + value = value.strip().strip("\"'") + file_values[key] = value + return file_values + + def _perform_cast( + self, + key: str, + value: typing.Any, + cast: typing.Callable[[typing.Any], typing.Any] | None = None, + ) -> typing.Any: + if cast is None or value is None: + return value + elif cast is bool and isinstance(value, str): + mapping = {"true": True, "1": True, "false": False, "0": False} + value = value.lower() + if value not in mapping: + raise ValueError(f"Config '{key}' has value '{value}'. Not a valid bool.") + return mapping[value] + try: + return cast(value) + except (TypeError, ValueError): + raise ValueError(f"Config '{key}' has value '{value}'. Not a valid {cast.__name__}.") |