Metadata-Version: 2.4
Name: jh-vault-helper
Version: 0.1.0
Summary: Reusable HashiCorp Vault helper and secret refresher utilities for Python services.
Author: Jacaranda Health Tech Team
Project-URL: Homepage, https://github.com/Jacaranda-Health/hashicorp-self-deplyment
Project-URL: Repository, https://github.com/Jacaranda-Health/hashicorp-self-deplyment
Keywords: hashicorp,vault,secrets,config
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Security
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.31.0

# jh-vault-helper

Reusable HashiCorp Vault helpers for Python services.

This package provides:

- `VaultKVClient` for Vault KV v2 reads
- `load_env_from_vault()` to load secrets into `os.environ`
- `VaultSecretRefresher` for background refresh
- `start_vault_auto_refresh()` and `stop_vault_auto_refresh()` convenience helpers

## Installation

```bash
pip install jh-vault-helper
```

## Quick Start

```python
from jh_vault_helper import load_env_from_vault

load_env_from_vault(
    mount="mentors",
    secret_path="staging",
    override_existing=False,
)
```

## `config.py` Integration

```python
import logging
import os
import sys

from dotenv import load_dotenv
from jh_vault_helper import load_env_from_vault

load_dotenv(override=True)

logger = logging.getLogger("config")


def is_production_environment() -> bool:
    return (os.getenv("APP_ENV") or "").strip().lower() == "production"


def load_vault_configuration() -> None:
    errors = []

    env_mount = os.getenv("VAULT_KV_MOUNT")
    env_path = os.getenv("VAULT_KV_PATH")
    if not env_path:
        env_path = "production" if is_production_environment() else "staging"

    shared_mount = os.getenv("VAULT_SHARED_MOUNT", "shared")
    shared_path = os.getenv("VAULT_SHARED_PATH", "services/common")

    def fetch(label: str, mount: str | None, secret_path: str | None) -> None:
        if not mount or not secret_path:
            return
        try:
            load_env_from_vault(
                mount=mount,
                secret_path=secret_path,
                override_existing=False,
            )
        except Exception as exc:
            errors.append((label, mount, secret_path, exc))

    fetch("environment", env_mount, env_path)
    fetch("shared", shared_mount, shared_path)

    if errors:
        for label, mount, secret_path, exc in errors:
            logger.error(
                "Vault load failed for %s secrets (mount=%s path=%s): %s",
                label,
                mount,
                secret_path,
                exc,
            )

        if is_production_environment():
            sys.exit(1)
```

## Auto Refresh

```python
from jh_vault_helper import start_vault_auto_refresh, stop_vault_auto_refresh

start_vault_auto_refresh(refresh_interval=300)

# On shutdown
stop_vault_auto_refresh()
```

## Environment Variables

Required:

- `VAULT_ADDR`
- `VAULT_TOKEN` or `VAULT_ROLE_ID` + `VAULT_SECRET_ID`
- `VAULT_KV_MOUNT` unless passed explicitly

Optional:

- `VAULT_KV_PATH`
- `VAULT_NAMESPACE`
- `VAULT_VERIFY`
- `VAULT_TIMEOUT`
- `VAULT_AUTO_REFRESH`
- `VAULT_REFRESH_INTERVAL`
- `VAULT_SHARED_MOUNT`
- `VAULT_SHARED_PATH`
- `APP_ENV`

If `VAULT_KV_PATH` is unset, the package defaults to `production` when `APP_ENV=production` and `staging` otherwise.

## Release Workflow

```bash
python -m build
twine check dist/*
twine upload --repository-url https://test.pypi.org/legacy/ dist/*
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ jh-vault-helper==0.1.0
twine upload dist/*
```

If a version is already published, bump the patch version in `pyproject.toml`, rebuild, and publish the new version.
