import os
import re
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any
from dotenv import load_dotenv as _load_dotenv_origin
from lib import jsonutils, loggerutils, timeutils
logger = loggerutils.getLogger('app')
TRUES = ('1', 'True', 'true', 'yes', 'on', True)
def load_env(
env_path: Path | str | None = None,
local_env_path: Path | str | None = None,
override: dict[str, Any] | None = None,
) -> None:
"""
Load environment variables from .env files
Args:
env_path: Path to main .env file. If None, no env file loaded.
local_env_path: Path to local .env file (overrides). Loaded if exists.
override: Dict of env vars to set after loading files.
"""
if env_path:
_load_env_file(Path(env_path))
if local_env_path:
local_path = Path(local_env_path)
if local_path.exists():
_load_env_file(local_path)
if override:
for key, value in override.items():
os.environ[key] = str(value)
return None
def _load_env_file(env_path: Path) -> None:
"""Load environment variables from a .env file."""
_load_dotenv_origin(env_path, override=True)
logger.info(f'Loaded env from: {env_path}')
def parse_datetime(
key: str,
default: datetime | None = None,
required: bool = False,
) -> datetime | None:
"""Parse datetime value from environment variable.
Args:
key: Environment variable name
default: Default value if key not found
required: If True, raises ValueError when key is missing
Returns:
Datetime value from environment or default
Raises:
ValueError: If required=True and key is not set
"""
if key not in os.environ:
if required:
raise ValueError(f"Required environment variable '{key}' is not set")
return default
dt_str = os.environ[key]
dt = timeutils.from_iso_format(dt_str)
if not isinstance(dt, datetime):
return default
return dt
def parse_timedelta(
key: str,
default: timedelta | None = None,
required: bool = False,
) -> timedelta | None:
"""Parse timedelta value from environment variable.
Args:
key: Environment variable name
default: Default value if key not found
required: If True, raises ValueError when key is missing
Returns:
Timedelta value from environment or default
Raises:
ValueError: If required=True and key is not set
"""
if key not in os.environ:
if required:
raise ValueError(f"Required environment variable '{key}' is not set")
return default
time_dict = {}
pattern = r"(hours|minutes|days)=(\d+)"
matches = re.findall(pattern, os.environ[key])
if not matches:
raise ValueError(f"Invalid timedelta format for '{key}': {os.environ[key]}")
for time_unit, time_value in matches:
time_dict[time_unit] = int(time_value)
return timedelta(**time_dict)
def parse_str_list(
key: str,
default: list[Any] | None = None,
required: bool = False,
) -> list[str] | None:
"""Parse list of strings from environment variable (comma-separated).
Args:
key: Environment variable name
default: Default value if key not found
required: If True, raises ValueError when key is missing
Returns:
List of strings from environment or default
Raises:
ValueError: If required=True and key is not set
"""
if key not in os.environ:
if required:
raise ValueError(f"Required environment variable '{key}' is not set")
return default
items = os.environ[key].split(',')
return [i.strip() for i in items if i.strip()]
def parse_json(
key: str,
default: dict[str, Any] | None = None,
required: bool = False,
) -> dict[str, Any] | None:
"""Parse dict from environment variable (JSON string).
Args:
key: Environment variable name
default: Default value if key not found
required: If True, raises ValueError when key is missing
Returns:
Dict from environment or default
Raises:
ValueError: If required=True and key is not set
"""
if key not in os.environ:
if required:
raise ValueError(f"Required environment variable '{key}' is not set")
return default
dct = jsonutils.loads(os.environ[key].strip().strip('\''))
return dct
def parse_int(
key: str,
default: int | None = None,
required: bool = False,
) -> int | None:
"""Parse integer value from environment variable.
Args:
key: Environment variable name
default: Default value if key not found
required: If True, raises ValueError when key is missing
Returns:
Integer value from environment or default
Raises:
ValueError: If required=True and key is not set
"""
if key not in os.environ:
if required:
raise ValueError(f"Required environment variable '{key}' is not set")
return default
return int(os.environ[key])
def parse_float(
key: str,
default: float | None = None,
required: bool = False,
) -> float | None:
"""Parse float value from environment variable.
Args:
key: Environment variable name
default: Default value if key not found
required: If True, raises ValueError when key is missing
Returns:
Float value from environment or default
Raises:
ValueError: If required=True and key is not set
"""
if key not in os.environ:
if required:
raise ValueError(f"Required environment variable '{key}' is not set")
return default
return float(os.environ[key])
def parse_bool(
key: str,
default: bool = False,
required: bool = False,
) -> bool:
"""Parse boolean value from environment variable.
Args:
key: Environment variable name
default: Default value if key not found
required: If True, raises ValueError when key is missing
Returns:
True if env value in ('1', 'True', 'true', 'yes', 'on', True), False otherwise
Raises:
ValueError: If required=True and key is not set
"""
if key not in os.environ:
if required:
raise ValueError(f"Required environment variable '{key}' is not set")
return default
return os.environ[key] in TRUES
def parse_str(
key: str,
default: str | None = None,
required: bool = False,
) -> str | None:
"""Parse string value from environment variable.
Args:
key: Environment variable name
default: Default value if key not found
required: If True, raises ValueError when key is missing
Returns:
String value from environment or default
Raises:
ValueError: If required=True and key is not set
"""
if key not in os.environ:
if required:
raise ValueError(f"Required environment variable '{key}' is not set")
return default
return os.environ[key]