Metadata-Version: 2.5
Name: taskwire
Version: 0.1.0
Summary: Progress reporting and awaitable dialogs for long-running server-side operations.
Project-URL: Homepage, https://github.com/velis74/taskwire
Project-URL: Repository, https://github.com/velis74/taskwire
Project-URL: Issues, https://github.com/velis74/taskwire/issues
Project-URL: Documentation, https://docs.velis.si/taskwire/
Author-email: Jure Erznožnik <jure.erznoznik@gmail.com>
License: MIT
License-File: LICENSE
Keywords: celery,dialogs,dynamicforms,fastapi,progress,websocket
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT 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 :: Internet :: WWW/HTTP
Classifier: Typing :: Typed
Requires-Python: >=3.10
Provides-Extra: celery
Requires-Dist: celery>=5.3; extra == 'celery'
Requires-Dist: nest-asyncio>=1.6; extra == 'celery'
Provides-Extra: demo
Requires-Dist: uvicorn>=0.27; extra == 'demo'
Provides-Extra: dev
Requires-Dist: coverage; extra == 'dev'
Requires-Dist: httpx; extra == 'dev'
Requires-Dist: hypothesis; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-asyncio; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.100; extra == 'fastapi'
Provides-Extra: muxws
Requires-Dist: dynamicforms-fastapi-viewsets[muxws]>=0.5.4; extra == 'muxws'
Requires-Dist: muxws>=0.3.1; extra == 'muxws'
Provides-Extra: redis
Requires-Dist: redis>=5.0; extra == 'redis'
Provides-Extra: viewsets
Requires-Dist: dynamicforms-fastapi-viewsets>=0.5.4; extra == 'viewsets'
Description-Content-Type: text/markdown

# <img src="taskwire-icon.svg" alt="" style="width:2.5em; height: 2.5em; vertical-align: middle"> taskwire

Progress reporting and awaitable dialogs for long-running operations, in Python and TypeScript.

A long-running job — an import, a report, a batch — needs to tell whoever started it what it is
doing while it is doing it, and sometimes needs to ask them something before it can continue.
taskwire is that conversation, and the four things it carries:

- **progress**, with nested subtasks whose percentages compose correctly rather than overwriting
  each other, and commits coalesced so a tight loop cannot flood anything;
- **awaitable dialogs** — the worker asks a question mid-job and blocks until the answer arrives,
  with the first answer winning across every tab that is looking;
- **cancellation**, cooperative and sticky, which raises in the worker at its next progress call;
- **collectable results** — the job parks a file or a value for later collection and *exits*,
  holding no worker while the record waits.

## The one idea worth knowing

**The store is the truth; a push is only an accelerator.** Every state change is written before
anything is sent anywhere, so a dropped, coalesced or suppressed push never changes what the next
read returns. There is no event log, no sequence numbers and no replay: a client is entitled to
current state, and asking for it is always enough.

## One protocol, three implementations

The layering is the shape of the source tree:

```
             protocol            documents, envelopes, the six kinds, the state
                 │               machines, validation.  Knows of no transport.
     ┌───────────┼───────────┐
   local        REST         WS         three independent implementations of it
     │           │           │
  (none)   fastapi / asgi   muxws       one adapter each, to the world outside
```

The **protocol** is specified once and depends on nothing. Each **implementation** carries it over
one medium and is written against the protocol layer alone, never against another implementation.
Each **adapter** is the thin piece that binds an implementation to a particular framework, and is
the only place that framework is imported.

| Implementation | Carries the protocol over | Adapter | Needs |
|---|---|---|---|
| **local** | the process itself — no wire, no socket, no serialization | none | nothing |
| **REST** | request/response polling; the baseline everywhere | `contrib.viewsets`, `contrib.fastapi`, `contrib.asgi` | nothing in core |
| **WS** | one push per envelope down, the six calls up, lowest latency | `contrib.muxws` | [muxws](https://docs.velis.si/muxws/) |

Swapping one for another changes latency and nothing else — no feature, no state, no document.
Both languages ship the protocol and the operating half, so a browser-only application runs
operations rather than only watching them; the adapters, the Redis store and the Celery entry are
Python's, and the polling client and the register are TypeScript's.

## Install

```bash
pip install taskwire                 # core: no runtime dependencies at all
pip install "taskwire[redis]"        # cross-process store and backplane
pip install "taskwire[fastapi]"      # the REST adapter
pip install "taskwire[viewsets]"     # the REST API as a fastapi-viewsets viewset
pip install "taskwire[celery]"       # the worker entry point
pip install "taskwire[muxws]"        # the WebSocket transport
```

```bash
npm install taskwire
```

## The demo

```bash
pip install -e ".[fastapi,viewsets,demo]" && npm install
python demo.py                       # then open http://127.0.0.1:5174
```

A night batch over one book: four back-office jobs that between them exercise progress, nested
progress, awaitable dialogs, cancellation and collectable results. See [`demo/README.md`](demo/README.md).

## Status

Pre-1.0 and under active construction. The wire format is versioned independently of the package
(`Envelope.v`), so package semver says nothing about it.

## Links

- [Documentation](https://docs.velis.si/taskwire/)
- [Repository](https://github.com/velis74/taskwire)

## License

MIT. Copyright (c) 2026 Jure Erznožnik.
