Metadata-Version: 2.4
Name: nexus-client-py
Version: 0.1.3
Summary: Python client for the Nexus annotation platform — browse projects, pull files, validate schemas, and push annotations programmatically.
Project-URL: Homepage, https://nexus.recodesolutions.com
Project-URL: Documentation, https://bitbucket.org/intelliusrecodesolutions/nexus-client
Project-URL: Repository, https://bitbucket.org/intelliusrecodesolutions/nexus-client
Project-URL: Issues, https://bitbucket.org/intelliusrecodesolutions/nexus-client/issues
Author-email: KamerAI <dev@kamerai.com>
License: Proprietary
Keywords: annotation,data-labeling,json-extraction,machine-learning,nexus
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.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx<1.0,>=0.27
Requires-Dist: jsonschema<5.0,>=4.0
Requires-Dist: pydantic<3.0,>=2.0
Provides-Extra: dev
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# nexus-client

Python client for the [Nexus](https://nexus.recodesolutions.com) annotation platform. Browse projects, pull file contents, validate annotations, and push results for human QA review.

## Install

```bash
pip install nexus-client
```

## Quick Start

```python
from nexus_client import NexusClient

client = NexusClient(
    url="https://nexus.example.com",
    api_key="nxs_your_api_key_here",
    workspace_id=1,
)

# List projects
for project in client.list_projects():
    print(f"{project.id}: {project.name} ({project.project_type})")
```

## Authentication

Generate an API key from **Settings > API Keys** in the Nexus UI.

- Keys are workspace-scoped (one key = one workspace).
- Keys have a configurable expiry (max 365 days).
- Keys inherit the permissions of the user who created them.

---

## Usage Examples

### Browse Projects and Files

```python
with client.project(5) as project:
    schema = project.schema()
    print(f"Schema: {schema.name} v{schema.version}")

    for file in project.files():
        content = file.content()
        print(f"{file.label}: {len(content)} chars")
```

### Validate and Push Annotations

```python
with client.project(5) as project:
    for file in project.files():
        content = file.content()
        annotation = my_model.predict(content)

        errors = file.validate(annotation)
        if errors:
            print(f"Skipping {file.label}: {errors}")
            continue

        version = file.push_annotation(
            data=annotation,
            annotator_name="InvoiceExtractorV2",
        )
        print(f"{file.label} -> {version.version_label}")
```

### Batch Push

```python
with client.project(5) as project:
    annotations = {}
    for file in project.files():
        annotations[file.id] = my_model.predict(file.content())

    summary = project.push_annotations(
        annotations=annotations,
        annotator_name="InvoiceExtractorV2",
    )
    print(f"{summary.success}/{summary.total} succeeded")
    for err in summary.errors:
        print(f"  File {err['file_id']}: {err['error']}")
```

### Read Annotation Versions

```python
with client.project(5) as project:
    file = project.file(42)

    ai_versions = file.versions(type="ai")
    for v in ai_versions:
        data = file.get_version_data(v.id)
        print(f"{v.version_label}: {list(data.keys())}")
```

---

## API Reference

### `NexusClient`

Main client class. All operations start here.

```python
client = NexusClient(url, api_key, workspace_id, timeout=30.0)
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `url` | `str` | Base URL of the Nexus instance (e.g., `"https://nexus.example.com"`). |
| `api_key` | `str` | API key starting with `"nxs_"`. |
| `workspace_id` | `int` | Workspace ID to scope operations to. |
| `timeout` | `float` | HTTP timeout in seconds. Default `30.0`. |

#### `client.list_projects()`

List all projects in the workspace.

**Returns:** `list[Project]`

```python
projects = client.list_projects()
```

#### `client.get_project(project_id)`

Get a single project by ID.

| Parameter | Type | Description |
|-----------|------|-------------|
| `project_id` | `int` | The project ID. |

**Returns:** `Project`
**Raises:** `NotFoundError` if the project does not exist.

```python
project = client.get_project(5)
```

#### `client.list_files(project_id)`

List all files in a project (deduplicated).

| Parameter | Type | Description |
|-----------|------|-------------|
| `project_id` | `int` | The project ID. |

**Returns:** `list[File]`

```python
files = client.list_files(project_id=5)
```

#### `client.get_file_content(project_id, file_id)`

Get the text content of a file, or the PDF URL for `pdf_json_extraction` projects.

| Parameter | Type | Description |
|-----------|------|-------------|
| `project_id` | `int` | The project ID. |
| `file_id` | `int` | The ProjectFile ID. |

**Returns:** `str` — text content or PDF URL.

```python
content = client.get_file_content(project_id=5, file_id=42)
```

#### `client.get_schema(project_id)`

Get the current JSON schema for a project.

| Parameter | Type | Description |
|-----------|------|-------------|
| `project_id` | `int` | The project ID. |

**Returns:** `Schema`
**Raises:** `NotFoundError` if no schema exists.

```python
schema = client.get_schema(project_id=5)
print(schema.definition)
```

#### `client.validate(data, schema)`

Validate annotation data against a schema. Runs client-side only — no API call.

| Parameter | Type | Description |
|-----------|------|-------------|
| `data` | `dict` | Annotation data to validate. |
| `schema` | `Schema` | Schema object from `get_schema()`. |

**Returns:** `list[str]` — error messages. Empty if valid.

```python
errors = client.validate(data=annotation, schema=schema)
```

#### `client.push_annotation(project_id, file_id, data, annotator_name, metadata=None)`

Push a single annotation as an AI version. Auto-increments version number (a1, a2, ...).

| Parameter | Type | Description |
|-----------|------|-------------|
| `project_id` | `int` | The project ID. |
| `file_id` | `int` | The ProjectFile ID. |
| `data` | `dict` | Annotation data (must match project schema). |
| `annotator_name` | `str` | Your script/tool name (stored as `"EXT: {name}"` in Nexus). |
| `metadata` | `dict \| None` | Optional extra metadata (e.g., `{"method": "gpt-4"}`). |

**Returns:** `AnnotationVersion`
**Raises:** `ValidationError` if data fails schema validation.

```python
annotation = my_model.predict(content)
version = client.push_annotation(
    project_id=5, file_id=42,
    data=annotation,
    annotator_name="InvoiceExtractorV2",
)
print(version.version_label)  # "a1"
```

#### `client.push_annotations(project_id, annotations, annotator_name, metadata=None)`

Push annotations for multiple files in one batch. All files get the same version number.

| Parameter | Type | Description |
|-----------|------|-------------|
| `project_id` | `int` | The project ID. |
| `annotations` | `dict[int, dict]` | Map of `{file_id: annotation_data}`. |
| `annotator_name` | `str` | Your script/tool name. |
| `metadata` | `dict \| None` | Optional extra metadata. |

**Returns:** `PushSummary`

```python
summary = client.push_annotations(
    project_id=5,
    annotations={42: data1, 43: data2},
    annotator_name="InvoiceExtractorV2",
)
```

#### `client.get_file_versions(file_id, type=None)`

List annotation versions for a file.

| Parameter | Type | Description |
|-----------|------|-------------|
| `file_id` | `int` | The ProjectFile ID. |
| `type` | `str \| None` | `"ai"`, `"human"`, or `None` (all). |

**Returns:** `list[AnnotationVersion]`

```python
ai_versions = client.get_file_versions(file_id=42, type="ai")
```

#### `client.get_version_data(file_id, version_id, version_type="ai")`

Fetch the annotation JSON for a specific version.

| Parameter | Type | Description |
|-----------|------|-------------|
| `file_id` | `int` | The ProjectFile ID. |
| `version_id` | `int` | The version ID (from `AnnotationVersion.id`). |
| `version_type` | `str` | `"ai"` (default) or `"human"`. |

**Returns:** `dict` — the annotation data.

```python
data = client.get_version_data(file_id=42, version_id=100)
```

#### `client.project(project_id)`

Get a `ProjectContext` for scoped operations. Use as a context manager.

| Parameter | Type | Description |
|-----------|------|-------------|
| `project_id` | `int` | The project ID. |

**Returns:** `ProjectContext`

```python
with client.project(5) as project:
    schema = project.schema()
```

#### `client.close()`

Close the HTTP connection. Called automatically when using `with NexusClient(...)`.

---

### `ProjectContext`

Scoped project operations. Obtained via `client.project(project_id)`.

| Property | Type | Description |
|----------|------|-------------|
| `id` | `int` | The project ID. |

#### `project.schema()`

Get the project schema (cached after first call).

**Returns:** `Schema`

#### `project.files()`

List all files as `FileContext` objects.

**Returns:** `list[FileContext]`

#### `project.file(file_id)`

Get a single `FileContext` by ID (no API call).

| Parameter | Type | Description |
|-----------|------|-------------|
| `file_id` | `int` | The ProjectFile ID. |

**Returns:** `FileContext`

#### `project.push_annotations(annotations, annotator_name, metadata=None)`

Batch push annotations (same as `client.push_annotations` but scoped to this project).

**Returns:** `PushSummary`

---

### `FileContext`

Scoped file operations. Obtained via `project.files()` or `project.file(id)`.

| Property | Type | Description |
|----------|------|-------------|
| `id` | `int` | The ProjectFile ID. |
| `label` | `str` | Display filename. |
| `path` | `str` | Storage path. |

#### `file.content()`

Get file text content or PDF URL.

**Returns:** `str`

#### `file.versions(type=None)`

List annotation versions.

| Parameter | Type | Description |
|-----------|------|-------------|
| `type` | `str \| None` | `"ai"`, `"human"`, or `None` (all). |

**Returns:** `list[AnnotationVersion]`

#### `file.get_version_data(version_id, version_type="ai")`

Fetch annotation JSON for a specific version.

**Returns:** `dict`

#### `file.validate(data)`

Validate annotation data client-side against the project schema.

| Parameter | Type | Description |
|-----------|------|-------------|
| `data` | `dict` | Annotation data to validate. |

**Returns:** `list[str]` — errors. Empty if valid.

#### `file.push_annotation(data, annotator_name, metadata=None)`

Push an annotation as an AI version for this file.

| Parameter | Type | Description |
|-----------|------|-------------|
| `data` | `dict` | Annotation data. |
| `annotator_name` | `str` | Your script/tool name. |
| `metadata` | `dict \| None` | Optional extra metadata. |

**Returns:** `AnnotationVersion`

---

### Models

#### `Project`

| Field | Type | Description |
|-------|------|-------------|
| `id` | `int` | Project ID. |
| `name` | `str` | Display name. |
| `description` | `str \| None` | Optional description. |
| `project_type` | `str` | `"json_extraction"`, `"pdf_json_extraction"`, etc. |
| `file_count` | `int` | Number of files. |

#### `File`

| Field | Type | Description |
|-------|------|-------------|
| `id` | `int` | ProjectFile ID. |
| `label` | `str` | Display filename. |
| `path` | `str` | Storage path. |
| `project_id` | `int \| None` | Parent project ID. |

#### `Schema`

| Field | Type | Description |
|-------|------|-------------|
| `id` | `int` | Schema record ID. |
| `name` | `str` | Schema name. |
| `version` | `int` | Current version number. |
| `version_id` | `int` | Current version record ID. |
| `definition` | `dict` | JSON Schema Draft 7 definition. |

#### `AnnotationVersion`

| Field | Type | Description |
|-------|------|-------------|
| `id` | `int` | Version record ID. |
| `type` | `str` | `"ai"` or `"human"`. |
| `version_number` | `int` | Raw number (e.g., `3`). |
| `version_label` | `str` | Display label (e.g., `"a3"` or `"v3"`). |
| `status` | `str` | `"published"`, `"draft"`, or `"archived"`. |
| `created_at` | `str \| None` | ISO 8601 timestamp. |

#### `PushSummary`

| Field | Type | Description |
|-------|------|-------------|
| `total` | `int` | Total annotations attempted. |
| `success` | `int` | Successfully pushed. |
| `failed` | `int` | Failed. |
| `errors` | `list[dict]` | `[{"file_id": int, "error": str}]`. |
| `versions` | `list[dict]` | `[{"id", "file_id", "version_number", "version_label"}]`. |

---

### Exceptions

All exceptions inherit from `NexusError`.

| Exception | HTTP Code | When |
|-----------|-----------|------|
| `NexusError` | any | Base class for all errors. |
| `AuthError` | 401, 403 | Invalid/expired API key, no access. |
| `NotFoundError` | 404 | Project, file, or schema not found. |
| `ValidationError` | 422 | Annotation data fails schema validation. |

All exceptions have `.message` (str) and `.status_code` (int | None) attributes.
`ValidationError` additionally has `.errors` (list[str]).

```python
from nexus_client import NexusError, AuthError

try:
    client.push_annotation(...)
except AuthError:
    print("Bad API key")
except NexusError as e:
    print(f"Error {e.status_code}: {e.message}")
```

---

## Development

```bash
pip install -e ".[dev]"
pytest
```

### Docker (for integration testing)

```bash
cd docker
docker compose build
docker compose run nexus-client pytest
```
