These helpers read and write project-level and bot-level .env files.

openenv.envfiles

openenv.envfiles

Helpers for project and bot environment files.

openenv.envfiles.project_env

openenv.envfiles.project_env

Helpers for loading and updating the project root .env file.

get_project_env_value(root, key)

Resolve a configuration value from the OS env first, then the project .env.

Source code in src/openenv/envfiles/project_env.py
def get_project_env_value(root: str | Path, key: str) -> str | None:
    """Resolve a configuration value from the OS env first, then the project .env."""
    if value := os.environ.get(key):
        return value
    return load_project_env(project_env_path(root)).get(key)

load_project_env(path)

Load KEY=VALUE pairs from a project .env file.

Source code in src/openenv/envfiles/project_env.py
def load_project_env(path: str | Path) -> dict[str, str]:
    """Load KEY=VALUE pairs from a project .env file."""
    env_path = Path(path)
    if not env_path.exists():
        return {}
    return dict(parse_project_env_text(env_path.read_text(encoding="utf-8"), label=str(env_path)))

parse_project_env_text(text, *, label)

Parse a project .env file.

Source code in src/openenv/envfiles/project_env.py
def parse_project_env_text(text: str, *, label: str) -> list[tuple[str, str]]:
    """Parse a project .env file."""
    entries: list[tuple[str, str]] = []
    seen: set[str] = set()
    for line_no, raw_line in enumerate(text.splitlines(), start=1):
        stripped = raw_line.strip()
        if not stripped or stripped.startswith("#"):
            continue
        if "=" not in raw_line:
            raise ValidationError(f"{label}:{line_no} must use KEY=VALUE syntax.")
        key, value = raw_line.split("=", 1)
        key = key.strip()
        if not _ENV_KEY_PATTERN.match(key):
            raise ValidationError(
                f"{label}:{line_no} has an invalid env var name: {key!r}"
            )
        if key in seen:
            raise ValidationError(f"{label}:{line_no} duplicates env var {key}.")
        seen.add(key)
        entries.append((key, value))
    return entries

project_env_path(root)

Return the project-level .env path.

Source code in src/openenv/envfiles/project_env.py
def project_env_path(root: str | Path) -> Path:
    """Return the project-level .env path."""
    return Path(root).resolve() / PROJECT_ENV_FILENAME

upsert_project_env_text(text, key, value)

Insert or replace a KEY=VALUE line while preserving other content.

Source code in src/openenv/envfiles/project_env.py
def upsert_project_env_text(text: str, key: str, value: str) -> str:
    """Insert or replace a KEY=VALUE line while preserving other content."""
    lines = text.splitlines()
    rendered_line = f"{key}={value}"
    updated = False
    result: list[str] = []
    for raw_line in lines:
        stripped = raw_line.strip()
        if stripped and not stripped.startswith("#") and "=" in raw_line:
            current_key, _ = raw_line.split("=", 1)
            if current_key.strip() == key:
                result.append(rendered_line)
                updated = True
                continue
        result.append(raw_line)
    if not updated:
        if result and result[-1] != "":
            result.append("")
        result.append(rendered_line)
    return "\n".join(result).rstrip() + "\n"

write_project_env_value(root, key, value)

Upsert a single KEY=VALUE entry in the project root .env file.

Source code in src/openenv/envfiles/project_env.py
def write_project_env_value(root: str | Path, key: str, value: str) -> Path:
    """Upsert a single KEY=VALUE entry in the project root .env file."""
    if not _ENV_KEY_PATTERN.match(key):
        raise ValidationError(f"Invalid env var name: {key!r}")
    env_path = project_env_path(root)
    existing_text = env_path.read_text(encoding="utf-8") if env_path.exists() else ""
    rendered = upsert_project_env_text(existing_text, key, value)
    env_path.write_text(rendered, encoding="utf-8")
    return env_path

openenv.envfiles.secret_env

openenv.envfiles.secret_env

Helpers for sidecar .env secret reference files.

load_secret_refs(path)

Load secret refs from a sidecar .env file.

Source code in src/openenv/envfiles/secret_env.py
def load_secret_refs(path: str | Path) -> list[SecretRef]:
    """Load secret refs from a sidecar .env file."""
    return [
        SecretRef(name=name, source=f"env:{name}", required=True)
        for name in load_secret_values(path)
    ]

load_secret_values(path)

Load env key/value pairs from a sidecar .env file.

Source code in src/openenv/envfiles/secret_env.py
def load_secret_values(path: str | Path) -> dict[str, str]:
    """Load env key/value pairs from a sidecar .env file."""
    env_path = Path(path)
    if not env_path.exists():
        return {}
    return dict(parse_secret_env_text(env_path.read_text(encoding="utf-8"), label=str(env_path)))

parse_secret_env_text(text, *, label)

Parse a bot sidecar .env file.

Source code in src/openenv/envfiles/secret_env.py
def parse_secret_env_text(text: str, *, label: str) -> list[tuple[str, str]]:
    """Parse a bot sidecar .env file."""
    entries: list[tuple[str, str]] = []
    seen: set[str] = set()
    for line_no, raw_line in enumerate(text.splitlines(), start=1):
        stripped = raw_line.strip()
        if not stripped or stripped.startswith("#"):
            continue
        if "=" not in raw_line:
            raise ValidationError(f"{label}:{line_no} must use KEY=VALUE syntax.")
        name, value = raw_line.split("=", 1)
        name = name.strip()
        if not _ENV_KEY_PATTERN.match(name):
            raise ValidationError(
                f"{label}:{line_no} has an invalid env var name: {name!r}"
            )
        if name in seen:
            raise ValidationError(f"{label}:{line_no} duplicates env var {name}.")
        seen.add(name)
        entries.append((name, value))
    return entries

render_secret_env(secret_names, *, existing_values=None, display_name=None)

Render the canonical bot .env file.

Source code in src/openenv/envfiles/secret_env.py
def render_secret_env(
    secret_names: list[str],
    *,
    existing_values: dict[str, str] | None = None,
    display_name: str | None = None,
) -> str:
    """Render the canonical bot .env file."""
    values = existing_values or {}
    names = _unique_preserving_order(secret_names)
    header = [
        (
            f"# Secret references for {display_name}"
            if display_name
            else "# Secret references"
        ),
        "# Keys declared here are synthesized into runtime.secret_refs for the bot.",
    ]
    lines = list(header)
    for name in names:
        lines.append("")
        lines.append(f"{name}={values.get(name, '')}")
    return "\n".join(lines).rstrip() + "\n"

secret_env_path(directory)

Return the canonical sidecar env path for a manifest directory.

Source code in src/openenv/envfiles/secret_env.py
def secret_env_path(directory: str | Path) -> Path:
    """Return the canonical sidecar env path for a manifest directory."""
    return Path(directory) / BOT_SECRET_ENV_FILENAME

write_secret_env(path, secret_names, *, existing_values=None, display_name=None)

Write the canonical bot .env file.

Source code in src/openenv/envfiles/secret_env.py
def write_secret_env(
    path: str | Path,
    secret_names: list[str],
    *,
    existing_values: dict[str, str] | None = None,
    display_name: str | None = None,
) -> None:
    """Write the canonical bot .env file."""
    Path(path).write_text(
        render_secret_env(
            secret_names,
            existing_values=existing_values,
            display_name=display_name,
        ),
        encoding="utf-8",
    )