Metadata-Version: 2.4
Name: synfire
Version: 0.0.2
Summary: Python SDK and CLI for the Synfire Model Registry
Project-URL: Homepage, https://synfire.dev
Project-URL: Documentation, https://synfire.dev/docs/sdk
Author-email: Synfire <contact@synfire.dev>
License-Expression: LicenseRef-Synfire-Proprietary
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<1.0.0,>=0.25.0
Requires-Dist: keyring<26.0.0,>=24.0.0
Requires-Dist: pydantic<3.0.0,>=2.0.0
Requires-Dist: rich<14.0.0,>=13.0.0
Requires-Dist: tomli>=1.1.0; python_version < '3.11'
Requires-Dist: typer<1.0.0,>=0.9.0
Provides-Extra: dev
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: respx>=0.20.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Provides-Extra: nir
Requires-Dist: nir>=1.0.0; extra == 'nir'
Description-Content-Type: text/markdown

# Synfire Python Package

Python SDK and CLI for the [Synfire Model Registry](https://synfire.dev) - a public registry for hosting and sharing neuromorphic models in the Neuromorphic Intermediate Representation (NIR) format.

## Installation

```bash
pip install synfire
```

This installs both:
- the Python SDK (`from synfire import Client`)
- the CLI (`synfire ...`)

To load NIR models directly (recommended):

```bash
pip install synfire[nir]
```

For development:

```bash
pip install synfire[dev]
```

## Quick Start

### CLI

```bash
synfire --help
synfire login
synfire whoami
```

### Python

```python
from synfire import Client

# Create a client (uses stored credentials)
client = Client()

# Check who you're authenticated as
user = client.whoami()
print(f"Logged in as: {user.email}")

# Search for models
results = client.search("gesture", platforms=["T1"])
for repo in results:
    print(f"{repo.full_name}: {repo.latest_version}")

# Download a model
release = client.pull("nrg-lab/gesture-recognition", version="1.0.0")

# Load the NIR graph (requires: pip install synfire[nir])
graph = release.load()
```

## Authentication

The SDK resolves credentials in this order:

1. **Explicit token parameter**: `Client(token="synfire_...")`
2. **Environment variable**: `SYNFIRE_TOKEN`
3. **Credential store**: Tokens stored via CLI login (OS keyring)

### Interactive Use (Recommended)

For local development, use the CLI bundled with this package to log in:

```bash
# Login (stores token securely in OS keyring)
synfire login

# Verify authentication
synfire whoami
```

Then create a client without specifying a token:

```python
from synfire import Client

client = Client()  # Uses token from keyring
user = client.whoami()
print(f"Authenticated as: {user.email}")
```

### CI/CD Pipelines

For automated environments, use the environment variable:

```bash
export SYNFIRE_TOKEN="synfire_your_token_here"
```

```python
from synfire import Client

# Token resolved from environment variable
client = Client()
```

### Explicit Token (Testing/Scripts)

For scripts or testing, pass the token directly:

```python
from synfire import Client

client = Client(token="synfire_your_token_here")
```

### Token Validation

By default, the Client validates the token on initialization by calling the API.
This ensures you get immediate feedback if the token is invalid:

```python
from synfire import Client, AuthenticationError

try:
    client = Client()
    print(f"Valid token for: {client.user.email}")
except AuthenticationError as e:
    print(f"Authentication failed: {e}")
```

To skip validation (e.g., for offline preparation):

```python
client = Client(token="synfire_...", validate_token=False)
```

### Managing Credentials Programmatically

Use `CredentialStore` for direct credential management:

```python
from synfire import CredentialStore

# Store a token (equivalent to 'synfire login')
CredentialStore.store_token("synfire_your_token_here")

# Get the current token (checks env var first, then keyring)
token = CredentialStore.get_token()

# Check if a token is available
if CredentialStore.has_token():
    print("Token available")

# Check the token source
if CredentialStore.is_using_env_token():
    print("Using environment variable")
elif CredentialStore.has_stored_token():
    print("Using stored credential")

# Delete stored token (equivalent to 'synfire logout')
CredentialStore.delete_token()
```

## Client API

### Initialization

```python
from synfire import Client

# Default: uses credential resolution and validates token
client = Client()

# Explicit token
client = Client(token="synfire_...")

# Custom API URL (for local testing or a custom deployment)
client = Client(base_url="http://localhost:8000/api/v1")

# Skip token validation
client = Client(token="synfire_...", validate_token=False)
```

### Context Manager Support

The client supports context managers for automatic resource cleanup:

```python
with Client() as client:
    user = client.whoami()
    # ... use client ...
# Resources automatically released
```

Or manually close:

```python
client = Client()
try:
    user = client.whoami()
finally:
    client.close()
```

### Properties

```python
client = Client()

# How the token was resolved: 'explicit', 'environment', or 'keyring'
print(client.token_source)

# The configured API base URL
print(client.base_url)

# Cached user info (available if validate_token=True)
if client.user:
    print(f"User: {client.user.email}")
```

### User Methods

```python
# Get authenticated user info
user = client.whoami()
print(f"Email: {user.email}")
print(f"Username: {user.username}")
print(f"Namespace: {user.username}")
```

### Repository Methods

```python
# Get repository information
repo = client.get_repository("org/repo-name")
print(f"Latest version: {repo.latest_version}")
print(f"Summary: {repo.summary}")

# Get release information
release = client.get_release("org/repo-name", version="1.0.0")
release_latest = client.get_release("org/repo-name")  # Gets latest
```

### Search

```python
# Search for repositories
results = client.search("gesture", platforms=["T1", "Loihi2"])
for repo in results:
    print(f"{repo.full_name}: {repo.summary}")

# Search with tags
results = client.search("vision", tags=["classification"])

# Pagination
page1 = client.search("model", limit=10, offset=0)
page2 = client.search("model", limit=10, offset=10)
```

### Pull

```python
# Download latest version
release = client.pull("org/repo-name")

# Download specific version
release = client.pull("org/repo-name", version="1.0.0")

# Alternative syntax
release = client.pull("org/repo-name:1.0.0")

# Custom output directory
release = client.pull("org/repo-name", output_dir=Path("./my-models"))

# Load the NIR graph
graph = release.load()  # Requires synfire[nir]
```

### Publish

```python
from pathlib import Path

# Publish a new release
release = client.publish(
    "my-org/my-model",
    version="1.0.0",
    model_path=Path("./model.nir"),
    card_path=Path("./nir-card.json"),
    notes_path=Path("./RELEASE_NOTES.md"),  # Optional
)
```

### Verify

```python
# Verify file integrity against registry
is_valid = client.verify(
    Path("./model.nir"),
    "org/repo-name",
    "1.0.0"
)
if is_valid:
    print("File integrity verified!")
```

## Hardware Targets

The registry supports models targeting various neuromorphic platforms:

| Target | Description |
|--------|-------------|
| `T1` | Innatera T1 SoC |
| `Pulsar` | Innatera Pulsar (C1) SoC |
| `Loihi2` | Intel Loihi 2 |
| `SpiNNaker2` | SpiNNaker 2 |
| `BrainScaleS-2` | BrainScaleS-2 |
| `Xylo` | SynSense Xylo |
| `Speck` | SynSense Speck |
| `Generic` | Software simulation |

Use the `HardwareTarget` enum for type-safe values:

```python
from synfire import HardwareTarget

# In search queries
results = client.search("gesture", platforms=[HardwareTarget.T1.value])

# Check valid targets
print(list(HardwareTarget))
```

## Exception Handling

All SDK exceptions inherit from `SynfireError`:

```python
from synfire import (
    Client,
    SynfireError,
    AuthenticationError,
    ValidationError,
    NotFoundError,
    ConflictError,
    TombstonedError,
    NetworkError,
    ChecksumMismatchError,
)

try:
    client = Client()
    release = client.pull("org/repo:1.0.0")
except AuthenticationError as e:
    # Invalid/missing token or insufficient permissions
    print(f"Auth error: {e.message}")
    print(f"Error code: {e.error_code}")
except NotFoundError as e:
    # Repository or version doesn't exist
    print(f"Not found: {e.message}")
except TombstonedError as e:
    # Release was tombstoned (soft-deleted)
    print(f"Release unavailable: {e.reason}")
except NetworkError as e:
    # Connection issues
    print(f"Network error: {e.message}")
except SynfireError as e:
    # Catch-all for any SDK error
    print(f"Error: {e.message}")
```

### Exception Details

| Exception | Cause | Resolution |
|-----------|-------|------------|
| `AuthenticationError` | Invalid/expired token, missing token, insufficient permissions | Run `synfire login` or check SYNFIRE_TOKEN |
| `ValidationError` | Invalid data format, schema validation failure | Check nir-card.json and model.nir format |
| `NotFoundError` | Repository/version doesn't exist | Verify repository name and version |
| `ConflictError` | Version already exists | Use a different version number |
| `TombstonedError` | Release was tombstoned | Use a different version |
| `NetworkError` | Connection failed, timeout | Check internet connection |
| `ChecksumMismatchError` | Downloaded file corrupted | Re-download the file |

## Using with NIR Frameworks

Load models into NIR-compatible frameworks:

```python
from synfire import Client

client = Client()
release = client.pull("nrg-lab/gesture-recognition:1.0.0")
graph = release.load()

# Use with Norse (PyTorch)
import norse.torch as norse
model = norse.from_nir(graph)

# Or with snnTorch
import snntorch as snn
model = snn.from_nir(graph)

# Or with Sinabs
import sinabs
model = sinabs.from_nir(graph)

# Or with Lava
from lava.lib.dl import nir as lava_nir
model = lava_nir.from_nir(graph)
```

## Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `SYNFIRE_TOKEN` | API token for authentication | - |
| `SYNFIRE_API_URL` | Override API base URL | `https://synfire.dev/api/v1` |

## Type Hints

The SDK is fully typed with PEP 561 support. Type stubs are included:

```python
from synfire import Client, User, Repository, Release

def get_latest_release(client: Client, repo: str) -> Release:
    return client.get_release(repo)
```

## Thread Safety

The `Client` class is **not** thread-safe. Create separate client instances
for concurrent use:

```python
import threading
from synfire import Client

def worker():
    with Client() as client:
        # Use client within this thread
        user = client.whoami()

threads = [threading.Thread(target=worker) for _ in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()
```

## Logging

Enable debug logging to see API calls:

```python
import logging

logging.basicConfig(level=logging.DEBUG)
logging.getLogger("synfire").setLevel(logging.DEBUG)

client = Client()  # Will log API calls
```

## Documentation

- **SDK Documentation**: [docs.synfire.dev/sdk](https://docs.synfire.dev/sdk)
- **CLI Reference**: [docs.synfire.dev/cli](https://docs.synfire.dev/cli)
- **API Reference**: [docs.synfire.dev/api](https://docs.synfire.dev/api)
- **NIR Specification**: [github.com/neuromorphs/NIR](https://github.com/neuromorphs/NIR)

## Contributing

Contributions are currently managed by the Synfire maintainers. To report issues
or get in touch about contributing, contact us at [contact@synfire.dev](mailto:contact@synfire.dev).

## License

Copyright (c) Innatera. All rights reserved.

This package may be downloaded, installed, and used to access the Synfire platform.
No permission is granted to copy, modify, redistribute, sublicense, or create
derivative works except as explicitly permitted by Innatera.
