Metadata-Version: 2.4
Name: kuu
Version: 0.2.6
Summary: lightweight async distributed task queue
Keywords: apscheduler,message queue,queue,schedule tasks,scheduler,scheduling,task,task queue
Author: Alexey Pechenin
Author-email: Alexey Pechenin <me@pyrorhythm.dev>
License-Expression: Apache-2.0
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AnyIO
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Distributed Computing
Classifier: Topic :: System :: Networking
Classifier: Typing :: Typed
Requires-Dist: adaptix>=3.0.0b12
Requires-Dist: anyio>=4.10,<5
Requires-Dist: msgspec>=0.21
Requires-Dist: typer>=0.24.2
Requires-Dist: uvloop>=0.22.1
Requires-Dist: watchfiles>=1.1.1
Requires-Dist: jinja2>=3.1.6 ; extra == 'dashboard'
Requires-Dist: starlette>=1.0.0 ; extra == 'dashboard'
Requires-Dist: uvicorn>=0.46.0 ; extra == 'dashboard'
Requires-Dist: websockets>=15.0 ; extra == 'dashboard'
Requires-Dist: dishka>=1.10.1 ; extra == 'di'
Requires-Dist: nats-py>=2.7 ; extra == 'nats'
Requires-Dist: opentelemetry-api>=1.41,<2 ; extra == 'otel'
Requires-Dist: opentelemetry-sdk>=1.41,<2 ; extra == 'otel'
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.41,<2 ; extra == 'otel'
Requires-Dist: opentelemetry-instrumentation-logging>=0.60,<1 ; extra == 'otel'
Requires-Dist: structlog>=24,<27 ; extra == 'otel'
Requires-Dist: asyncpg>=0.31.0 ; extra == 'postgres'
Requires-Dist: prometheus-client>=0.20 ; extra == 'prometheus'
Requires-Dist: redis>=6.0,<8 ; extra == 'redis'
Requires-Dist: structlog>=24,<27 ; extra == 'structlog'
Requires-Python: >=3.12
Project-URL: homepage, https://github.com/pyrorhythm/kuu
Project-URL: docs, https://pyrorhythm.github.io/kuu
Provides-Extra: dashboard
Provides-Extra: di
Provides-Extra: nats
Provides-Extra: otel
Provides-Extra: postgres
Provides-Extra: prometheus
Provides-Extra: redis
Provides-Extra: structlog
Description-Content-Type: text/markdown

<p align="center">
  <img src=".github/logo/svg/kuu-logo-slogan-white.svg" alt="kuu" width="50%"/>
  <br/><br/>
  <a href="https://pypi.org/project/kuu/"><img src="https://img.shields.io/pypi/v/kuu?color=blue" alt="PyPI"/></a>
  <a href="https://pypi.org/project/kuu/"><img src="https://img.shields.io/pypi/pyversions/kuu" alt="Python"/></a>
  <a href="https://github.com/pyrorhythm/kuu/blob/master/LICENSE"><img src="https://img.shields.io/pypi/l/kuu" alt="License"/></a>
  <a href="https://pypi.org/project/kuu/"><img src="https://img.shields.io/pypi/dm/kuu" alt="Downloads"/></a>
</p>

<h1 align="center">kuu</h1>

---

> ### a _native_ distributed task queue for python

```shell
uv add kuu
# extras: dashboard, nats, postgres, prometheus, redis
```

## quick start

```python
# myapp/app.py
from kuu import Kuu
from kuu.brokers.redis import RedisBroker
from kuu.results.redis import RedisResults

app = Kuu(broker=RedisBroker(url=...), results=RedisResults(url=...))


# myapp/tasks.py
from typing import TypedDict
from datetime import timedelta
from .app import app


class ChargeResult(TypedDict):
    ok: bool
    charged: int


@app.task
async def charge(user_id: int, amount_cents: int) -> ChargeResult:
    return {"ok": True, "charged": amount_cents}


@app.sched(every(hours=4, starting=time(hours=1, minutes=30))) # 1:30, 5:30, 9:30...
async def refresh_balance() -> None: ...


# myapp/main.py
from .tasks import charge


async def run() -> None:
    # type checker infers TaskHandle[ChargeResult]
    # args/kwargs of the task remain typed
    handle = await charge.q(user_id=1, amount_cents=500)

    # type checker infers ChargeResult
    result = await handle.result(timeout=30)
```

```sh
# reads ./kuunfig.toml or [tool.kuu] in ./pyproject.toml
# starts control plane with all presets spawned
uv run kuu start

# singular preset with dashboard / remote uplink
uv run kuu start --preset ...
```

## what's inside

- **brokers**: Redis Streams, NATS JetStream, in-memory (for tests)
- **scheduler**: interval jobs (`@app.every`) and composable cron-like schedules (`@app.sched`)
- **middleware**: logging, retry with exponential backoff + jitter, timeout, plus custom hooks
- **events**: pub/sub signals for task lifecycle (`task_enqueued` .. `task_dead`)
- **serialization**: JSON (msgspec), Msgpack, Pickle, with extensible type coercion via `marshal`
- **persistence**: SQLite (zero-config) and PostgreSQL backends for run/log history
- **dashboard**: Starlette+HTMX web UI with live worker/queue stats and task management
- **prometheus**: multiprocess metrics with worker-side emitter and client-side middleware
- **hot reload**: watch filesystem changes, restart worker pool on settled batches

## config

put the block below into `kuunfig.toml`, or under `[tool.kuu]` in your `pyproject.toml`

`[default]` holds base values; each `[presets.<name>]` overrides only the fields you set.
unset fields fall back to `[default]`. a flat config (no `[default]` wrapper) still works

```toml
[default]
queues = []              # consume from; empty = auto-discover from registry
processes = 1            # worker subprocesses to spawn
concurrency = 64         # max concurrent tasks per worker
prefetch = 16            # batch size; defaults to max(1, concurrency // 4)
shutdown_timeout = 30.0  # seconds to wait for in-flight tasks on stop

[default.metrics]
enable = false
host = "0.0.0.0"
port = 9191

[default.dashboard]
enable = false
host = "0.0.0.0"
port = 8181
path = "/dashboard"

scheduler.enable = false    # run scheduler loop in-process; jobs declared via app.every / app.sched

[default.watch]
enable = false              # reload workers on filesystem changes
root = "."                  # path to watch
respect_gitignore = true    # skip files matched by .gitignore
exclude = [".git/**"]       # extra globs to exclude
reload_delay = 0.25
reload_debounce = 0.5

[default.persistence]
enable = true               # store run/log history
dsn = "sqlite:///./kuu.db"  # sqlite (default) or postgres://...;
# also can be provided via KUU_PERSISTENCE_DSN env var
schema = ""                 # postgres schema; empty = default
runs_table = "kuu_runs"
logs_table = "kuu_run_logs"
keep_days = 7               # auto-purge runs older than this
max_runs = 100_000          # hard cap on stored runs
log_level = "INFO"
capture_args = true         # capture task args/kwargs in run detail

[presets.prod]
processes = 8
concurrency = 256

[presets.dev]
processes = 1
concurrency = 16
```

any setting can be overridden from the CLI with `-o dotted.path=value`
values are parsed as JSON when possible (`true`, `42`, `["a","b"]`), otherwise kept as strings

## contribution

well if you insist... (issues / PRs welcome)

#clankersgoaway
