Metadata-Version: 2.5
Name: chanx
Version: 2.12.3
Summary: Typed WebSocket APIs for Python: routing, validation, broadcasting, AsyncAPI docs and typed clients for Django Channels, FastAPI and ASGI.
Project-URL: Documentation, https://chanx.readthedocs.io/
Project-URL: Homepage, https://github.com/huynguyengl99/chanx
Project-URL: Repository, https://github.com/huynguyengl99/chanx
Author-email: Huy Nguyen <danghuy1999@gmail.com>
License: 
        
        BSD License
        
        Copyright (c) 2025, Huy Nguyen
        All rights reserved.
        
        Redistribution and use in source and binary forms, with or without modification,
        are permitted provided that the following conditions are met:
        
        * Redistributions of source code must retain the above copyright notice, this
          list of conditions and the following disclaimer.
        
        * Redistributions in binary form must reproduce the above copyright notice, this
          list of conditions and the following disclaimer in the documentation and/or
          other materials provided with the distribution.
        
        * Neither the name of the copyright holder nor the names of its
          contributors may be used to endorse or promote products derived from this
          software without specific prior written permission.
        
        THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
        ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
        WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
        IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
        INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
        BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
        DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
        OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
        OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
        OF THE POSSIBILITY OF SUCH DAMAGE.
License-File: AUTHORS.rst
License-File: LICENSE
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Python: <4.0,>=3.11
Requires-Dist: pydantic<3,>=2
Provides-Extra: channels
Requires-Dist: channels<5,>=4; extra == 'channels'
Requires-Dist: django>=5; extra == 'channels'
Requires-Dist: djangorestframework<4,>=3; extra == 'channels'
Requires-Dist: pyhumps>=3.8.0; extra == 'channels'
Requires-Dist: structlog; extra == 'channels'
Requires-Dist: typing-extensions; extra == 'channels'
Provides-Extra: cli
Requires-Dist: click<9,>=8.1.0; extra == 'cli'
Requires-Dist: httpx<1,>=0.27.0; extra == 'cli'
Requires-Dist: jinja2<4,>=3.1.0; extra == 'cli'
Requires-Dist: pyyaml<7,>=6.0.0; extra == 'cli'
Requires-Dist: websockets; extra == 'cli'
Provides-Extra: client
Requires-Dist: websockets; extra == 'client'
Provides-Extra: core
Requires-Dist: pyhumps>=3.8.0; extra == 'core'
Requires-Dist: structlog; extra == 'core'
Requires-Dist: typing-extensions; extra == 'core'
Provides-Extra: fast-channels
Requires-Dist: fast-channels>=1.0.2; extra == 'fast-channels'
Requires-Dist: fastapi>=0.118.0; extra == 'fast-channels'
Requires-Dist: pyhumps>=3.8.0; extra == 'fast-channels'
Requires-Dist: structlog; extra == 'fast-channels'
Requires-Dist: typing-extensions; extra == 'fast-channels'
Description-Content-Type: text/markdown

<h1>
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/huynguyengl99/chanx/main/docs/_static/brand/chanx-wordmark-dark.svg">
    <img alt="Chanx" src="https://raw.githubusercontent.com/huynguyengl99/chanx/main/docs/_static/brand/chanx-wordmark.svg" height="64">
  </picture>
</h1>

[![PyPI](https://img.shields.io/pypi/v/chanx)](https://pypi.org/project/chanx/)
[![Python](https://img.shields.io/pypi/pyversions/chanx)](https://pypi.org/project/chanx/)
[![Tests](https://github.com/huynguyengl99/chanx/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/huynguyengl99/chanx/actions/workflows/test.yml)
[![Coverage](https://codecov.io/gh/huynguyengl99/chanx/branch/main/graph/badge.svg?token=X8R3BDPTY6)](https://codecov.io/gh/huynguyengl99/chanx)

**Typed WebSocket APIs for Python.** Write one handler per message; Chanx routes,
validates, broadcasts and documents it, and generates typed clients from the result.
Runs on Django Channels, FastAPI, or any ASGI framework.

**[Documentation](https://chanx.readthedocs.io/)** ·
[Django quick start](https://chanx.readthedocs.io/en/latest/quick-start.html?framework=django) ·
[FastAPI quick start](https://chanx.readthedocs.io/en/latest/quick-start.html?framework=fastapi) ·
[TypeScript clients](https://huynguyengl99.github.io/chanx-js/) ·
[Ready-made kits](https://huynguyengl99.github.io/chanx-kit/)

![A typed Python handler, the AsyncAPI docs Chanx generates from it, generating a TypeScript client, and that client catching a typo](https://raw.githubusercontent.com/huynguyengl99/chanx/main/docs/_static/chanx-demo.gif)

## Why Chanx

A raw WebSocket endpoint is one `receive` method that grows forever:

```python
async def receive(self, text_data):
    data = json.loads(text_data)
    if data.get("action") == "chat":
        if "text" not in data.get("payload", {}):
            return await self.send(json.dumps({"error": "missing text"}))
        ...  # find the room, loop over connections, send to each
    elif data.get("action") == "ping":
        ...
    # no types, no docs, and the frontend guesses the message shapes
```

With Chanx, each message is a Pydantic model and each handler is a typed method:

```python
@ws_handler(output_type=ChatNotification)
async def handle_chat(self, message: ChatMessage) -> None:
    await self.broadcast_message(ChatNotification(payload=message.payload))
```

From that, Chanx gives you:

- **Routing by type.** Incoming JSON is dispatched on its `action` field to the handler
  whose argument type matches. No `if`/`elif` chains.
- **Validation.** Payloads are checked by Pydantic before your code runs; bad ones get a
  structured error reply.
- **Groups and events.** Broadcast to groups, and push messages from HTTP views, tasks or
  workers through the channel layer (Redis or in-memory).
- **AsyncAPI 3.0 docs.** A spec and an interactive docs page, generated from your
  handlers, so the contract the frontend reads is the code that runs.
- **Typed clients.** Generate a Python client, or a TypeScript client with React, Vue,
  Svelte and Solid bindings via [chanx-js](https://huynguyengl99.github.io/chanx-js/).
- **Testing and auth.** Test communicators for both frameworks, and authenticators that
  reuse your DRF permission classes on Django.
- **Static typing throughout.** Checked with mypy and pyright in strict mode, so handler
  and message mistakes show up in your editor.

## Install

```bash
pip install "chanx[channels]"        # Django Channels
pip install "chanx[fast_channels]"   # FastAPI, Starlette and other ASGI frameworks
```

Python 3.11 to 3.13. Optional extras: `chanx[cli]` for the client generator,
`chanx[client]` to run generated Python clients.

## Quick start

A room chat on FastAPI in one file. Save it as `main.py`:

```python
from typing import Literal

from fastapi import FastAPI, Request
from fast_channels.layers import InMemoryChannelLayer, register_channel_layer
from pydantic import BaseModel

from chanx.core.decorators import channel, ws_handler
from chanx.fast_channels import asyncapi_docs, asyncapi_spec_json
from chanx.fast_channels.websocket import AsyncJsonWebsocketConsumer
from chanx.messages.base import BaseMessage


class ChatPayload(BaseModel):
    text: str


class ChatMessage(BaseMessage):  # client -> server
    action: Literal["chat"] = "chat"
    payload: ChatPayload


class ChatNotification(BaseMessage):  # server -> everyone in the group
    action: Literal["chat_notification"] = "chat_notification"
    payload: ChatPayload


@channel(name="chat", description="Room chat")
class ChatConsumer(AsyncJsonWebsocketConsumer):
    channel_layer_alias = "default"
    groups = ["lobby"]  # joined on connect

    @ws_handler(output_type=ChatNotification)
    async def handle_chat(self, message: ChatMessage) -> None:
        await self.broadcast_message(ChatNotification(payload=message.payload))


register_channel_layer("default", InMemoryChannelLayer())  # use Redis in production

app = FastAPI()
app.add_websocket_route("/ws/chat", ChatConsumer.as_asgi())


@app.get("/asyncapi")
async def docs(request: Request):
    return await asyncapi_docs(request=request, app=app)


@app.get("/asyncapi.json")
async def spec(request: Request):
    return await asyncapi_spec_json(request=request, app=app)
```

Run it with `uvicorn main:app`, open two connections to `ws://localhost:8000/ws/chat`,
and send from one:

```json
{"action": "chat", "payload": {"text": "hi"}}
```

Both receive:

```json
{"action": "chat_notification", "payload": {"text": "hi"}}
```

A malformed message (`{"action": "chat", "payload": {"txt": "hi"}}`) never reaches the
handler; the sender gets a validation error instead. The interactive docs are at
<http://localhost:8000/asyncapi>.

**On Django**, the consumer is the same class imported from
`chanx.channels.websocket`, routed with Channels, with docs from
`path("asyncapi/", include("chanx.channels.urls"))`. See the
[Django quick start](https://chanx.readthedocs.io/en/latest/quick-start.html?framework=django).

## Typed clients

Clients are generated from the AsyncAPI spec, so they always match the server.

TypeScript, with [chanx-js](https://huynguyengl99.github.io/chanx-js/):

```bash
npm install @chanx-js/client
npx @chanx-js/codegen http://localhost:8000/asyncapi.json -o src/generated
```

```tsx
import { useChannel } from '@chanx-js/client/react';
import { chat } from './generated';

function Chat() {
  const { send } = useChannel(chat);
  // `send` only accepts messages the server declares; a typo is a compile error.
  return <button onClick={() => send({ action: 'chat', payload: { text: 'hi' } })}>Say hi</button>;
}
```

Python, with the built-in generator:

```bash
pip install "chanx[cli]"
chanx generate-client --schema http://localhost:8000/asyncapi.json --output ./chat_client
```

## Ecosystem

- **[chanx-js](https://huynguyengl99.github.io/chanx-js/)**: TypeScript clients for the
  browser and Node, and a TypeScript server that speaks the same protocol and can share a
  Redis layer with Python.
- **[chanx-kit](https://huynguyengl99.github.io/chanx-kit/)**: copy-in components built on
  Chanx (notifications, chat with history, presence, agent streaming, voice), in the
  spirit of shadcn/ui.

## Learn more

- [Introduction](https://chanx.readthedocs.io/en/latest/introduction.html) and
  [comparison with Channels and FastAPI](https://chanx.readthedocs.io/en/latest/comparison.html)
- Tutorials: [Django](https://chanx.readthedocs.io/en/latest/tutorial-django/prerequisites.html),
  [FastAPI](https://chanx.readthedocs.io/en/latest/tutorial-fastapi/prerequisites.html)
- [Configuration](https://chanx.readthedocs.io/en/latest/configuration.html) and
  [API reference](https://chanx.readthedocs.io/en/latest/reference/core.html)
- [Changelog](https://github.com/huynguyengl99/chanx/blob/main/CHANGELOG.md)

## Contributing

Issues and pull requests are welcome. See [CONTRIBUTING.md](https://github.com/huynguyengl99/chanx/blob/main/CONTRIBUTING.md).

## License

BSD. See [LICENSE](https://github.com/huynguyengl99/chanx/blob/main/LICENSE).
