# PromptCanvas Python SDK

High-performance Python SDK for fetching prompts from PromptCanvas with built-in caching, prefetching, and variable resolution.

## Features

- **Sync & Async** - Full support for both synchronous and asynchronous code
- **LRU Caching** - Thread-safe cache with TTL and stale-while-revalidate
- **Prefetching** - Warm up cache on initialization
- **Variable Resolution** - Template interpolation with `{{ variable }}` syntax
- **Autocomplete** - Slug suggestions for IDE integration
- **Batch Fetching** - Single request for multiple prompts
- **Type Hints** - Full type annotations with dataclasses
- **CLI Support** - Pull prompts to local files with `promptcanvas pull`
- **Local Mode** - Load prompts from local files for offline/fast access

## Installation

```bash
pip install promptcanvas
```

Or install from source:

```bash
cd packages/python-sdk
pip install -e .
```

## Quick Start

```python
from promptcanvas import PromptCanvas

# Initialize
pc = PromptCanvas(api_key="pc_live_xxx")

# Fetch a prompt
prompt = pc.get("system-prompt")
print(prompt.content)

# With variables
prompt = pc.get("welcome-message", variables={
    "user_name": "John",
    "company": "Acme"
})
# Template: "Hello {{ user_name }}, welcome to {{ company }}!"
# Result: "Hello John, welcome to Acme!"
```

## CLI - Pull Prompts Locally

Pull production prompts from PromptCanvas and store them as local files. Perfect for:
- **Offline development** - No network dependency
- **Version control** - Track prompt changes in git
- **Fast access** - Zero latency, no API calls
- **CI/CD** - Include prompts in your build process

### Setup

```bash
# Initialize config file
promptcanvas init
```

This creates a `promptcanvas.json` file:

```json
{
  "apiKey": "${PROMPTCANVAS_API_KEY}",
  "outputDir": "prompts",
  "format": "json",
  "includeMetadata": true
}
```

### Pull Prompts

```bash
# Pull a specific prompt
promptcanvas pull system-prompt

# Pull all production prompts
promptcanvas pull --all

# List available prompts
promptcanvas list
```

### Use Local Prompts

```python
from promptcanvas import LocalPrompts

# Load prompts from local files
prompts = LocalPrompts(dir="./prompts")

# Get a prompt
system_prompt = prompts.get("system-prompt")
print(system_prompt.content)

# With variables
welcome = prompts.get("welcome-message", variables={"user_name": "John"})
```

Or use the simple getter:

```python
from promptcanvas import create_local_getter

get_prompt = create_local_getter("./prompts")

# Returns just the content string
content = get_prompt("system-prompt")
with_vars = get_prompt("welcome", name="John")
```

## Async Usage

```python
import asyncio
from promptcanvas import PromptCanvas

async def main():
    pc = PromptCanvas(api_key="pc_live_xxx")
    
    # Async fetch
    prompt = await pc.aget("system-prompt")
    print(prompt.content)
    
    # Batch fetch
    result = await pc.aget_many(["prompt-1", "prompt-2"])
    for p in result.prompts:
        print(p.slug, p.content)
    
    await pc.aclose()

asyncio.run(main())
```

## Configuration

```python
pc = PromptCanvas(
    # Required
    api_key="pc_live_xxx",
    
    # Optional
    base_url="https://your-instance.supabase.co/functions/v1/sdk",
    timeout=10.0,           # Request timeout in seconds
    cache_ttl=300.0,        # Cache TTL in seconds (5 minutes)
    cache_max_size=1000,    # Max cached prompts
    stale_while_revalidate=True,  # Return stale while refreshing
    max_retries=3,          # Retry attempts
    prefetch=["critical-prompt"],  # Prefetch on init
    debug=False,            # Enable debug logging
)
```

## API Reference

### Sync Methods

| Method | Description |
|--------|-------------|
| `get(slug, variables?, skip_cache?)` | Fetch single prompt |
| `get_many(slugs, variables?, skip_cache?)` | Batch fetch |
| `suggest(query, limit?)` | Autocomplete suggestions |
| `list(category?, limit?, offset?)` | List all prompts |
| `warmup(slugs)` | Pre-populate cache |
| `invalidate(slug)` | Remove from cache |
| `clear_cache()` | Clear all cache |
| `get_cache_stats()` | Get cache statistics |

### Async Methods

| Method | Description |
|--------|-------------|
| `aget(slug, variables?, skip_cache?)` | Fetch single prompt |
| `aget_many(slugs, variables?, skip_cache?)` | Batch fetch |
| `asuggest(query, limit?)` | Autocomplete suggestions |
| `alist(category?, limit?, offset?)` | List all prompts |
| `awarmup(slugs)` | Pre-populate cache |
| `ainvalidate(slug)` | Remove from cache |
| `aclear_cache()` | Clear all cache |

## Error Handling

```python
from promptcanvas import (
    PromptCanvas,
    PromptNotFoundError,
    NoProductionVersionError,
    RateLimitError,
    AuthenticationError,
)

pc = PromptCanvas(api_key="pc_live_xxx")

try:
    prompt = pc.get("my-prompt")
except PromptNotFoundError as e:
    print(f"Prompt not found: {e.slug}")
except NoProductionVersionError as e:
    print(f"No production version: {e.slug}")
except RateLimitError as e:
    print(f"Rate limited, retry after {e.retry_after_ms}ms")
except AuthenticationError:
    print("Invalid API key")
```

## Context Manager

```python
# Sync
with PromptCanvas(api_key="xxx") as pc:
    prompt = pc.get("my-prompt")

# Async
async with PromptCanvas(api_key="xxx") as pc:
    prompt = await pc.aget("my-prompt")
```

## Variable Resolution

```python
from promptcanvas import VariableResolver

# Extract variables from template
template = "Hello {{ name }}, your order {{ order_id }} is ready."
variables = VariableResolver.extract(template)
# ['name', 'order_id']

# Validate variables
valid, missing = VariableResolver.validate(template, {"name": "John"})
# (False, ['order_id'])

# Create resolver with globals
resolver = VariableResolver.create_with_globals({"app_name": "MyApp"})
result = resolver("Welcome to {{ app_name }}, {{ user }}!", {"user": "John"})
# "Welcome to MyApp, John!"
```

## License

MIT
