Python reactive state management framework with server/client incremental sync
Project description
Introduction
ReactPie makes server-side data mutations behave like slicing a pie — each attribute change or container operation produces a precise incremental delta, serialized into a compact binary protocol and pushed over WebSocket in real time. Clients automatically reconstruct a fully consistent reactive mirror of the game state.
Zero external dependencies. Pure Python standard library.
Features
- Field-level deltas — generated at mutation time, no post-hoc diffing
- Reactive containers —
RList/RDict/RSetwrap native containers, mutations sync automatically - Binary protocol — custom TAG + zigzag + LEB128 encoding, more compact than JSON
- Lifecycle management — reference-counted connect / disconnect / die
- Declarative callbacks —
watch()for field changes,on_create/on_deletefor entity lifecycle
Installation
pip install reactpie
Development mode:
git clone https://github.com/Darksky/ai-game-arena
cd ai-game-arena/python-package/reactpie
pip install -e ".[dev]"
Quick Start
import asyncio
from reactpie import ReactServer, ReactClient, RClass, reactive, watch
# ── Define a data model ──
@router
class User(RClass):
name = reactive(str, default="")
hp = reactive(int, default=100)
def __rinit__(self, name: str = ""):
self.name = name
# ── Server ──
server = ReactServer()
server.include_type(User)
server.append(router)
client = ReactClient()
client.include_type(User)
# ── Client-side listeners ──
@client.on_create(User)
def on_create(instance: User):
print(f"Player created: {instance.name}")
@watch(instance, "hp")
def on_hp_change(inst, delta):
print(f"{inst.name} HP: {inst.hp}")
# ── Run ──
async def main():
async def runner():
async for packet in server.serve():
client.push(packet)
task = asyncio.create_task(runner())
user = User("Alice")
server.collect(user)
user.hp -= 20 # client automatically receives the HP delta
await asyncio.sleep(1)
task.cancel()
await server.wait_stop()
asyncio.run(main())
Modules
| Module | Responsibility |
|---|---|
reactpie.core |
Reactive base classes, serialization protocol, watch(), context & routing |
reactpie.server |
ReactServer — instant/batch push modes, serve() async generator |
reactpie.client |
ReactClient — deserialization, state mirroring, lifecycle callbacks |
API Overview
RClass — Reactive Data Model
from reactpie import RClass, reactive, reactive_self
class Player(RClass):
hp = reactive(int, default=100)
name = reactive(str, default="")
inventory = reactive(list, default=[]) # auto-wrapped as RList
target = reactive_self() # self-referencing field
def __rinit__(self, name: str = ""): # replaces __init__
self.name = name
def __post_load__(self): # called after client-side reconstruction
...
watch — Change Listeners
from reactpie import watch, DeltaType
watch(obj, callback=cb) # listen to all deltas
watch(obj, callback=cb, catch=DeltaType.UPDATE) # filter by delta type
watch(obj, callback=cb, field="hp") # filter by field name
watch(obj, callback=cb, depth=0) # filter by depth
@watch(obj, field="x") # decorator form
def on_x_change(instance, delta):
...
ReactServer / ReactClient
# Server
server = ReactServer()
server.include_type(MyModel)
server.collect(root_instance) # mark as root, triggers client on_create
async for packet in server.serve(): # yields MultiDelta bytes
await websocket.send(packet)
# Client
client = ReactClient()
client.include_type(MyModel)
@client.on_create(MyModel) # fires when a ROOT delta arrives
def handle(instance): ...
@client.on_delete(MyModel) # fires when refcount drops to zero
def handle(instance): ...
client.push(raw_bytes) # deserialize and apply
Reactive Containers
from reactpie import RList, RDict, RSet
party = RList()
party.append(alice) # → reactive_connect, live count +1
party.remove(alice) # → reactive_disconnect, live count -1
party.clear() # → disconnect all elements
# RDict, RSet work the same way
Binary Protocol
MultiDelta packet format:
┌──────────┬──────────┬───────────┬─────────────────────┐
│ Magic (2)│ Ver (1) │ Count (2) │ Delta × Count │
│ "RD" │ 0x01 │ u16 LE │ │
└──────────┴──────────┴───────────┴─────────────────────┘
| DeltaType | Description |
|---|---|
UPDATE |
Field assignment |
ROOT / CREATE |
Instance creation (ROOT triggers on_create, CREATE registers only) |
APPEND / EXTEND / INSERT |
List append operations |
REMOVE / POP / CLEAR |
Container removal |
SET |
Index/key assignment |
Examples
| Example | Description |
|---|---|
01_simple_e2e.py |
Single-file end-to-end via asyncio.Queue |
game_schema.py |
Shared data models (Player, Projectile, GameState) |
game_server.py |
WebSocket game server |
game_client.py |
WebSocket game client |
run_game.py |
Single-process launcher |
python examples/01_simple_e2e.py # single-file demo
python examples/run_game.py # multi-file WebSocket demo
Testing
python -m pytest tests/ -v # 242 tests
Project Structure
reactpie/
├── reactpie/
│ ├── __init__.py # public API exports
│ ├── core.py # framework core
│ ├── server.py # ReactServer
│ └── client.py # ReactClient
├── examples/ # example code
├── tests/ # 242 unit tests
├── docs/ # logo & docs
├── pyproject.toml
└── README.md
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file reactpie-0.1.0.tar.gz.
File metadata
- Download URL: reactpie-0.1.0.tar.gz
- Upload date:
- Size: 41.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
00d8e97429c6a2a08a78039ee6c2975e47d5914034376e4faa18332e939cbe70
|
|
| MD5 |
abec4ba8c5d4d0b00381aee50a16ba2c
|
|
| BLAKE2b-256 |
274b9a62a9346b9e96145b5e1f1720e92b980a919cc5f3db3de7b0e1fc0d8c37
|
File details
Details for the file reactpie-0.1.0-py3-none-any.whl.
File metadata
- Download URL: reactpie-0.1.0-py3-none-any.whl
- Upload date:
- Size: 27.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1176e52926e8651460b2dab99a9edec6bf4c78eb80830b74f779e772b4c692b9
|
|
| MD5 |
ba1779418164ab4e99635a547b9a23a1
|
|
| BLAKE2b-256 |
31982cdaafd8128f6a53163d2ded75f378ada47d7ebee4e70248e0fc722df6d3
|