remotestate is the Python runtime for RemoteState apps. It owns the state store, service methods, and server that expose Python state to React.
If you want the high-level product overview, start with the repository root README: RemoteState
pip install remotestateOr with pixi:
pixi add remotestate- Python
>= 3.12 - from the package directory:
cd remotestate-py - install dependencies with
pixi install - format with
pixi run format - run checks with
pixi run checks - run tests with
pixi run tests - build a wheel with
pixi run build
import remotestate as rs
class CounterService(rs.Service):
def __init__(self) -> None:
super().__init__(rs.Store({"count": 0, "user": {"name": "forman"}}))
@rs.action
async def increment(self) -> None:
self.store.set("count", self.store.get("count") + 1)
@rs.query
async def compute(self, x: float) -> float:
self.notify(name="Computing", detail="reading current count", progress=50)
return x * self.store.get("count")
rs.serve(CounterService(), ui_dist="my-ui/dist")The public Python API is exported from remotestate:
StoreandStoreAtServiceandServeResultactionandqueryservepath
Store(initial, *, default_factory=None) holds the Python-side application state.
-
the root state may be any JSON-serializable value, including a mapping, list, scalar, Pydantic model, or dataclass
-
nested dicts, lists, Pydantic models, and dataclasses are all supported
-
default_factoryreceives the missing prefix as ars.path.Pathtuple -
statereturns the current root state value -
get(path=(), require=False)reads a value from a path such as ``,user.name, `[0].label`, or `items[0].label`; omit `path` to read the root state value -
set(path, value)writes a value and notifies subscribers -
store[path]andstore[path] = valueare notebook-friendly aliases forget()andset() -
store.atreturns a notebook-friendly accessor of typeStoreAtfor nested state variables using item or attribute syntax -
store.at.some.path = valuefor example executes nestedset()calls; use item syntax for arrays or keys that are not valid identifiers -
subscribe(callback)receives batched path-to-value updates after changes flush -
default_factorycan materialize missing parents while setting nested values
import remotestate as rs
class User:
def __init__(self, name: str = "", city: str = "") -> None:
self.name = name
self.city = city
def defaults(path: rs.path.Path):
if path == ("user",):
return User()
if path == ("items",):
return []
return {}
store = rs.Store({}, default_factory=defaults)
store.set("user.city", "Hamburg")
store.set("items[0].label", "foo")
store["items", 0, "label"] = "bar"
store.at.user.city = "Berlin"
store.at.items[0].label = "baz"
assert store.get("user.city") == "Berlin"
assert store.get() is store.state
assert store["items"] == [{"label": "baz"}]
assert store[()] is store.stateget() never calls the default factory. Reads stay side-effect free, and missing
values return None unless require=True is passed.
Use @action for state-changing service methods and @query for read-only methods.
@actionbatchesstore.set()calls and flushes them as oneaction_resultmessage.@queryis read-only; mutating the store inside a query raisesPermissionError- sync and async methods are both supported
class Counter(rs.Service):
def __init__(self) -> None:
super().__init__(rs.Store({"count": 0}))
@rs.action
def increment(self) -> None:
self.store.set("count", self.store.get("count") + 1)
@rs.query
async def multiply(self, x: float) -> float:
self.notify(name="Working", progress=25)
return x * self.store.get("count")Service exposes the store and progress helper used by actions and queries:
notify(name=None, detail=None, progress=None)emitsupdate_taskprogress messages for tracked calls
The reserved service method name is notify. Store reads and writes use
service.store.get(...) and service.store.set(...); get and set remain
available for custom actions or queries.
Service._init_app(app) can be overridden to customize the FastAPI app when serve()
creates one.
serve(service, *, ui_dist, mounts, app, display, width, height, host, port, **uvicorn_settings)
starts the RemoteState server and connects it to a frontend bundle.
serviceis aServiceinstanceui_distcan be a local React build directory or an HTTP(S) URLmountsadds additional static pathsapplets you supply your own FastAPI appdisplaycontrols how the UI is shown:"auto","browser","notebook","none", or a callbackhostandportconfigure the backend server
By default, RemoteState chooses a free port, opens a browser outside notebooks, and renders inline inside notebooks. Re-running the same notebook cell restarts the server automatically.
serve() returns a ServeResult with the resolved URLs and server handles:
result = rs.serve(CounterService(), ui_dist="my-ui/dist", display="none")
print("Server URL: ", result.server_url)
print("WebSocket URL: ", result.ws_url)
print("UI Base URL: ", result.ui_base_url)remotestate.path exposes the parsed path types used by Store.default_factory and other
advanced integrations. Parsed paths are tuples of string property names and integer array
indices, matching the TypeScript package's string | number path segments:
PathPathSegmentPathInputPathSegmentInput
RemoteState paths use a simplified JSONPath
subset without the "$." prefix:
- the empty string addresses the root state value
- the first segment may be an identifier, bracketed integer index, or bracketed JSON string key
- later segments may be dotted identifiers, bracketed integer indices, or bracketed JSON string keys
- bracketed string keys may use either single or double quotes; canonical output uses double quotes
- the whole string must match the grammar; prefix parsing is not allowed
| Example | Valid? | Notes |
|---|---|---|
| empty string | yes | root state value |
user |
yes | root property shorthand |
[0].label |
yes | array root plus child property |
items[0].label |
yes | dotted identifier plus integer index |
["display name"].value |
yes | bracketed string key at the root |
user["display name"] |
yes | bracketed string key |
$.user |
no | "$." prefix is not part of the syntax |
items[01] |
no | indices are canonical integers without leading zeroes |
Use normalize_path() when accepting raw path inputs, and use parse_path() and
format_path() when you need to inspect, validate, or construct string paths.