# octamem

Official Python SDK for [OctaMem](https://octamem.com) - Memory as a Service platform.

Access your OctaMem memories from Python with both synchronous and asynchronous clients.

## Installation

From PyPI:

```bash
pip install octamem
```

**Install locally (use in another folder like pip install):**

From the octamem-python directory:

```bash
cd /path/to/octamem-python
pip install -e .
```

Or from any folder (use the path to octamem-python):

```bash
pip install -e /path/to/octamem-python
```

`-e` is editable mode: the package is linked, so you can change the SDK code and use it in other projects without reinstalling. After this, `import octamem` works from any Python file in that environment.

## Quick Start

```python
from octamem import OctaMem

# Create client with your API key
client = OctaMem(api_key="your-api-key")

# Or use environment variable OCTAMEM_API_KEY
client = OctaMem()

# Search memories
results = client.get(query="meeting notes")
print(results)

# Add new memory
client.add(content="Remember to call John tomorrow")

# Get memory details
info = client.details()
print(f"Usage: {info.usage}/{info.limit}")
```

## Async Usage

```python
import asyncio
from octamem import AsyncOctaMem

async def main():
    async with AsyncOctaMem(api_key="your-api-key") as client:
        # Search memories
        results = await client.get(query="meeting notes")
        print(results)

        # Add memory
        await client.add(content="New memory content")

asyncio.run(main())
```

## Working with Multiple Memories

Each API key is linked to a specific memory. Use different keys for different memories:

```python
from octamem import OctaMem

# Personal memory
personal = OctaMem(api_key="personal-memory-api-key")

# Crypto knowledge base
crypto = OctaMem(api_key="crypto-memory-api-key")

# Search personal memories
notes = personal.get(query="project ideas")

# Add to personal memory
personal.add(content="New project idea: AI assistant")

# Search crypto knowledge
btc_info = crypto.get(query="bitcoin halving")
```

## Methods

### `get(query)` - Search Memories

Fetch memories matching your query.

```python
results = client.get(query="search term")
```

### `add(content)` - Add Memory

Store new content in your memory.

```python
result = client.add(content="Buy groceries tomorrow")
```

### `details()` - Memory Information

Get information about your memory.

```python
info = client.details()
print(f"Usage: {info.usage}, Limit: {info.limit}")
```

## Configuration

```python
client = OctaMem(
    api_key="your-api-key",      # Or set OCTAMEM_API_KEY env var
    base_url="https://platform.octamem.com",  # Custom API endpoint
    timeout=30.0,                 # Request timeout in seconds
    max_retries=3,               # Retry attempts on failure
    retry_delay=1.0,             # Base delay between retries
)
```

## Error Handling

```python
from octamem import (
    OctaMem,
    AuthenticationError,
    MethodNotSupportedError,
    APIConnectionError,
    RateLimitError,
    ValidationError,
)

client = OctaMem()

try:
    result = client.add(content="test")
except AuthenticationError:
    print("Invalid or expired API key")
except MethodNotSupportedError:
    print("This memory does not support adding content")
except APIConnectionError:
    print("Network error - check your connection")
except RateLimitError as e:
    print(f"Rate limited - retry after {e.retry_after} seconds")
except ValidationError as e:
    print(f"Invalid input: {e.message}")
```

### Exception Hierarchy

```
OctaMemError (base)
├── AuthenticationError      # 401 - Invalid API key
├── MethodNotSupportedError  # 403 - Method not available
├── NotFoundError            # 404 - Resource not found
├── ValidationError          # 400 - Invalid input
├── RateLimitError           # 429 - Too many requests
├── APIError                 # Other API errors
├── APIConnectionError       # Network failures
├── APITimeoutError          # Request timeout
└── InternalServerError      # 5xx errors
```

## Context Manager Support

Both sync and async clients support context managers for automatic cleanup:

```python
# Sync
with OctaMem() as client:
    results = client.get(query="test")

# Async
async with AsyncOctaMem() as client:
    results = await client.get(query="test")
```

## Type Safety

Full type hints and Pydantic models for IDE autocomplete and type checking:

```python
from octamem import OctaMem, GetParams, MemoryDetails

client = OctaMem()

# Type-safe parameters
params: GetParams = {"query": "test"}
results = client.get(params)

# Typed response models
details: MemoryDetails = client.details()
print(details.usage)  # int | None
print(details.limit)  # int | None
```

## Project structure (pip/SDK layout)

The repo follows the standard **src layout** for a pip-installable package:

```
octamem-python/
├── pyproject.toml      # Build metadata, deps, tool config (PEP 517/518)
├── README.md
├── LICENSE            # MIT
├── .gitignore
├── test_sdk.py        # Optional: manual test script
└── src/
    └── octamem/       # The installable package (import octamem)
        ├── __init__.py
        ├── py.typed   # PEP 561: marks package as typed
        ├── _client.py
        ├── _async_client.py
        ├── _http.py
        ├── _types.py
        ├── _constants.py
        ├── _exceptions.py
        └── _validators.py
```

- **src layout**: Package lives under `src/octamem/` so `pip install` (or `pip install -e .`) installs the package correctly; no need to change this.
- **pyproject.toml**: Single source for build (hatchling), dependencies, and metadata; no `setup.py` needed.
- **py.typed**: Empty file that marks the package as typed for type checkers.

## Requirements

- Python 3.9+
- httpx
- pydantic

## License

MIT
