Metadata-Version: 2.4
Name: ripv-py
Version: 1.0.0
Summary: Python SDK for PersonalVault - secure API key retrieval
License-Expression: MIT
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.24.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: respx>=0.20; extra == "dev"

# PersonalVault Python SDK

Secure API key retrieval for Python backend applications.

## Installation

```bash
pip install personalvault
```

## Quick Start

### Environment Variables

```bash
export PV_API_KEY="pvault_bk_..."       # Required - your backend key
export PV_API_URL="https://pv.example.com"  # Optional - defaults to http://localhost:8923
export PV_CACHE_TTL="3600"              # Optional - cache TTL in seconds (default: 3600)
```

### Synchronous (Django, Flask)

```python
from personalvault import init, pv

# Initialize once at startup
init()

# Retrieve keys instantly from cache
openai_key = pv("OPENAI_API_KEY")
db_password = pv("DB_PASS")
```

### Asynchronous (FastAPI, AIOHTTP)

```python
from personalvault import pv_async

# Auto-initializes on first call, then serves from cache
openai_key = await pv_async("OPENAI_API_KEY")
db_password = await pv_async("DB_PASS")
```

## API Reference

### `init() -> None`
Synchronously fetch and cache all allowed keys. Blocks until complete.

### `pv(key_name: str) -> str`
Synchronous cache lookup. Raises `PVNotInitializedError` if `init()` hasn't been called or cache has expired.

### `pv_async(key_name: str) -> str`
Async key retrieval. Automatically initializes/refreshes the cache when needed.

## Exceptions

| Exception | When |
|---|---|
| `PVAuthError` | `PV_API_KEY` missing, invalid, or expired |
| `PVDeniedError` | Server rejected request (IP allowlist, etc.) |
| `PVKeyNotFoundError` | Key not in vault or not allowed for this backend key |
| `PVNotInitializedError` | `pv()` called before `init()` or cache expired |

## Framework Examples

### FastAPI

```python
from contextlib import asynccontextmanager
from fastapi import FastAPI
from personalvault import pv_async, pv

@asynccontextmanager
async def lifespan(app: FastAPI):
    await pv_async("DB_URL")  # warm the cache
    yield

app = FastAPI(lifespan=lifespan)

@app.get("/")
async def root():
    return {"db": pv("DB_URL")}
```

### Flask

```python
from flask import Flask
from personalvault import init, pv

app = Flask(__name__)
init()  # fetch keys at import time

@app.route("/")
def index():
    return {"db": pv("DB_URL")}
```

### Django (settings.py)

```python
from personalvault import init, pv

init()

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "PASSWORD": pv("DB_PASS"),
    }
}
```
