# Python SDK

Official Python SDK for KeyEnv.

Source: https://keyenv.dev/docs/sdks/python/

Official Python SDK for KeyEnv with full type annotations.

## Installation

```bash
pip install keyenv
```

Or with poetry:

```bash
poetry add keyenv
```

## Quick Start

```python
from keyenv import KeyEnv
import os

client = KeyEnv(token=os.environ["KEYENV_TOKEN"])

# Load secrets into os.environ
client.load_env("your-project-id", "production")
print(os.environ["DATABASE_URL"])
```

## Initialize the Client

```python
from keyenv import KeyEnv

client = KeyEnv(
    token="your-service-token",
    timeout=30.0,  # optional, default 30s
)
```

### Context Manager

Use as a context manager for automatic cleanup:

```python
with KeyEnv(token="your-token") as client:
    secrets = client.export_secrets("project-id", "production")
```

## Loading Secrets

### Load into os.environ

The simplest way to use secrets in your application:

```python
count = client.load_env("project-id", "production")
print(f"Loaded {count} secrets")

# Now use them
print(os.environ["DATABASE_URL"])
print(os.environ["API_KEY"])
```

### Export as Dictionary

Get secrets as a key-value dictionary:

```python
env = client.export_secrets_as_dict("project-id", "production")
print(env["DATABASE_URL"])
print(env["API_KEY"])
```

### Export as List

Get secrets with metadata:

```python
secrets = client.export_secrets("project-id", "production")
for secret in secrets:
    print(f"{secret.key}={secret.value}")
```

## Managing Secrets

### Get a Single Secret

```python
secret = client.get_secret("project-id", "production", "DATABASE_URL")
print(secret.value)
print(secret.description)
```

### Set a Secret

Creates or updates a secret:

```python
client.set_secret("project-id", "production", "API_KEY", "sk_live_...")

# With description
client.set_secret(
    "project-id",
    "production",
    "API_KEY",
    "sk_live_...",
    description="Production API key"
)
```

### Delete a Secret

```python
client.delete_secret("project-id", "production", "OLD_KEY")
```

## Bulk Operations

### Bulk Import

Import multiple secrets at once:

```python
from keyenv import BulkSecretItem

result = client.bulk_import(
    "project-id",
    "development",
    [
        BulkSecretItem(key="DATABASE_URL", value="postgres://localhost/mydb"),
        BulkSecretItem(key="REDIS_URL", value="redis://localhost:6379"),
        {"key": "API_KEY", "value": "sk_test_..."},  # Also accepts dicts
    ],
    overwrite=True,
)
print(f"Created: {result.created}, Updated: {result.updated}")
```

### Generate .env File

```python
env_content = client.generate_env_file("project-id", "production")
with open(".env", "w") as f:
    f.write(env_content)
```

## Projects & Environments

### List Projects

```python
projects = client.list_projects()
for project in projects:
    print(f"{project.name} ({project.id})")
```

### Get Project Details

```python
project = client.get_project("project-id")
print(f"Project: {project.name}")
for env in project.environments:
    print(f"  - {env.name}")
```

### List Environments

```python
environments = client.list_environments("project-id")
for env in environments:
    print(env.name)
```

## Environment Permissions

Manage who can access secrets in each environment.

### List Permissions

```python
permissions = client.list_permissions("project-id", "production")
for perm in permissions:
    print(f"{perm.user_email}: {perm.role}")
```

### Set Permission

```python
# Grant write access to a user
client.set_permission("project-id", "production", "user-id", "write")
```

### Delete Permission

```python
client.delete_permission("project-id", "production", "user-id")
```

### Get My Permissions

```python
permissions, is_team_admin = client.get_my_permissions("project-id")
for perm in permissions:
    print(f"{perm.environment_name}: {perm.role} (can_write: {perm.can_write})")
```

### Bulk Set Permissions

```python
client.bulk_set_permissions("project-id", "production", [
    {"user_id": "user-1", "role": "write"},
    {"user_id": "user-2", "role": "read"},
])
```

### Project Defaults

```python
# Get default permissions
defaults = client.get_project_defaults("project-id")

# Set default permissions for new team members
client.set_project_defaults("project-id", [
    {"environment_name": "development", "default_role": "write"},
    {"environment_name": "staging", "default_role": "read"},
    {"environment_name": "production", "default_role": "none"},
])
```

## Error Handling

```python
from keyenv import KeyEnv, KeyEnvError

try:
    secret = client.get_secret("project-id", "production", "MISSING_KEY")
except KeyEnvError as e:
    print(f"Error {e.status}: {e.message}")

    if e.status == 401:
        print("Invalid or expired token")
    elif e.status == 403:
        print("Access denied")
    elif e.status == 404:
        print("Secret not found")
```

## Type Hints

The SDK includes full type annotations for better IDE support:

```python
from keyenv import Secret, SecretWithValue, Project, Environment
```

## API Reference

### Constructor Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `token` | `str` | Yes | - | Service token |
| `timeout` | `float` | No | `30.0` | Request timeout (seconds) |

### Methods

| Method | Description |
|--------|-------------|
| `get_current_user()` | Get current user/token info |
| `list_projects()` | List all accessible projects |
| `get_project(id)` | Get project with environments |
| `list_environments(project_id)` | List environments in a project |
| `list_secrets(project_id, env)` | List secret keys (no values) |
| `export_secrets(project_id, env)` | Export secrets with values |
| `export_secrets_as_dict(project_id, env)` | Export as dictionary |
| `get_secret(project_id, env, key)` | Get single secret |
| `set_secret(project_id, env, key, value)` | Create or update secret |
| `delete_secret(project_id, env, key)` | Delete secret |
| `bulk_import(project_id, env, secrets)` | Bulk import secrets |
| `load_env(project_id, env)` | Load secrets into os.environ |
| `generate_env_file(project_id, env)` | Generate .env file content |
| `list_permissions(project_id, env)` | List permissions for an environment |
| `set_permission(project_id, env, user_id, role)` | Set user's permission |
| `delete_permission(project_id, env, user_id)` | Delete user's permission |
| `bulk_set_permissions(project_id, env, permissions)` | Bulk set permissions |
| `get_my_permissions(project_id)` | Get current user's permissions |
| `get_project_defaults(project_id)` | Get default permissions |
| `set_project_defaults(project_id, defaults)` | Set default permissions |

## Examples

### FastAPI Application

```python
from fastapi import FastAPI
from keyenv import KeyEnv
import os

# Load secrets at startup
client = KeyEnv(token=os.environ["KEYENV_TOKEN"])
client.load_env(os.environ["KEYENV_PROJECT"], "production")

app = FastAPI()

@app.get("/")
async def root():
    return {"status": "ok"}

@app.get("/config")
async def config():
    return {
        "api_url": os.environ.get("API_URL"),
        # Don't expose sensitive secrets!
    }
```

### Django Settings

```python
# settings.py
from keyenv import KeyEnv
import os

# Load secrets before Django initializes
if os.environ.get("KEYENV_TOKEN"):
    client = KeyEnv(token=os.environ["KEYENV_TOKEN"])
    client.load_env(os.environ["KEYENV_PROJECT"], os.environ.get("ENV", "development"))

# Now use secrets in settings
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "HOST": os.environ.get("DB_HOST"),
        "NAME": os.environ.get("DB_NAME"),
        "USER": os.environ.get("DB_USER"),
        "PASSWORD": os.environ.get("DB_PASSWORD"),
    }
}

SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")
```

### Flask Application

```python
from flask import Flask
from keyenv import KeyEnv
import os

# Load secrets
client = KeyEnv(token=os.environ["KEYENV_TOKEN"])
client.load_env(os.environ["KEYENV_PROJECT"], "production")

app = Flask(__name__)
app.config["SECRET_KEY"] = os.environ["FLASK_SECRET_KEY"]

@app.route("/")
def hello():
    return {"status": "ok"}
```

### Script/CLI Tool

```python
#!/usr/bin/env python3
import argparse
from keyenv import KeyEnv
import os

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--env", default="development")
    args = parser.parse_args()

    client = KeyEnv(token=os.environ["KEYENV_TOKEN"])
    secrets = client.export_secrets_as_dict("my-project", args.env)

    print(f"Database: {secrets['DATABASE_URL']}")
    # Do something with secrets...

if __name__ == "__main__":
    main()
```
