Package: ws-sync
ws-sync: WebSocket SyncThis library defines a very simple WebSocket and JSON & JSON Patch based protocol for keeping the python backend and the browser frontend in sync. There's a corresponding react library that implements the frontend side of the protocol.QuickstartSyncing simple statesBackendLet's say you have the following object:classNotes:def__init__(self):# my attributes, as usualself.title="My Notes"self.notes=[]@propertydeftotal_length(self):returnsum(len(note)fornoteinself.notes)defrename(self,new_title):self.title=new_titledefadd(self,note):self.notes.append(note)To sync it to the frontend, it is as simple as:+from ws_sync import Syncclass Notes:def __init__(self):# my attributes, as usualself.title = "My Notes"self.notes = []+       # create the sync object and define the key of the object+       self.sync = Sync("NOTES", self)@propertydef total_length(self):return sum(len(note) for note in self.notes)+   async def rename(self, new_title):self.title = new_title+       await self.sync() # make sure the frontend knows about the change+   async def add(self, note):self.notes.append(note)+       await self.sync() # make sure the frontend knows about the changeTheSync("Notes", self)call automatically detects the attributes to sync inself, which are all attributes or@propertiesthat do not start with an underscore. You can also specify the attributes to sync manually:self.sync=Sync("NOTES",self,title=...,notes=...,total_length="totalLength",)The keyword argument is the local name of the attribute, the value is the name of the attribute in the frontend. If the value is..., the local and frontend name are the same. This is useful if you want to rename an attribute in the frontend without changing the name in the backend (e.g. snake_case to camelCase).FrontendOn the frontend, you can use theuseSynchook to sync the state to the backend:constNotes=()=>{constnotes=useSync("NOTES",{title:"",notes:[],})return(<div><h1>{notes.title}</h1><ul>{notes.notes.map(note=><li>{note}</li>)}</ul></div>)}The second parameter ofuseSyncis the initial state.The returnednotesobject not only contains the state, but also the setters and syncers:constNotes=()=>{constnotes=useSync("NOTES",{title:"",notes:[],})return(<div><inputvalue={notes.title}onChange={e=>notes.syncTitle(e.target.value)}/><ul>{notes.notes.map(note=><li>{note}</li>)}</ul></div>)}ActionsActions are a way to call methods on the remote (action handlers), usually frontend -> backend.TODOTasksTasks are like actions, but for long-running operations and can be cancelled.TODOServerOf course, to actually connect the frontend and backend, you need a server. Here's an example using FastAPI:fromfastapiimportFastAPIfromws_syncimportSessionfrom.notesimportNotes# create a new session, in this case only 1 global sessionwithSession()assession:my_notes=Notes()my_session=session# FastAPI serverapp=FastAPI()@app.websocket("/ws")asyncdefwebsocket_endpoint(ws:WebSocket):awaitws.accept()try:awaitmy_session.new_connection(ws)awaitmy_session.handle_connection()finally:my_session.ws=Noneawaitws.close()Concepts and ImplementationHigh-LevelSession: A session is a connection between a frontend and a backend, and itpersists across WebSocket reconnects. This means that any interruption of the connection will not affect the backend state in any way, and all theself.sync()calls will be ignored. On reconnect, the frontend will automatically restore the latest state from the backend.Sync: A sync operation will generate a new snapshot of the object state, calculate the difference to the previous state snapshot, and send a JSON Patch object to the frontend. The frontend will then apply the patch to its local state. This is done automatically on everyself.sync()call. This way, only the changes are sent over the network, and the frontend state is always in sync with the backend state.Low-LevelEvents: The primitive of the protocol are events. An event is a JSON object with a simple{"type": "event_type", "data": any}format. All the operations done by theSyncobject uses different events, including actions and tasks.