Metadata-Version: 2.3
Name: nyxa-queue
Version: 0.1.0
Summary: Jobs and queue helpers for Nyxa — sync or Redis/ARQ via QUEUE_CONNECTION
Keywords: fastapi,jobs,queue,nyxa
Author: Al-Amin Islam Nerob
Author-email: Al-Amin Islam Nerob <alamin@aincoder.com>
License: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Dist: nyxa>=0.1.0
Requires-Dist: arq>=0.26 ; extra == 'redis'
Requires-Python: >=3.11
Project-URL: Homepage, https://nyxadev.com
Project-URL: Repository, https://github.com/NyxaDev/nyxa
Provides-Extra: redis
Description-Content-Type: text/markdown

# nyxa-queue — Jobs for Nyxa

Job dispatch for apps built with [nyxa](https://pypi.org/project/nyxa/).
Default is **sync** (in-process). Optional **Redis/ARQ** via `QUEUE_CONNECTION=redis`.

## At a glance

| What you need | Nyxa Queue |
| --- | --- |
| Dispatch a job | `nyxa_queue.dispatch(MyJob(...))` |
| Define a job | `Job` subclass with `async def handle` |
| Run later | `dispatch(MyJob(...), delay=60)` (redis only) |
| Skip duplicates | `unique=True` / `Job.unique` / `unique_id=` (redis / ARQ `_job_id`) |
| Retry on failure | `QUEUE_MAX_TRIES` + `raise Retry(defer=…)` from `nyxa_queue` |
| Run a worker | `nyxa queue work` (ARQ) or `nyxa queue work <job>` (one-shot) |
| Scaffold a job | `nyxa make:job ReindexSearch` |

## Install

```bash
uv add nyxa-queue
# Redis/ARQ worker:
uv add 'nyxa-queue[redis]'
```

## Quick start (sync)

```python
from nyxa_queue import Job, dispatch, dispatch_async

class ReindexSearch(Job):
    def __init__(self, user_id: str) -> None:
        self.user_id = user_id

    async def handle(self) -> None:
        print(f"reindex {self.user_id}")

dispatch(ReindexSearch("42"))
await dispatch_async(ReindexSearch("42"))
```

Job instance attributes must be **JSON-serializable** when using Redis.

## Redis / ARQ

```bash
uv add 'nyxa-queue[redis]'
```

```bash
QUEUE_CONNECTION=redis
QUEUE_REDIS_URL=redis://localhost:6379/0   # or REDIS_URL=
# QUEUE_MAX_TRIES=5                        # ARQ worker retries (default 5)
```

- `dispatch` / `dispatch_async` **enqueue** (do not run inline); return ARQ job id or `None`
- Worker: `uv run nyxa queue work` (long-running); `uv run nyxa queue work --burst`
- One-shot debug (any connection): `uv run nyxa queue work reindex_search`

### Delay (redis only)

```python
await dispatch_async(ReindexSearch("42"), delay=60)           # seconds
await dispatch_async(ReindexSearch("42"), delay=timedelta(minutes=5))
await dispatch_async(ReindexSearch("42"), delay_until=when)   # datetime
```

Sync ignores `delay` / `delay_until` and runs immediately. Do not pass both.

### Uniqueness (redis only)

```python
await dispatch_async(ReindexSearch("42"), unique=True)
await dispatch_async(ReindexSearch("42"), unique_id="reindex:42")

class ReindexSearch(Job):
    unique = True
    def unique_id(self) -> str | None:
        return f"reindex:{self.user_id}"
```

When a duplicate is already queued/in-flight, ARQ skips enqueue and `dispatch_async` returns `None`.

### Retries

Worker uses `QUEUE_MAX_TRIES` (default `5`). Inside `handle`, raise ARQ’s `Retry`:

```python
from nyxa_queue import Job, Retry  # Retry requires nyxa-queue[redis]

class Flaky(Job):
    async def handle(self) -> None:
        raise Retry(defer=30)  # retry after 30s
```

## CLI

```bash
# sync / debug — run a named job once
uv run nyxa queue work reindex_search
uv run nyxa queue work playground.jobs.reindex_search:ReindexSearch

# redis — long-running ARQ worker
QUEUE_CONNECTION=redis uv run nyxa queue work
QUEUE_CONNECTION=redis uv run nyxa queue work --burst
```
