[{"data":1,"prerenderedAt":2153},["ShallowReactive",2],{"docs:/docs/sdks/python":3},{"id":4,"title":5,"body":6,"description":2146,"extension":2147,"meta":2148,"navigation":64,"path":2149,"seo":2150,"stem":2151,"__hash__":2152},"docs/docs/sdks/python.md","Python SDK",{"type":7,"value":8,"toc":2127},"minimark",[9,13,17,22,104,108,309,354,358,438,449,453,456,578,582,592,709,713,795,811,815,822,947,972,976,1064,1072,1076,1094,1153,1157,1160,1299,1309,1313,1340,1529,1533,1588,1601,1605,1608,1777,1781,1788,1880,1894,1898,2023,2055,2059,2075,2079,2123],[10,11,5],"h1",{"id":12},"python-sdk",[14,15,16],"p",{},"The official NoLag SDK for Python. Full async/await support with type hints\nfor Python 3.10+.",[18,19,21],"h2",{"id":20},"installation","Installation",[23,24,25],"code-tabs",{},[26,27,33],"pre",{"className":28,"code":29,"filename":30,"language":31,"meta":32,"style":32},"language-bash shiki shiki-themes github-light github-dark","# pip\npip install nolag\n\n# poetry\npoetry add nolag\n\n# uv\nuv add nolag\n","Terminal","bash","",[34,35,36,45,59,66,72,83,88,94],"code",{"__ignoreMap":32},[37,38,41],"span",{"class":39,"line":40},"line",1,[37,42,44],{"class":43},"sJ8bj","# pip\n",[37,46,48,52,56],{"class":39,"line":47},2,[37,49,51],{"class":50},"sScJk","pip",[37,53,55],{"class":54},"sZZnC"," install",[37,57,58],{"class":54}," nolag\n",[37,60,62],{"class":39,"line":61},3,[37,63,65],{"emptyLinePlaceholder":64},true,"\n",[37,67,69],{"class":39,"line":68},4,[37,70,71],{"class":43},"# poetry\n",[37,73,75,78,81],{"class":39,"line":74},5,[37,76,77],{"class":50},"poetry",[37,79,80],{"class":54}," add",[37,82,58],{"class":54},[37,84,86],{"class":39,"line":85},6,[37,87,65],{"emptyLinePlaceholder":64},[37,89,91],{"class":39,"line":90},7,[37,92,93],{"class":43},"# uv\n",[37,95,97,100,102],{"class":39,"line":96},8,[37,98,99],{"class":50},"uv",[37,101,80],{"class":54},[37,103,58],{"class":54},[18,105,107],{"id":106},"quick-start","Quick Start",[23,109,110],{},[26,111,116],{"className":112,"code":113,"filename":114,"language":115,"meta":32,"style":32},"language-python shiki shiki-themes github-light github-dark","import asyncio\nfrom nolag import NoLag\n\n# The slug returned when you created the app (slugs carry a random suffix)\nAPP_SLUG = \"chat-a3f9\"\n\nasync def main():\n    # Create client with your actor token\n    client = NoLag(\"your-actor-token\")\n\n    # Connect to NoLag\n    await client.connect()\n\n    # Scope to an app and a room. The room must already exist.\n    room = client.set_app(APP_SLUG).set_room(\"general\")\n\n    # Subscribe to a topic in the room\n    await room.subscribe(\"messages\")\n\n    # Listen for messages on that topic\n    def on_message(data, meta):\n        print(f\"Received: {data}\")\n\n    room.on(\"messages\", on_message)\n\n    # Publish a message\n    await room.emit(\"messages\", {\"hello\": \"world\"})\n\n    # Keep running\n    await asyncio.sleep(60)\n\n    # Disconnect when done (synchronous)\n    client.disconnect()\n\nasyncio.run(main())\n","Python","python",[34,117,118,123,128,132,137,142,146,151,156,162,167,173,179,184,190,196,201,207,213,218,224,230,236,241,247,252,258,264,269,275,281,286,292,298,303],{"__ignoreMap":32},[37,119,120],{"class":39,"line":40},[37,121,122],{},"import asyncio\n",[37,124,125],{"class":39,"line":47},[37,126,127],{},"from nolag import NoLag\n",[37,129,130],{"class":39,"line":61},[37,131,65],{"emptyLinePlaceholder":64},[37,133,134],{"class":39,"line":68},[37,135,136],{},"# The slug returned when you created the app (slugs carry a random suffix)\n",[37,138,139],{"class":39,"line":74},[37,140,141],{},"APP_SLUG = \"chat-a3f9\"\n",[37,143,144],{"class":39,"line":85},[37,145,65],{"emptyLinePlaceholder":64},[37,147,148],{"class":39,"line":90},[37,149,150],{},"async def main():\n",[37,152,153],{"class":39,"line":96},[37,154,155],{},"    # Create client with your actor token\n",[37,157,159],{"class":39,"line":158},9,[37,160,161],{},"    client = NoLag(\"your-actor-token\")\n",[37,163,165],{"class":39,"line":164},10,[37,166,65],{"emptyLinePlaceholder":64},[37,168,170],{"class":39,"line":169},11,[37,171,172],{},"    # Connect to NoLag\n",[37,174,176],{"class":39,"line":175},12,[37,177,178],{},"    await client.connect()\n",[37,180,182],{"class":39,"line":181},13,[37,183,65],{"emptyLinePlaceholder":64},[37,185,187],{"class":39,"line":186},14,[37,188,189],{},"    # Scope to an app and a room. The room must already exist.\n",[37,191,193],{"class":39,"line":192},15,[37,194,195],{},"    room = client.set_app(APP_SLUG).set_room(\"general\")\n",[37,197,199],{"class":39,"line":198},16,[37,200,65],{"emptyLinePlaceholder":64},[37,202,204],{"class":39,"line":203},17,[37,205,206],{},"    # Subscribe to a topic in the room\n",[37,208,210],{"class":39,"line":209},18,[37,211,212],{},"    await room.subscribe(\"messages\")\n",[37,214,216],{"class":39,"line":215},19,[37,217,65],{"emptyLinePlaceholder":64},[37,219,221],{"class":39,"line":220},20,[37,222,223],{},"    # Listen for messages on that topic\n",[37,225,227],{"class":39,"line":226},21,[37,228,229],{},"    def on_message(data, meta):\n",[37,231,233],{"class":39,"line":232},22,[37,234,235],{},"        print(f\"Received: {data}\")\n",[37,237,239],{"class":39,"line":238},23,[37,240,65],{"emptyLinePlaceholder":64},[37,242,244],{"class":39,"line":243},24,[37,245,246],{},"    room.on(\"messages\", on_message)\n",[37,248,250],{"class":39,"line":249},25,[37,251,65],{"emptyLinePlaceholder":64},[37,253,255],{"class":39,"line":254},26,[37,256,257],{},"    # Publish a message\n",[37,259,261],{"class":39,"line":260},27,[37,262,263],{},"    await room.emit(\"messages\", {\"hello\": \"world\"})\n",[37,265,267],{"class":39,"line":266},28,[37,268,65],{"emptyLinePlaceholder":64},[37,270,272],{"class":39,"line":271},29,[37,273,274],{},"    # Keep running\n",[37,276,278],{"class":39,"line":277},30,[37,279,280],{},"    await asyncio.sleep(60)\n",[37,282,284],{"class":39,"line":283},31,[37,285,65],{"emptyLinePlaceholder":64},[37,287,289],{"class":39,"line":288},32,[37,290,291],{},"    # Disconnect when done (synchronous)\n",[37,293,295],{"class":39,"line":294},33,[37,296,297],{},"    client.disconnect()\n",[37,299,301],{"class":39,"line":300},34,[37,302,65],{"emptyLinePlaceholder":64},[37,304,306],{"class":39,"line":305},35,[37,307,308],{},"asyncio.run(main())\n",[310,311,313],"callout",{"type":312},"info",[14,314,315,319,320,323,324,327,328,331,332,335,336,341,342,345,346,349,350,353],{},[316,317,318],"strong",{},"Topics live inside rooms."," The only hierarchy is ",[34,321,322],{},"app/room/topic",": the room context above addresses ",[34,325,326],{},"chat-a3f9/general/messages",". Topic names are single tokens (",[34,329,330],{},"[a-zA-Z0-9_:-]",", no ",[34,333,334],{},"/","). Rooms never exist implicitly: create them via the ",[337,338,340],"a",{"href":339},"#rest-api-client","REST API"," or the portal first, or subscribing returns ",[34,343,344],{},"unknown_topic"," (42940). App slugs always get a random 4-hex suffix, so read ",[34,347,348],{},"slug"," from the create response and pass that to ",[34,351,352],{},"set_app()",".",[18,355,357],{"id":356},"configuration","Configuration",[23,359,360],{},[26,361,363],{"className":112,"code":362,"filename":114,"language":115,"meta":32,"style":32},"from nolag import NoLag, NoLagOptions, QoS\n\noptions = NoLagOptions(\n    url=\"wss://broker.nolag.app/ws\",  # Custom broker URL\n    reconnect=True,                     # Auto-reconnect on disconnect\n    reconnect_interval=5.0,             # Seconds between reconnect attempts\n    max_reconnect_attempts=10,          # Max reconnect attempts (default 10; 0 disables reconnection)\n    heartbeat_interval=30.0,            # Heartbeat interval in seconds (0 to disable)\n    qos=QoS.AT_LEAST_ONCE,              # Default QoS level\n    load_balance=False,                 # Enable load balancing\n    load_balance_group=None,            # Load balance group name\n    debug=True,                         # Enable debug logging\n)\n\nclient = NoLag(\"your-actor-token\", options)\n",[34,364,365,370,374,379,384,389,394,399,404,409,414,419,424,429,433],{"__ignoreMap":32},[37,366,367],{"class":39,"line":40},[37,368,369],{},"from nolag import NoLag, NoLagOptions, QoS\n",[37,371,372],{"class":39,"line":47},[37,373,65],{"emptyLinePlaceholder":64},[37,375,376],{"class":39,"line":61},[37,377,378],{},"options = NoLagOptions(\n",[37,380,381],{"class":39,"line":68},[37,382,383],{},"    url=\"wss://broker.nolag.app/ws\",  # Custom broker URL\n",[37,385,386],{"class":39,"line":74},[37,387,388],{},"    reconnect=True,                     # Auto-reconnect on disconnect\n",[37,390,391],{"class":39,"line":85},[37,392,393],{},"    reconnect_interval=5.0,             # Seconds between reconnect attempts\n",[37,395,396],{"class":39,"line":90},[37,397,398],{},"    max_reconnect_attempts=10,          # Max reconnect attempts (default 10; 0 disables reconnection)\n",[37,400,401],{"class":39,"line":96},[37,402,403],{},"    heartbeat_interval=30.0,            # Heartbeat interval in seconds (0 to disable)\n",[37,405,406],{"class":39,"line":158},[37,407,408],{},"    qos=QoS.AT_LEAST_ONCE,              # Default QoS level\n",[37,410,411],{"class":39,"line":164},[37,412,413],{},"    load_balance=False,                 # Enable load balancing\n",[37,415,416],{"class":39,"line":169},[37,417,418],{},"    load_balance_group=None,            # Load balance group name\n",[37,420,421],{"class":39,"line":175},[37,422,423],{},"    debug=True,                         # Enable debug logging\n",[37,425,426],{"class":39,"line":181},[37,427,428],{},")\n",[37,430,431],{"class":39,"line":186},[37,432,65],{"emptyLinePlaceholder":64},[37,434,435],{"class":39,"line":192},[37,436,437],{},"client = NoLag(\"your-actor-token\", options)\n",[14,439,440,441,444,445,448],{},"There is no unlimited mode: reconnection stops after ",[34,442,443],{},"max_reconnect_attempts","\nconsecutive failures, and ",[34,446,447],{},"0"," never reconnects.",[18,450,452],{"id":451},"fluent-api","Fluent API",[14,454,455],{},"The fluent API lets you scope operations to a specific app and room,\nso you don't need to repeat the full topic path every time.",[23,457,458],{},[26,459,461],{"className":112,"code":460,"filename":114,"language":115,"meta":32,"style":32},"# Scope topics to an app and room. APP_SLUG is the suffixed slug from the create response\nroom = client.set_app(APP_SLUG).set_room(\"general\")\n\n# Subscribe, listen, and emit within the room\nawait room.subscribe(\"messages\")\nroom.on(\"messages\", lambda data, meta: print(data))\nawait room.emit(\"messages\", {\"text\": \"Hello from general!\"})\n\n# This is equivalent to using full topic paths (with APP_SLUG = \"chat-a3f9\"):\n# await client.subscribe(\"chat-a3f9/general/messages\")\n# client.on(\"chat-a3f9/general/messages\", handler)\n# await client.emit(\"chat-a3f9/general/messages\", data)\n\n# Room also supports filter management\nawait room.set_filters(\"messages\", [\"lang:en\", \"lang:fr\"])\nawait room.add_filters(\"messages\", [\"lang:de\"])\nawait room.remove_filters(\"messages\", [\"lang:fr\"])\n\n# Room-scoped presence\nawait room.set_presence({\"status\": \"online\"})\nmembers = await room.fetch_presence()\n\n# Unsubscribe within the room\nawait room.unsubscribe(\"messages\")\n",[34,462,463,468,473,477,482,487,492,497,501,506,511,516,521,525,530,535,540,545,549,554,559,564,568,573],{"__ignoreMap":32},[37,464,465],{"class":39,"line":40},[37,466,467],{},"# Scope topics to an app and room. APP_SLUG is the suffixed slug from the create response\n",[37,469,470],{"class":39,"line":47},[37,471,472],{},"room = client.set_app(APP_SLUG).set_room(\"general\")\n",[37,474,475],{"class":39,"line":61},[37,476,65],{"emptyLinePlaceholder":64},[37,478,479],{"class":39,"line":68},[37,480,481],{},"# Subscribe, listen, and emit within the room\n",[37,483,484],{"class":39,"line":74},[37,485,486],{},"await room.subscribe(\"messages\")\n",[37,488,489],{"class":39,"line":85},[37,490,491],{},"room.on(\"messages\", lambda data, meta: print(data))\n",[37,493,494],{"class":39,"line":90},[37,495,496],{},"await room.emit(\"messages\", {\"text\": \"Hello from general!\"})\n",[37,498,499],{"class":39,"line":96},[37,500,65],{"emptyLinePlaceholder":64},[37,502,503],{"class":39,"line":158},[37,504,505],{},"# This is equivalent to using full topic paths (with APP_SLUG = \"chat-a3f9\"):\n",[37,507,508],{"class":39,"line":164},[37,509,510],{},"# await client.subscribe(\"chat-a3f9/general/messages\")\n",[37,512,513],{"class":39,"line":169},[37,514,515],{},"# client.on(\"chat-a3f9/general/messages\", handler)\n",[37,517,518],{"class":39,"line":175},[37,519,520],{},"# await client.emit(\"chat-a3f9/general/messages\", data)\n",[37,522,523],{"class":39,"line":181},[37,524,65],{"emptyLinePlaceholder":64},[37,526,527],{"class":39,"line":186},[37,528,529],{},"# Room also supports filter management\n",[37,531,532],{"class":39,"line":192},[37,533,534],{},"await room.set_filters(\"messages\", [\"lang:en\", \"lang:fr\"])\n",[37,536,537],{"class":39,"line":198},[37,538,539],{},"await room.add_filters(\"messages\", [\"lang:de\"])\n",[37,541,542],{"class":39,"line":203},[37,543,544],{},"await room.remove_filters(\"messages\", [\"lang:fr\"])\n",[37,546,547],{"class":39,"line":209},[37,548,65],{"emptyLinePlaceholder":64},[37,550,551],{"class":39,"line":215},[37,552,553],{},"# Room-scoped presence\n",[37,555,556],{"class":39,"line":220},[37,557,558],{},"await room.set_presence({\"status\": \"online\"})\n",[37,560,561],{"class":39,"line":226},[37,562,563],{},"members = await room.fetch_presence()\n",[37,565,566],{"class":39,"line":232},[37,567,65],{"emptyLinePlaceholder":64},[37,569,570],{"class":39,"line":238},[37,571,572],{},"# Unsubscribe within the room\n",[37,574,575],{"class":39,"line":243},[37,576,577],{},"await room.unsubscribe(\"messages\")\n",[18,579,581],{"id":580},"subscribing-to-topics","Subscribing to Topics",[14,583,584,587,588,591],{},[34,585,586],{},"subscribe()"," registers interest in a topic.\nUse ",[34,589,590],{},"on()"," separately to attach a message handler.",[23,593,594],{},[26,595,597],{"className":112,"code":596,"filename":114,"language":115,"meta":32,"style":32},"from nolag import SubscribeOptions, QoS\n\nroom = client.set_app(APP_SLUG).set_room(\"general\")\n\n# Basic subscription + handler\nawait room.subscribe(\"messages\")\nroom.on(\"messages\", lambda data, meta: print(data))\n\n# With options (QoS, filters, load balancing)\noptions = SubscribeOptions(\n    qos=QoS.AT_LEAST_ONCE,\n    load_balance=True,\n    load_balance_group=\"workers\",\n    filters=[\"priority:high\", \"region:us\"]\n)\nawait room.subscribe(\"tasks\", options)\nroom.on(\"tasks\", lambda data, meta: print(f\"Task: {data}\"))\n\n# Client-level equivalent: the full app/room/topic path\nawait client.subscribe(f\"{APP_SLUG}/general/messages\")\nclient.on(f\"{APP_SLUG}/general/messages\", lambda data, meta: print(data))\n\n# Unsubscribe\nawait room.unsubscribe(\"messages\")\n",[34,598,599,604,608,612,616,621,625,629,633,638,643,648,653,658,663,667,672,677,681,686,691,696,700,705],{"__ignoreMap":32},[37,600,601],{"class":39,"line":40},[37,602,603],{},"from nolag import SubscribeOptions, QoS\n",[37,605,606],{"class":39,"line":47},[37,607,65],{"emptyLinePlaceholder":64},[37,609,610],{"class":39,"line":61},[37,611,472],{},[37,613,614],{"class":39,"line":68},[37,615,65],{"emptyLinePlaceholder":64},[37,617,618],{"class":39,"line":74},[37,619,620],{},"# Basic subscription + handler\n",[37,622,623],{"class":39,"line":85},[37,624,486],{},[37,626,627],{"class":39,"line":90},[37,628,491],{},[37,630,631],{"class":39,"line":96},[37,632,65],{"emptyLinePlaceholder":64},[37,634,635],{"class":39,"line":158},[37,636,637],{},"# With options (QoS, filters, load balancing)\n",[37,639,640],{"class":39,"line":164},[37,641,642],{},"options = SubscribeOptions(\n",[37,644,645],{"class":39,"line":169},[37,646,647],{},"    qos=QoS.AT_LEAST_ONCE,\n",[37,649,650],{"class":39,"line":175},[37,651,652],{},"    load_balance=True,\n",[37,654,655],{"class":39,"line":181},[37,656,657],{},"    load_balance_group=\"workers\",\n",[37,659,660],{"class":39,"line":186},[37,661,662],{},"    filters=[\"priority:high\", \"region:us\"]\n",[37,664,665],{"class":39,"line":192},[37,666,428],{},[37,668,669],{"class":39,"line":198},[37,670,671],{},"await room.subscribe(\"tasks\", options)\n",[37,673,674],{"class":39,"line":203},[37,675,676],{},"room.on(\"tasks\", lambda data, meta: print(f\"Task: {data}\"))\n",[37,678,679],{"class":39,"line":209},[37,680,65],{"emptyLinePlaceholder":64},[37,682,683],{"class":39,"line":215},[37,684,685],{},"# Client-level equivalent: the full app/room/topic path\n",[37,687,688],{"class":39,"line":220},[37,689,690],{},"await client.subscribe(f\"{APP_SLUG}/general/messages\")\n",[37,692,693],{"class":39,"line":226},[37,694,695],{},"client.on(f\"{APP_SLUG}/general/messages\", lambda data, meta: print(data))\n",[37,697,698],{"class":39,"line":232},[37,699,65],{"emptyLinePlaceholder":64},[37,701,702],{"class":39,"line":238},[37,703,704],{},"# Unsubscribe\n",[37,706,707],{"class":39,"line":243},[37,708,577],{},[18,710,712],{"id":711},"publishing-messages","Publishing Messages",[23,714,715],{},[26,716,718],{"className":112,"code":717,"filename":114,"language":115,"meta":32,"style":32},"from nolag import EmitOptions, QoS\n\n# Publish any data (dict, list, string, bytes, etc.)\nawait room.emit(\"messages\", {\"text\": \"Hello!\"})\n\n# With options\noptions = EmitOptions(\n    qos=QoS.AT_LEAST_ONCE,\n    retain=True,            # Broker keeps the last message for new subscribers\n    filter=\"priority:high\", # Publish with a filter value\n    echo=False,             # Per-connection flag; see the note below\n)\nawait room.emit(\"status\", {\"online\": True}, options)\n\n# Client-level equivalent: the full path\nawait client.emit(f\"{APP_SLUG}/general/messages\", {\"text\": \"Hello!\"})\n",[34,719,720,725,729,734,739,743,748,753,757,762,767,772,776,781,785,790],{"__ignoreMap":32},[37,721,722],{"class":39,"line":40},[37,723,724],{},"from nolag import EmitOptions, QoS\n",[37,726,727],{"class":39,"line":47},[37,728,65],{"emptyLinePlaceholder":64},[37,730,731],{"class":39,"line":61},[37,732,733],{},"# Publish any data (dict, list, string, bytes, etc.)\n",[37,735,736],{"class":39,"line":68},[37,737,738],{},"await room.emit(\"messages\", {\"text\": \"Hello!\"})\n",[37,740,741],{"class":39,"line":74},[37,742,65],{"emptyLinePlaceholder":64},[37,744,745],{"class":39,"line":85},[37,746,747],{},"# With options\n",[37,749,750],{"class":39,"line":90},[37,751,752],{},"options = EmitOptions(\n",[37,754,755],{"class":39,"line":96},[37,756,647],{},[37,758,759],{"class":39,"line":158},[37,760,761],{},"    retain=True,            # Broker keeps the last message for new subscribers\n",[37,763,764],{"class":39,"line":164},[37,765,766],{},"    filter=\"priority:high\", # Publish with a filter value\n",[37,768,769],{"class":39,"line":169},[37,770,771],{},"    echo=False,             # Per-connection flag; see the note below\n",[37,773,774],{"class":39,"line":175},[37,775,428],{},[37,777,778],{"class":39,"line":181},[37,779,780],{},"await room.emit(\"status\", {\"online\": True}, options)\n",[37,782,783],{"class":39,"line":186},[37,784,65],{"emptyLinePlaceholder":64},[37,786,787],{"class":39,"line":192},[37,788,789],{},"# Client-level equivalent: the full path\n",[37,791,792],{"class":39,"line":198},[37,793,794],{},"await client.emit(f\"{APP_SLUG}/general/messages\", {\"text\": \"Hello!\"})\n",[310,796,797],{"type":312},[14,798,799,802,803,806,807,810],{},[316,800,801],{},"Publishers never receive their own messages."," The broker drops a message before delivering it to the actor that published it, whatever ",[34,804,805],{},"echo"," is set to. Append your own message to the UI locally. ",[34,808,809],{},"echo=False"," only adds a per-connection drop for the rare case of two connections sharing one actor token.",[18,812,814],{"id":813},"filters","Filters",[14,816,817,818,821],{},"Filters let subscribers receive only messages matching specific criteria.\nPublishers tag messages with a filter value via ",[34,819,820],{},"EmitOptions.filter",",\nand subscribers set which filters they care about.",[23,823,824],{},[26,825,827],{"className":112,"code":826,"filename":114,"language":115,"meta":32,"style":32},"from nolag import SubscribeOptions, EmitOptions\n\nroom = client.set_app(APP_SLUG).set_room(\"ops\")\n\n# Subscribe with initial filters\nawait room.subscribe(\"events\", SubscribeOptions(\n    filters=[\"region:us\", \"priority:high\"]\n))\nroom.on(\"events\", lambda data, meta: print(data, meta.filter))\n\n# Replace all filters for a topic\nawait room.set_filters(\"events\", [\"region:eu\"])\n\n# Add filters to the existing set\nawait room.add_filters(\"events\", [\"priority:critical\"])\n\n# Remove specific filters\nawait room.remove_filters(\"events\", [\"region:eu\"])\n\n# Clear all filters (switches back to wildcard / receive all)\nawait room.set_filters(\"events\", [])\n\n# Publish with a filter value\nawait room.emit(\"events\", {\"msg\": \"US high-priority\"},\n    EmitOptions(filter=\"region:us\"))\n",[34,828,829,834,838,843,847,852,857,862,867,872,876,881,886,890,895,900,904,909,914,918,923,928,932,937,942],{"__ignoreMap":32},[37,830,831],{"class":39,"line":40},[37,832,833],{},"from nolag import SubscribeOptions, EmitOptions\n",[37,835,836],{"class":39,"line":47},[37,837,65],{"emptyLinePlaceholder":64},[37,839,840],{"class":39,"line":61},[37,841,842],{},"room = client.set_app(APP_SLUG).set_room(\"ops\")\n",[37,844,845],{"class":39,"line":68},[37,846,65],{"emptyLinePlaceholder":64},[37,848,849],{"class":39,"line":74},[37,850,851],{},"# Subscribe with initial filters\n",[37,853,854],{"class":39,"line":85},[37,855,856],{},"await room.subscribe(\"events\", SubscribeOptions(\n",[37,858,859],{"class":39,"line":90},[37,860,861],{},"    filters=[\"region:us\", \"priority:high\"]\n",[37,863,864],{"class":39,"line":96},[37,865,866],{},"))\n",[37,868,869],{"class":39,"line":158},[37,870,871],{},"room.on(\"events\", lambda data, meta: print(data, meta.filter))\n",[37,873,874],{"class":39,"line":164},[37,875,65],{"emptyLinePlaceholder":64},[37,877,878],{"class":39,"line":169},[37,879,880],{},"# Replace all filters for a topic\n",[37,882,883],{"class":39,"line":175},[37,884,885],{},"await room.set_filters(\"events\", [\"region:eu\"])\n",[37,887,888],{"class":39,"line":181},[37,889,65],{"emptyLinePlaceholder":64},[37,891,892],{"class":39,"line":186},[37,893,894],{},"# Add filters to the existing set\n",[37,896,897],{"class":39,"line":192},[37,898,899],{},"await room.add_filters(\"events\", [\"priority:critical\"])\n",[37,901,902],{"class":39,"line":198},[37,903,65],{"emptyLinePlaceholder":64},[37,905,906],{"class":39,"line":203},[37,907,908],{},"# Remove specific filters\n",[37,910,911],{"class":39,"line":209},[37,912,913],{},"await room.remove_filters(\"events\", [\"region:eu\"])\n",[37,915,916],{"class":39,"line":215},[37,917,65],{"emptyLinePlaceholder":64},[37,919,920],{"class":39,"line":220},[37,921,922],{},"# Clear all filters (switches back to wildcard / receive all)\n",[37,924,925],{"class":39,"line":226},[37,926,927],{},"await room.set_filters(\"events\", [])\n",[37,929,930],{"class":39,"line":232},[37,931,65],{"emptyLinePlaceholder":64},[37,933,934],{"class":39,"line":238},[37,935,936],{},"# Publish with a filter value\n",[37,938,939],{"class":39,"line":243},[37,940,941],{},"await room.emit(\"events\", {\"msg\": \"US high-priority\"},\n",[37,943,944],{"class":39,"line":249},[37,945,946],{},"    EmitOptions(filter=\"region:us\"))\n",[310,948,949],{"type":312},[14,950,951,954,955,957,958,957,961,964,965,968,969,353],{},[316,952,953],{},"No filters = wildcard:"," Subscribing without filters receives all messages on the topic, including filtered publishes. Subscribing with filters only receives messages published with a matching filter. Messages published without a filter are only delivered to wildcard (no-filter) subscribers. Each filter is its own routing key, not a privacy boundary. Max 100 filters per topic; values must not contain ",[34,956,334],{},", ",[34,959,960],{},"#",[34,962,963],{},"+"," or ",[34,966,967],{},"|",". See ",[337,970,814],{"href":971},"/docs/concepts/filters",[18,973,975],{"id":974},"connection-events","Connection Events",[23,977,978],{},[26,979,981],{"className":112,"code":980,"filename":114,"language":115,"meta":32,"style":32},"from nolag import ConnectionStatus\n\n# Listen for connection events\nclient.on(\"connect\", lambda: print(\"Connected!\"))\nclient.on(\"disconnect\", lambda reason: print(f\"Disconnected: {reason}\"))\nclient.on(\"reconnect\", lambda: print(\"Reconnected!\"))\n\n# Broker errors arrive as NoLagServerError (see Error Handling)\nclient.on(\"error\", lambda err: print(f\"Error: {err}\"))\n\n# Hydration: with a hydration webhook configured on the app, the broker sends\n# the webhook's response body once per subscribe. `topic` is the bare topic name.\nclient.on(\"hydration\", lambda topic, data: print(f\"Initial state for {topic}: {data}\"))\n\n# Check connection status\nif client.status == ConnectionStatus.CONNECTED:\n    print(\"We're connected!\")\n",[34,982,983,988,992,997,1002,1007,1012,1016,1021,1026,1030,1035,1040,1045,1049,1054,1059],{"__ignoreMap":32},[37,984,985],{"class":39,"line":40},[37,986,987],{},"from nolag import ConnectionStatus\n",[37,989,990],{"class":39,"line":47},[37,991,65],{"emptyLinePlaceholder":64},[37,993,994],{"class":39,"line":61},[37,995,996],{},"# Listen for connection events\n",[37,998,999],{"class":39,"line":68},[37,1000,1001],{},"client.on(\"connect\", lambda: print(\"Connected!\"))\n",[37,1003,1004],{"class":39,"line":74},[37,1005,1006],{},"client.on(\"disconnect\", lambda reason: print(f\"Disconnected: {reason}\"))\n",[37,1008,1009],{"class":39,"line":85},[37,1010,1011],{},"client.on(\"reconnect\", lambda: print(\"Reconnected!\"))\n",[37,1013,1014],{"class":39,"line":90},[37,1015,65],{"emptyLinePlaceholder":64},[37,1017,1018],{"class":39,"line":96},[37,1019,1020],{},"# Broker errors arrive as NoLagServerError (see Error Handling)\n",[37,1022,1023],{"class":39,"line":158},[37,1024,1025],{},"client.on(\"error\", lambda err: print(f\"Error: {err}\"))\n",[37,1027,1028],{"class":39,"line":164},[37,1029,65],{"emptyLinePlaceholder":64},[37,1031,1032],{"class":39,"line":169},[37,1033,1034],{},"# Hydration: with a hydration webhook configured on the app, the broker sends\n",[37,1036,1037],{"class":39,"line":175},[37,1038,1039],{},"# the webhook's response body once per subscribe. `topic` is the bare topic name.\n",[37,1041,1042],{"class":39,"line":181},[37,1043,1044],{},"client.on(\"hydration\", lambda topic, data: print(f\"Initial state for {topic}: {data}\"))\n",[37,1046,1047],{"class":39,"line":186},[37,1048,65],{"emptyLinePlaceholder":64},[37,1050,1051],{"class":39,"line":192},[37,1052,1053],{},"# Check connection status\n",[37,1055,1056],{"class":39,"line":198},[37,1057,1058],{},"if client.status == ConnectionStatus.CONNECTED:\n",[37,1060,1061],{"class":39,"line":203},[37,1062,1063],{},"    print(\"We're connected!\")\n",[14,1065,1066,1067,1071],{},"See ",[337,1068,1070],{"href":1069},"/docs/concepts/webhooks","Webhooks"," for configuring hydration.",[18,1073,1075],{"id":1074},"wildcard-handler-on_any-off_any","Wildcard Handler (on_any / off_any)",[14,1077,1078,1079,1082,1083,1086,1087,1090,1091,1093],{},"Use ",[34,1080,1081],{},"on_any()"," to receive every message regardless of topic.\nThe handler receives ",[34,1084,1085],{},"(topic, data, meta)",", where ",[34,1088,1089],{},"topic"," is the full\n",[34,1092,322],{}," path.",[23,1095,1096],{},[26,1097,1099],{"className":112,"code":1098,"filename":114,"language":115,"meta":32,"style":32},"# Listen for ALL messages regardless of topic\ndef log_all(topic, data, meta):\n    print(f\"[{topic}] {data} (from: {meta.sender})\")\n\nclient.on_any(log_all)\n\n# Remove a specific on_any handler\nclient.off_any(log_all)\n\n# Remove ALL on_any handlers\nclient.off_any()\n",[34,1100,1101,1106,1111,1116,1120,1125,1129,1134,1139,1143,1148],{"__ignoreMap":32},[37,1102,1103],{"class":39,"line":40},[37,1104,1105],{},"# Listen for ALL messages regardless of topic\n",[37,1107,1108],{"class":39,"line":47},[37,1109,1110],{},"def log_all(topic, data, meta):\n",[37,1112,1113],{"class":39,"line":61},[37,1114,1115],{},"    print(f\"[{topic}] {data} (from: {meta.sender})\")\n",[37,1117,1118],{"class":39,"line":68},[37,1119,65],{"emptyLinePlaceholder":64},[37,1121,1122],{"class":39,"line":74},[37,1123,1124],{},"client.on_any(log_all)\n",[37,1126,1127],{"class":39,"line":85},[37,1128,65],{"emptyLinePlaceholder":64},[37,1130,1131],{"class":39,"line":90},[37,1132,1133],{},"# Remove a specific on_any handler\n",[37,1135,1136],{"class":39,"line":96},[37,1137,1138],{},"client.off_any(log_all)\n",[37,1140,1141],{"class":39,"line":158},[37,1142,65],{"emptyLinePlaceholder":64},[37,1144,1145],{"class":39,"line":164},[37,1146,1147],{},"# Remove ALL on_any handlers\n",[37,1149,1150],{"class":39,"line":169},[37,1151,1152],{},"client.off_any()\n",[18,1154,1156],{"id":1155},"presence","Presence",[14,1158,1159],{},"Presence is scoped to rooms. Set it through the room context; presence events\narrive on the client.",[23,1161,1162],{},[26,1163,1165],{"className":112,"code":1164,"filename":114,"language":115,"meta":32,"style":32},"# Set presence scoped to a room (recommended)\nroom = client.set_app(APP_SLUG).set_room(\"general\")\nawait room.set_presence({\"status\": \"online\", \"username\": \"alice\"})\n\n# Or set presence with an explicit room ID\nawait client.set_presence({\"status\": \"online\"}, room_id=\"general\")\n\n# Fetch the presence list for a room (list of dicts from the broker)\nactors = await room.fetch_presence()\nfor actor in actors:\n    print(f\"{actor['actorTokenId']}: {actor['presence']}\")\n\n# Get cached presence for a specific actor\nactor = client.get_presence(\"some-actor-token-id\")\nif actor:\n    print(f\"{actor.actor_token_id}: {actor.presence}\")\n\n# Get all cached presence data\nfor actor in client.get_all_presence():\n    print(f\"{actor.actor_token_id}: {actor.presence}\")\n\n# Clear your presence\nawait client.clear_presence()\n\n# Presence events are client-level. Each carries actor_token_id and presence\nclient.on(\"presence:join\", lambda actor: print(f\"Joined: {actor.actor_token_id}\"))\nclient.on(\"presence:leave\", lambda actor: print(f\"Left: {actor.actor_token_id}\"))\nclient.on(\"presence:update\", lambda actor: print(f\"Updated: {actor.presence}\"))\n",[34,1166,1167,1172,1176,1181,1185,1190,1195,1199,1204,1209,1214,1219,1223,1228,1233,1238,1243,1247,1252,1257,1261,1265,1270,1275,1279,1284,1289,1294],{"__ignoreMap":32},[37,1168,1169],{"class":39,"line":40},[37,1170,1171],{},"# Set presence scoped to a room (recommended)\n",[37,1173,1174],{"class":39,"line":47},[37,1175,472],{},[37,1177,1178],{"class":39,"line":61},[37,1179,1180],{},"await room.set_presence({\"status\": \"online\", \"username\": \"alice\"})\n",[37,1182,1183],{"class":39,"line":68},[37,1184,65],{"emptyLinePlaceholder":64},[37,1186,1187],{"class":39,"line":74},[37,1188,1189],{},"# Or set presence with an explicit room ID\n",[37,1191,1192],{"class":39,"line":85},[37,1193,1194],{},"await client.set_presence({\"status\": \"online\"}, room_id=\"general\")\n",[37,1196,1197],{"class":39,"line":90},[37,1198,65],{"emptyLinePlaceholder":64},[37,1200,1201],{"class":39,"line":96},[37,1202,1203],{},"# Fetch the presence list for a room (list of dicts from the broker)\n",[37,1205,1206],{"class":39,"line":158},[37,1207,1208],{},"actors = await room.fetch_presence()\n",[37,1210,1211],{"class":39,"line":164},[37,1212,1213],{},"for actor in actors:\n",[37,1215,1216],{"class":39,"line":169},[37,1217,1218],{},"    print(f\"{actor['actorTokenId']}: {actor['presence']}\")\n",[37,1220,1221],{"class":39,"line":175},[37,1222,65],{"emptyLinePlaceholder":64},[37,1224,1225],{"class":39,"line":181},[37,1226,1227],{},"# Get cached presence for a specific actor\n",[37,1229,1230],{"class":39,"line":186},[37,1231,1232],{},"actor = client.get_presence(\"some-actor-token-id\")\n",[37,1234,1235],{"class":39,"line":192},[37,1236,1237],{},"if actor:\n",[37,1239,1240],{"class":39,"line":198},[37,1241,1242],{},"    print(f\"{actor.actor_token_id}: {actor.presence}\")\n",[37,1244,1245],{"class":39,"line":203},[37,1246,65],{"emptyLinePlaceholder":64},[37,1248,1249],{"class":39,"line":209},[37,1250,1251],{},"# Get all cached presence data\n",[37,1253,1254],{"class":39,"line":215},[37,1255,1256],{},"for actor in client.get_all_presence():\n",[37,1258,1259],{"class":39,"line":220},[37,1260,1242],{},[37,1262,1263],{"class":39,"line":226},[37,1264,65],{"emptyLinePlaceholder":64},[37,1266,1267],{"class":39,"line":232},[37,1268,1269],{},"# Clear your presence\n",[37,1271,1272],{"class":39,"line":238},[37,1273,1274],{},"await client.clear_presence()\n",[37,1276,1277],{"class":39,"line":243},[37,1278,65],{"emptyLinePlaceholder":64},[37,1280,1281],{"class":39,"line":249},[37,1282,1283],{},"# Presence events are client-level. Each carries actor_token_id and presence\n",[37,1285,1286],{"class":39,"line":254},[37,1287,1288],{},"client.on(\"presence:join\", lambda actor: print(f\"Joined: {actor.actor_token_id}\"))\n",[37,1290,1291],{"class":39,"line":260},[37,1292,1293],{},"client.on(\"presence:leave\", lambda actor: print(f\"Left: {actor.actor_token_id}\"))\n",[37,1295,1296],{"class":39,"line":266},[37,1297,1298],{},"client.on(\"presence:update\", lambda actor: print(f\"Updated: {actor.presence}\"))\n",[14,1300,1301,1304,1305,1308],{},[34,1302,1303],{},"client.set_presence()"," without a ",[34,1306,1307],{},"room_id"," is deprecated and is not broadcast.",[18,1310,1312],{"id":1311},"error-handling","Error Handling",[14,1314,1315,1316,1319,1320,1323,1324,1326,1327,957,1329,1331,1332,1335,1336,1339],{},"Broker-side failures (a room that does not exist, a topic the actor cannot\nwrite) are asynchronous: they arrive on the ",[34,1317,1318],{},"error"," event as ",[34,1321,1322],{},"NoLagServerError","\nwith ",[34,1325,1318],{}," (the machine name), ",[34,1328,34],{},[34,1330,1089],{}," and ",[34,1333,1334],{},"hint",". Local failures\n(not connected, a payload that cannot be msgpack-encoded) raise from the call.\nRegister the error handler before ",[34,1337,1338],{},"connect()"," so nothing is missed during the\nhandshake.",[23,1341,1342],{},[26,1343,1345],{"className":112,"code":1344,"filename":114,"language":115,"meta":32,"style":32},"import asyncio\nfrom nolag import NoLag, NoLagServerError, NoLagEncodeError\n\nAPP_SLUG = \"chat-a3f9\"\n\ndef on_error(err):\n    if isinstance(err, NoLagServerError):\n        # A structured frame from the broker (protocol v2)\n        print(f\"{err.error} ({err.code}) on {err.topic}: {err.hint}\")\n        if err.error == \"unknown_topic\":\n            # 42940: the room has not been created. Create it via the REST API.\n            pass\n    elif isinstance(err, NoLagEncodeError):\n        print(f\"Cannot encode payload for {err.op}: {err}\")\n    else:\n        print(f\"Error: {err}\")\n\nasync def main():\n    client = NoLag(\"your-actor-token\")\n    client.on(\"error\", on_error)\n\n    try:\n        await client.connect()\n    except Exception as e:\n        print(f\"Connection failed: {e}\")\n        return\n\n    room = client.set_app(APP_SLUG).set_room(\"general\")\n\n    # Local failures raise; broker failures go to on_error\n    try:\n        await room.emit(\"messages\", {\"data\": \"value\"})\n    except Exception as e:\n        print(f\"Emit failed: {e}\")\n\n    # Disconnect is synchronous, no await needed\n    client.disconnect()\n\nasyncio.run(main())\n",[34,1346,1347,1351,1356,1360,1364,1368,1373,1378,1383,1388,1393,1398,1403,1408,1413,1418,1423,1427,1431,1435,1440,1444,1449,1454,1459,1464,1469,1473,1477,1481,1486,1490,1495,1499,1504,1508,1514,1519,1524],{"__ignoreMap":32},[37,1348,1349],{"class":39,"line":40},[37,1350,122],{},[37,1352,1353],{"class":39,"line":47},[37,1354,1355],{},"from nolag import NoLag, NoLagServerError, NoLagEncodeError\n",[37,1357,1358],{"class":39,"line":61},[37,1359,65],{"emptyLinePlaceholder":64},[37,1361,1362],{"class":39,"line":68},[37,1363,141],{},[37,1365,1366],{"class":39,"line":74},[37,1367,65],{"emptyLinePlaceholder":64},[37,1369,1370],{"class":39,"line":85},[37,1371,1372],{},"def on_error(err):\n",[37,1374,1375],{"class":39,"line":90},[37,1376,1377],{},"    if isinstance(err, NoLagServerError):\n",[37,1379,1380],{"class":39,"line":96},[37,1381,1382],{},"        # A structured frame from the broker (protocol v2)\n",[37,1384,1385],{"class":39,"line":158},[37,1386,1387],{},"        print(f\"{err.error} ({err.code}) on {err.topic}: {err.hint}\")\n",[37,1389,1390],{"class":39,"line":164},[37,1391,1392],{},"        if err.error == \"unknown_topic\":\n",[37,1394,1395],{"class":39,"line":169},[37,1396,1397],{},"            # 42940: the room has not been created. Create it via the REST API.\n",[37,1399,1400],{"class":39,"line":175},[37,1401,1402],{},"            pass\n",[37,1404,1405],{"class":39,"line":181},[37,1406,1407],{},"    elif isinstance(err, NoLagEncodeError):\n",[37,1409,1410],{"class":39,"line":186},[37,1411,1412],{},"        print(f\"Cannot encode payload for {err.op}: {err}\")\n",[37,1414,1415],{"class":39,"line":192},[37,1416,1417],{},"    else:\n",[37,1419,1420],{"class":39,"line":198},[37,1421,1422],{},"        print(f\"Error: {err}\")\n",[37,1424,1425],{"class":39,"line":203},[37,1426,65],{"emptyLinePlaceholder":64},[37,1428,1429],{"class":39,"line":209},[37,1430,150],{},[37,1432,1433],{"class":39,"line":215},[37,1434,161],{},[37,1436,1437],{"class":39,"line":220},[37,1438,1439],{},"    client.on(\"error\", on_error)\n",[37,1441,1442],{"class":39,"line":226},[37,1443,65],{"emptyLinePlaceholder":64},[37,1445,1446],{"class":39,"line":232},[37,1447,1448],{},"    try:\n",[37,1450,1451],{"class":39,"line":238},[37,1452,1453],{},"        await client.connect()\n",[37,1455,1456],{"class":39,"line":243},[37,1457,1458],{},"    except Exception as e:\n",[37,1460,1461],{"class":39,"line":249},[37,1462,1463],{},"        print(f\"Connection failed: {e}\")\n",[37,1465,1466],{"class":39,"line":254},[37,1467,1468],{},"        return\n",[37,1470,1471],{"class":39,"line":260},[37,1472,65],{"emptyLinePlaceholder":64},[37,1474,1475],{"class":39,"line":266},[37,1476,195],{},[37,1478,1479],{"class":39,"line":271},[37,1480,65],{"emptyLinePlaceholder":64},[37,1482,1483],{"class":39,"line":277},[37,1484,1485],{},"    # Local failures raise; broker failures go to on_error\n",[37,1487,1488],{"class":39,"line":283},[37,1489,1448],{},[37,1491,1492],{"class":39,"line":288},[37,1493,1494],{},"        await room.emit(\"messages\", {\"data\": \"value\"})\n",[37,1496,1497],{"class":39,"line":294},[37,1498,1458],{},[37,1500,1501],{"class":39,"line":300},[37,1502,1503],{},"        print(f\"Emit failed: {e}\")\n",[37,1505,1506],{"class":39,"line":305},[37,1507,65],{"emptyLinePlaceholder":64},[37,1509,1511],{"class":39,"line":1510},36,[37,1512,1513],{},"    # Disconnect is synchronous, no await needed\n",[37,1515,1517],{"class":39,"line":1516},37,[37,1518,297],{},[37,1520,1522],{"class":39,"line":1521},38,[37,1523,65],{"emptyLinePlaceholder":64},[37,1525,1527],{"class":39,"line":1526},39,[37,1528,308],{},[18,1530,1532],{"id":1531},"qos-levels","QoS Levels",[1534,1535,1536,1552],"table",{},[1537,1538,1539],"thead",{},[1540,1541,1542,1546,1549],"tr",{},[1543,1544,1545],"th",{},"Level",[1543,1547,1548],{},"Name",[1543,1550,1551],{},"Description",[1553,1554,1555,1566,1577],"tbody",{},[1540,1556,1557,1560,1563],{},[1558,1559,447],"td",{},[1558,1561,1562],{},"AT_MOST_ONCE",[1558,1564,1565],{},"Fire and forget on the broker hop",[1540,1567,1568,1571,1574],{},[1558,1569,1570],{},"1",[1558,1572,1573],{},"AT_LEAST_ONCE",[1558,1575,1576],{},"Acknowledged on the broker hop (default)",[1540,1578,1579,1582,1585],{},[1558,1580,1581],{},"2",[1558,1583,1584],{},"EXACTLY_ONCE",[1558,1586,1587],{},"Deduplicated on the broker hop",[310,1589,1591],{"type":1590},"warning",[14,1592,1593,1596,1597,353],{},[316,1594,1595],{},"QoS is a broker-hop setting, not an end-to-end guarantee."," The level is validated (0, 1 or 2) and passed to the broker's internal MQTT hop. The WebSocket leg has an optional publish ack and no resend, so no level is an end-to-end delivery guarantee. Deduplicate with an idempotency key if a message must not be applied twice. See ",[337,1598,1600],{"href":1599},"/docs/concepts/qos","QoS",[18,1602,1604],{"id":1603},"rest-api-client","REST API Client",[14,1606,1607],{},"The SDK also includes a REST API client for managing apps, rooms, actors, and scopes:",[23,1609,1610],{},[26,1611,1613],{"className":112,"code":1612,"filename":114,"language":115,"meta":32,"style":32},"from nolag import NoLagApi, AppCreate, RoomCreate, ActorCreate\n\nasync def main():\n    # Create API client with project-scoped API key\n    async with NoLagApi(\"nlg_live_xxx.secret\") as api:\n        # List apps: PaginatedResult with data and pagination\n        apps = await api.apps.list()\n        print(f\"{len(apps.data)} of {apps.pagination.total} apps \"\n              f\"(page {apps.pagination.page} of {apps.pagination.page_count})\")\n\n        # Create a new app. Without topics every subscribe is unknown_topic\n        app = await api.apps.create(AppCreate(\n            name=\"my-chat-app\",\n            description=\"A real-time chat application\",\n            topics=[\"messages\"],\n        ))\n        print(app.slug)  # e.g. \"my-chat-app-a3f9\": pass this to set_app()\n\n        # Create a room in the app (rooms must exist before clients subscribe)\n        room = await api.rooms.create(app.app_id, RoomCreate(\n            name=\"general\",\n            slug=\"general\"\n        ))\n\n        # Create an actor (save the access token!)\n        actor = await api.actors.create(ActorCreate(\n            name=\"web-client\",\n            actor_type=\"device\"  # device, user, service, session, agent, orchestrator, observer\n        ))\n        print(f\"Actor token: {actor.access_token}\")\n\n        # Scopes are paginated like apps; rooms and actors lists are plain lists\n        scopes = await api.scopes.list()\n        print(scopes.pagination.total)\n",[34,1614,1615,1620,1624,1628,1633,1638,1643,1648,1653,1658,1662,1667,1672,1677,1682,1687,1692,1697,1701,1706,1711,1716,1721,1725,1729,1734,1739,1744,1749,1753,1758,1762,1767,1772],{"__ignoreMap":32},[37,1616,1617],{"class":39,"line":40},[37,1618,1619],{},"from nolag import NoLagApi, AppCreate, RoomCreate, ActorCreate\n",[37,1621,1622],{"class":39,"line":47},[37,1623,65],{"emptyLinePlaceholder":64},[37,1625,1626],{"class":39,"line":61},[37,1627,150],{},[37,1629,1630],{"class":39,"line":68},[37,1631,1632],{},"    # Create API client with project-scoped API key\n",[37,1634,1635],{"class":39,"line":74},[37,1636,1637],{},"    async with NoLagApi(\"nlg_live_xxx.secret\") as api:\n",[37,1639,1640],{"class":39,"line":85},[37,1641,1642],{},"        # List apps: PaginatedResult with data and pagination\n",[37,1644,1645],{"class":39,"line":90},[37,1646,1647],{},"        apps = await api.apps.list()\n",[37,1649,1650],{"class":39,"line":96},[37,1651,1652],{},"        print(f\"{len(apps.data)} of {apps.pagination.total} apps \"\n",[37,1654,1655],{"class":39,"line":158},[37,1656,1657],{},"              f\"(page {apps.pagination.page} of {apps.pagination.page_count})\")\n",[37,1659,1660],{"class":39,"line":164},[37,1661,65],{"emptyLinePlaceholder":64},[37,1663,1664],{"class":39,"line":169},[37,1665,1666],{},"        # Create a new app. Without topics every subscribe is unknown_topic\n",[37,1668,1669],{"class":39,"line":175},[37,1670,1671],{},"        app = await api.apps.create(AppCreate(\n",[37,1673,1674],{"class":39,"line":181},[37,1675,1676],{},"            name=\"my-chat-app\",\n",[37,1678,1679],{"class":39,"line":186},[37,1680,1681],{},"            description=\"A real-time chat application\",\n",[37,1683,1684],{"class":39,"line":192},[37,1685,1686],{},"            topics=[\"messages\"],\n",[37,1688,1689],{"class":39,"line":198},[37,1690,1691],{},"        ))\n",[37,1693,1694],{"class":39,"line":203},[37,1695,1696],{},"        print(app.slug)  # e.g. \"my-chat-app-a3f9\": pass this to set_app()\n",[37,1698,1699],{"class":39,"line":209},[37,1700,65],{"emptyLinePlaceholder":64},[37,1702,1703],{"class":39,"line":215},[37,1704,1705],{},"        # Create a room in the app (rooms must exist before clients subscribe)\n",[37,1707,1708],{"class":39,"line":220},[37,1709,1710],{},"        room = await api.rooms.create(app.app_id, RoomCreate(\n",[37,1712,1713],{"class":39,"line":226},[37,1714,1715],{},"            name=\"general\",\n",[37,1717,1718],{"class":39,"line":232},[37,1719,1720],{},"            slug=\"general\"\n",[37,1722,1723],{"class":39,"line":238},[37,1724,1691],{},[37,1726,1727],{"class":39,"line":243},[37,1728,65],{"emptyLinePlaceholder":64},[37,1730,1731],{"class":39,"line":249},[37,1732,1733],{},"        # Create an actor (save the access token!)\n",[37,1735,1736],{"class":39,"line":254},[37,1737,1738],{},"        actor = await api.actors.create(ActorCreate(\n",[37,1740,1741],{"class":39,"line":260},[37,1742,1743],{},"            name=\"web-client\",\n",[37,1745,1746],{"class":39,"line":266},[37,1747,1748],{},"            actor_type=\"device\"  # device, user, service, session, agent, orchestrator, observer\n",[37,1750,1751],{"class":39,"line":271},[37,1752,1691],{},[37,1754,1755],{"class":39,"line":277},[37,1756,1757],{},"        print(f\"Actor token: {actor.access_token}\")\n",[37,1759,1760],{"class":39,"line":283},[37,1761,65],{"emptyLinePlaceholder":64},[37,1763,1764],{"class":39,"line":288},[37,1765,1766],{},"        # Scopes are paginated like apps; rooms and actors lists are plain lists\n",[37,1768,1769],{"class":39,"line":294},[37,1770,1771],{},"        scopes = await api.scopes.list()\n",[37,1773,1774],{"class":39,"line":300},[37,1775,1776],{},"        print(scopes.pagination.total)\n",[18,1778,1780],{"id":1779},"load-balancing","Load Balancing",[14,1782,1783,1784,1787],{},"Load balancing is opt-in. Without it every subscriber receives every message.\nWith it, subscribers that share a ",[34,1785,1786],{},"load_balance_group"," form a group and each\nmessage is delivered to one member of the group:",[23,1789,1790],{},[26,1791,1793],{"className":112,"code":1792,"filename":114,"language":115,"meta":32,"style":32},"from nolag import NoLag, NoLagOptions, SubscribeOptions\n\n# Option 1: Enable load balancing globally via NoLagOptions\nclient = NoLag(\"your-actor-token\", NoLagOptions(\n    load_balance=True,\n    load_balance_group=\"task-workers\"\n))\n\n# Option 2: Enable per-subscription\nroom = client.set_app(APP_SLUG).set_room(\"image-processing\")\nawait room.subscribe(\n    \"tasks\",\n    SubscribeOptions(\n        load_balance=True,\n        load_balance_group=\"task-workers\"\n    )\n)\nroom.on(\"tasks\", lambda data, meta: print(f\"Processing: {data}\"))\n",[34,1794,1795,1800,1804,1809,1814,1818,1823,1827,1831,1836,1841,1846,1851,1856,1861,1866,1871,1875],{"__ignoreMap":32},[37,1796,1797],{"class":39,"line":40},[37,1798,1799],{},"from nolag import NoLag, NoLagOptions, SubscribeOptions\n",[37,1801,1802],{"class":39,"line":47},[37,1803,65],{"emptyLinePlaceholder":64},[37,1805,1806],{"class":39,"line":61},[37,1807,1808],{},"# Option 1: Enable load balancing globally via NoLagOptions\n",[37,1810,1811],{"class":39,"line":68},[37,1812,1813],{},"client = NoLag(\"your-actor-token\", NoLagOptions(\n",[37,1815,1816],{"class":39,"line":74},[37,1817,652],{},[37,1819,1820],{"class":39,"line":85},[37,1821,1822],{},"    load_balance_group=\"task-workers\"\n",[37,1824,1825],{"class":39,"line":90},[37,1826,866],{},[37,1828,1829],{"class":39,"line":96},[37,1830,65],{"emptyLinePlaceholder":64},[37,1832,1833],{"class":39,"line":158},[37,1834,1835],{},"# Option 2: Enable per-subscription\n",[37,1837,1838],{"class":39,"line":164},[37,1839,1840],{},"room = client.set_app(APP_SLUG).set_room(\"image-processing\")\n",[37,1842,1843],{"class":39,"line":169},[37,1844,1845],{},"await room.subscribe(\n",[37,1847,1848],{"class":39,"line":175},[37,1849,1850],{},"    \"tasks\",\n",[37,1852,1853],{"class":39,"line":181},[37,1854,1855],{},"    SubscribeOptions(\n",[37,1857,1858],{"class":39,"line":186},[37,1859,1860],{},"        load_balance=True,\n",[37,1862,1863],{"class":39,"line":192},[37,1864,1865],{},"        load_balance_group=\"task-workers\"\n",[37,1867,1868],{"class":39,"line":198},[37,1869,1870],{},"    )\n",[37,1872,1873],{"class":39,"line":203},[37,1874,428],{},[37,1876,1877],{"class":39,"line":209},[37,1878,1879],{},"room.on(\"tasks\", lambda data, meta: print(f\"Processing: {data}\"))\n",[14,1881,1882,1883,957,1886,1889,1890,353],{},"Workers whose actor type holds a persistent session (",[34,1884,1885],{},"agent",[34,1887,1888],{},"orchestrator",")\nalso have messages queued while they are away; see ",[337,1891,1893],{"href":1892},"/docs/concepts/replay","Replay",[18,1895,1897],{"id":1896},"type-definitions","Type Definitions",[23,1899,1900],{},[26,1901,1903],{"className":112,"code":1902,"filename":114,"language":115,"meta":32,"style":32},"from nolag import (\n    # WebSocket Client\n    NoLag,              # Main client class\n    NoLagOptions,       # Connection options\n    SubscribeOptions,   # Subscription options\n    EmitOptions,        # Publish options\n    ConnectionStatus,   # Connection status enum\n    ActorType,          # Actor type enum (client.actor_type after connect)\n    QoS,                # QoS level enum\n    MessageMeta,        # Message metadata\n    ActorPresence,      # Presence info\n    NoLagServerError,   # Structured broker error (error, code, topic, hint)\n    NoLagEncodeError,   # Payload could not be msgpack-encoded\n\n    # REST API Client\n    NoLagApi,           # REST API client\n    NoLagApiError,      # API error class\n    PaginatedResult,    # data + pagination\n    Pagination,         # total, page, page_count\n    App, AppCreate, AppUpdate,\n    Room, RoomCreate, RoomUpdate,\n    Actor, ActorWithToken, ActorCreate, ActorUpdate,\n    Scope, ScopeCreate, ScopeUpdate,\n)\n",[34,1904,1905,1910,1915,1920,1925,1930,1935,1940,1945,1950,1955,1960,1965,1970,1974,1979,1984,1989,1994,1999,2004,2009,2014,2019],{"__ignoreMap":32},[37,1906,1907],{"class":39,"line":40},[37,1908,1909],{},"from nolag import (\n",[37,1911,1912],{"class":39,"line":47},[37,1913,1914],{},"    # WebSocket Client\n",[37,1916,1917],{"class":39,"line":61},[37,1918,1919],{},"    NoLag,              # Main client class\n",[37,1921,1922],{"class":39,"line":68},[37,1923,1924],{},"    NoLagOptions,       # Connection options\n",[37,1926,1927],{"class":39,"line":74},[37,1928,1929],{},"    SubscribeOptions,   # Subscription options\n",[37,1931,1932],{"class":39,"line":85},[37,1933,1934],{},"    EmitOptions,        # Publish options\n",[37,1936,1937],{"class":39,"line":90},[37,1938,1939],{},"    ConnectionStatus,   # Connection status enum\n",[37,1941,1942],{"class":39,"line":96},[37,1943,1944],{},"    ActorType,          # Actor type enum (client.actor_type after connect)\n",[37,1946,1947],{"class":39,"line":158},[37,1948,1949],{},"    QoS,                # QoS level enum\n",[37,1951,1952],{"class":39,"line":164},[37,1953,1954],{},"    MessageMeta,        # Message metadata\n",[37,1956,1957],{"class":39,"line":169},[37,1958,1959],{},"    ActorPresence,      # Presence info\n",[37,1961,1962],{"class":39,"line":175},[37,1963,1964],{},"    NoLagServerError,   # Structured broker error (error, code, topic, hint)\n",[37,1966,1967],{"class":39,"line":181},[37,1968,1969],{},"    NoLagEncodeError,   # Payload could not be msgpack-encoded\n",[37,1971,1972],{"class":39,"line":186},[37,1973,65],{"emptyLinePlaceholder":64},[37,1975,1976],{"class":39,"line":192},[37,1977,1978],{},"    # REST API Client\n",[37,1980,1981],{"class":39,"line":198},[37,1982,1983],{},"    NoLagApi,           # REST API client\n",[37,1985,1986],{"class":39,"line":203},[37,1987,1988],{},"    NoLagApiError,      # API error class\n",[37,1990,1991],{"class":39,"line":209},[37,1992,1993],{},"    PaginatedResult,    # data + pagination\n",[37,1995,1996],{"class":39,"line":215},[37,1997,1998],{},"    Pagination,         # total, page, page_count\n",[37,2000,2001],{"class":39,"line":220},[37,2002,2003],{},"    App, AppCreate, AppUpdate,\n",[37,2005,2006],{"class":39,"line":226},[37,2007,2008],{},"    Room, RoomCreate, RoomUpdate,\n",[37,2010,2011],{"class":39,"line":232},[37,2012,2013],{},"    Actor, ActorWithToken, ActorCreate, ActorUpdate,\n",[37,2015,2016],{"class":39,"line":238},[37,2017,2018],{},"    Scope, ScopeCreate, ScopeUpdate,\n",[37,2020,2021],{"class":39,"line":243},[37,2022,428],{},[14,2024,2025,2028,2029,957,2032,957,2035,957,2038,957,2041,2043,2044,964,2046,2049,2050,1331,2052,2054],{},[34,2026,2027],{},"ActorCreate.actor_type"," accepts ",[34,2030,2031],{},"device",[34,2033,2034],{},"user",[34,2036,2037],{},"service",[34,2039,2040],{},"session",[34,2042,1885],{},",\n",[34,2045,1888],{},[34,2047,2048],{},"observer",". Only ",[34,2051,1885],{},[34,2053,1888],{}," hold a persistent\nbroker session.",[18,2056,2058],{"id":2057},"requirements","Requirements",[2060,2061,2062,2066,2069,2072],"ul",{},[2063,2064,2065],"li",{},"Python 3.10+",[2063,2067,2068],{},"websockets >= 12.0",[2063,2070,2071],{},"msgpack >= 1.0.0",[2063,2073,2074],{},"aiohttp >= 3.9.0",[18,2076,2078],{"id":2077},"next-steps","Next Steps",[2060,2080,2081,2091,2097,2103,2109,2113,2117],{},[2063,2082,2083,2087,2088],{},[337,2084,2086],{"href":2085},"/docs/agents/getting-started","AI Agents SDK"," - build multi-agent workflows with ",[34,2089,2090],{},"nolag-agents",[2063,2092,2093],{},[337,2094,2096],{"href":2095},"/docs/concepts/topics","Learn about Topics",[2063,2098,2099],{},[337,2100,2102],{"href":2101},"/docs/concepts/rooms","Rooms",[2063,2104,2105],{},[337,2106,2108],{"href":2107},"/docs/concepts/presence","Presence Tracking",[2063,2110,2111],{},[337,2112,814],{"href":971},[2063,2114,2115],{},[337,2116,1893],{"href":1892},[2063,2118,2119],{},[337,2120,2122],{"href":2121},"/docs/api-reference","REST API Reference",[2124,2125,2126],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":32,"searchDepth":47,"depth":47,"links":2128},[2129,2130,2131,2132,2133,2134,2135,2136,2137,2138,2139,2140,2141,2142,2143,2144,2145],{"id":20,"depth":47,"text":21},{"id":106,"depth":47,"text":107},{"id":356,"depth":47,"text":357},{"id":451,"depth":47,"text":452},{"id":580,"depth":47,"text":581},{"id":711,"depth":47,"text":712},{"id":813,"depth":47,"text":814},{"id":974,"depth":47,"text":975},{"id":1074,"depth":47,"text":1075},{"id":1155,"depth":47,"text":1156},{"id":1311,"depth":47,"text":1312},{"id":1531,"depth":47,"text":1532},{"id":1603,"depth":47,"text":1604},{"id":1779,"depth":47,"text":1780},{"id":1896,"depth":47,"text":1897},{"id":2057,"depth":47,"text":2058},{"id":2077,"depth":47,"text":2078},"Complete reference for the NoLag Python SDK. Connect, subscribe, publish, and manage real-time messaging with async/await support.","md",{},"/docs/sdks/python",{"title":5,"description":2146},"docs/sdks/python","gdmbDvYqoiM518tvq2KFBKKsh9I5ozTiXzzOOCqYV0Q",1789869421490]