zai / app /utils /env_file.py
bg5's picture
Upload 85 files
e0b68d2 verified
Raw History Blame Contribute Delete
1.77 kB
"""Helpers for updating .env files without dropping unrelated settings."""
from __future__ import annotations
import re
from pathlib import Path
from typing import Mapping
_ENV_KEY_PATTERN = re.compile(r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=")
def _serialize_env_value(value: object) -> str:
if isinstance(value, bool):
return "true" if value else "false"
text = "" if value is None else str(value)
if not text:
return ""
if any(char.isspace() for char in text) or any(
char in text for char in ["#", '"', "\\", "'"]
):
if "'" not in text:
return f"'{text}'"
escaped = text.replace("\\", "\\\\").replace('"', '\\"')
return f'"{escaped}"'
return text
def update_env_file(
updates: Mapping[str, object],
env_path: str | Path = ".env",
) -> None:
"""Update selected keys inside a .env file while preserving other lines."""
path = Path(env_path)
lines = path.read_text(encoding="utf-8").splitlines() if path.exists() else []
remaining_updates = {key: _serialize_env_value(value) for key, value in updates.items()}
for index, line in enumerate(lines):
match = _ENV_KEY_PATTERN.match(line)
if not match:
continue
key = match.group(1)
if key not in remaining_updates:
continue
lines[index] = f"{key}={remaining_updates.pop(key)}"
if remaining_updates:
if lines and lines[-1].strip():
lines.append("")
for key, value in remaining_updates.items():
lines.append(f"{key}={value}")
content = "\n".join(lines).rstrip()
path.write_text(f"{content}\n" if content else "", encoding="utf-8")