Spaces:
Running on Zero
Running on Zero
| """Central configuration accessors. | |
| Every accessor reads ``os.environ`` (or an explicit mapping) **live** on each call, | |
| so lazily-built runtimes and tests that monkeypatch the environment always observe | |
| the current value — there is no import-time snapshot. Stdlib-only, so this module is | |
| safe to import from any runtime, embedding subprocess, or Modal container. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from collections.abc import Mapping | |
| TRUE_VALUES = {"1", "true", "yes", "on"} | |
| FALSE_VALUES = {"0", "false", "no", "off"} | |
| class ConfigError(RuntimeError, ValueError): | |
| """Invalid configuration value. | |
| Subclasses both ``RuntimeError`` and ``ValueError`` so existing | |
| ``except RuntimeError`` handlers and ``pytest.raises`` checks keep working | |
| regardless of which base they expect. | |
| """ | |
| def _source(env: Mapping[str, str] | None) -> Mapping[str, str]: | |
| return os.environ if env is None else env | |
| def str_env(name: str, default: str = "", *, env: Mapping[str, str] | None = None) -> str: | |
| """Raw environment string, or ``default`` when unset.""" | |
| return _source(env).get(name, default) | |
| def bool_env(name: str, default: bool = False, *, env: Mapping[str, str] | None = None) -> bool: | |
| """Boolean flag. Empty or unrecognised values fall back to ``default``.""" | |
| raw = _source(env).get(name, "").strip().lower() | |
| if not raw: | |
| return default | |
| if raw in TRUE_VALUES: | |
| return True | |
| if raw in FALSE_VALUES: | |
| return False | |
| return default | |
| def tri_state_env(name: str, *, env: Mapping[str, str] | None = None) -> bool | None: | |
| """``True``/``False`` for recognised boolean strings, ``None`` when unset/unrecognised.""" | |
| raw = _source(env).get(name, "").strip().lower() | |
| if raw in TRUE_VALUES: | |
| return True | |
| if raw in FALSE_VALUES: | |
| return False | |
| return None | |
| def int_env( | |
| name: str, | |
| default: int, | |
| *, | |
| minimum: int | None = None, | |
| maximum: int | None = None, | |
| env: Mapping[str, str] | None = None, | |
| ) -> int: | |
| """Integer with optional bounds. Empty falls back to ``default``; out-of-range raises ConfigError.""" | |
| raw = _source(env).get(name, "").strip() | |
| if not raw: | |
| return default | |
| value = int(raw) | |
| if minimum is not None and value < minimum: | |
| raise ConfigError(f"{name} {_below_message(minimum)}") | |
| if maximum is not None and value > maximum: | |
| raise ConfigError(f"{name} must be at most {maximum}.") | |
| return value | |
| def optional_int_env( | |
| name: str, | |
| *, | |
| minimum: int = 1, | |
| env: Mapping[str, str] | None = None, | |
| ) -> int | None: | |
| """Integer or ``None`` when unset. Values below ``minimum`` raise ConfigError.""" | |
| raw = _source(env).get(name, "").strip() | |
| if not raw: | |
| return None | |
| value = int(raw) | |
| if value < minimum: | |
| raise ConfigError(f"{name} {_below_message(minimum)}") | |
| return value | |
| def first_nonempty_env(*names: str, default: str = "", env: Mapping[str, str] | None = None) -> str: | |
| """First non-empty (stripped) value among ``names``, else ``default``.""" | |
| source = _source(env) | |
| for name in names: | |
| value = source.get(name, "").strip() | |
| if value: | |
| return value | |
| return default | |
| def _below_message(minimum: int) -> str: | |
| if minimum == 1: | |
| return "must be a positive integer." | |
| if minimum == 0: | |
| return "must be a non-negative integer." | |
| return f"must be at least {minimum}." | |