Spaces:
Running
Running
Update node display/error in CRDT one by one during execution.
Browse files- lynxkite-app/src/lynxkite_app/crdt.py +8 -9
- lynxkite-app/src/lynxkite_app/main.py +3 -0
- lynxkite-core/src/lynxkite/core/ops.py +2 -1
- lynxkite-core/src/lynxkite/core/workspace.py +11 -0
- lynxkite-graph-analytics/src/lynxkite_graph_analytics/__init__.py +2 -1
- lynxkite-graph-analytics/src/lynxkite_graph_analytics/core.py +5 -11
- lynxkite-graph-analytics/src/lynxkite_graph_analytics/lynxkite_ops.py +1 -1
lynxkite-app/src/lynxkite_app/crdt.py
CHANGED
|
@@ -224,17 +224,16 @@ async def execute(
|
|
| 224 |
assert path.is_relative_to(config.DATA_PATH), "Provided workspace path is invalid"
|
| 225 |
# Save user changes before executing, in case the execution fails.
|
| 226 |
workspace.save(ws_pyd, path)
|
|
|
|
| 227 |
await workspace.execute(ws_pyd)
|
| 228 |
workspace.save(ws_pyd, path)
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
nc["data"]["display"] = np.data.display
|
| 237 |
-
nc["data"]["error"] = np.data.error
|
| 238 |
|
| 239 |
|
| 240 |
@contextlib.asynccontextmanager
|
|
|
|
| 224 |
assert path.is_relative_to(config.DATA_PATH), "Provided workspace path is invalid"
|
| 225 |
# Save user changes before executing, in case the execution fails.
|
| 226 |
workspace.save(ws_pyd, path)
|
| 227 |
+
add_crdt_bindings(ws_pyd, ws_crdt)
|
| 228 |
await workspace.execute(ws_pyd)
|
| 229 |
workspace.save(ws_pyd, path)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def add_crdt_bindings(ws_pyd: workspace.Workspace, ws_crdt: pycrdt.Map):
|
| 233 |
+
for nc, np in zip(ws_crdt["nodes"], ws_pyd.nodes):
|
| 234 |
+
if "data" not in nc:
|
| 235 |
+
nc["data"] = pycrdt.Map()
|
| 236 |
+
np._crdt = nc
|
|
|
|
|
|
|
| 237 |
|
| 238 |
|
| 239 |
@contextlib.asynccontextmanager
|
lynxkite-app/src/lynxkite_app/main.py
CHANGED
|
@@ -5,6 +5,7 @@ import shutil
|
|
| 5 |
import pydantic
|
| 6 |
import fastapi
|
| 7 |
import importlib
|
|
|
|
| 8 |
import pathlib
|
| 9 |
import pkgutil
|
| 10 |
from fastapi.staticfiles import StaticFiles
|
|
@@ -18,6 +19,8 @@ if os.environ.get("NX_CUGRAPH_AUTOCONFIG", "").strip().lower() == "true":
|
|
| 18 |
|
| 19 |
cudf.pandas.install()
|
| 20 |
|
|
|
|
|
|
|
| 21 |
|
| 22 |
def detect_plugins():
|
| 23 |
plugins = {}
|
|
|
|
| 5 |
import pydantic
|
| 6 |
import fastapi
|
| 7 |
import importlib
|
| 8 |
+
import pandas as pd
|
| 9 |
import pathlib
|
| 10 |
import pkgutil
|
| 11 |
from fastapi.staticfiles import StaticFiles
|
|
|
|
| 19 |
|
| 20 |
cudf.pandas.install()
|
| 21 |
|
| 22 |
+
pd.options.mode.copy_on_write = True # Prepare for Pandas 3.0.
|
| 23 |
+
|
| 24 |
|
| 25 |
def detect_plugins():
|
| 26 |
plugins = {}
|
lynxkite-core/src/lynxkite/core/ops.py
CHANGED
|
@@ -94,8 +94,9 @@ class Result:
|
|
| 94 |
JSON-serializable.
|
| 95 |
"""
|
| 96 |
|
| 97 |
-
output: typing.Any
|
| 98 |
display: ReadOnlyJSON | None = None
|
|
|
|
| 99 |
|
| 100 |
|
| 101 |
MULTI_INPUT = Input(name="multi", type="*")
|
|
|
|
| 94 |
JSON-serializable.
|
| 95 |
"""
|
| 96 |
|
| 97 |
+
output: typing.Any = None
|
| 98 |
display: ReadOnlyJSON | None = None
|
| 99 |
+
error: str | None = None
|
| 100 |
|
| 101 |
|
| 102 |
MULTI_INPUT = Input(name="multi", type="*")
|
lynxkite-core/src/lynxkite/core/workspace.py
CHANGED
|
@@ -4,6 +4,7 @@ import json
|
|
| 4 |
from typing import Optional
|
| 5 |
import dataclasses
|
| 6 |
import os
|
|
|
|
| 7 |
import pydantic
|
| 8 |
import tempfile
|
| 9 |
from . import ops
|
|
@@ -36,6 +37,16 @@ class WorkspaceNode(BaseConfig):
|
|
| 36 |
type: str
|
| 37 |
data: WorkspaceNodeData
|
| 38 |
position: Position
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
|
| 41 |
class WorkspaceEdge(BaseConfig):
|
|
|
|
| 4 |
from typing import Optional
|
| 5 |
import dataclasses
|
| 6 |
import os
|
| 7 |
+
import pycrdt
|
| 8 |
import pydantic
|
| 9 |
import tempfile
|
| 10 |
from . import ops
|
|
|
|
| 37 |
type: str
|
| 38 |
data: WorkspaceNodeData
|
| 39 |
position: Position
|
| 40 |
+
_crdt: pycrdt.Map
|
| 41 |
+
|
| 42 |
+
def publish_result(self, result: ops.Result):
|
| 43 |
+
"""Sends the result to the frontend. Call this in an executor when the result is available."""
|
| 44 |
+
with self._crdt.doc.transaction():
|
| 45 |
+
self._crdt["data"]["display"] = result.display
|
| 46 |
+
self._crdt["data"]["error"] = result.error
|
| 47 |
+
|
| 48 |
+
def publish_error(self, error: Exception | str):
|
| 49 |
+
self.publish_result(ops.Result(error=str(error)))
|
| 50 |
|
| 51 |
|
| 52 |
class WorkspaceEdge(BaseConfig):
|
lynxkite-graph-analytics/src/lynxkite_graph_analytics/__init__.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
| 1 |
-
from .
|
|
|
|
| 2 |
from . import networkx_ops # noqa (imported to trigger registration)
|
| 3 |
from . import pytorch_model_ops # noqa (imported to trigger registration)
|
|
|
|
| 1 |
+
from .core import * # noqa (easier access for core classes)
|
| 2 |
+
from . import lynxkite_ops # noqa (imported to trigger registration)
|
| 3 |
from . import networkx_ops # noqa (imported to trigger registration)
|
| 4 |
from . import pytorch_model_ops # noqa (imported to trigger registration)
|
lynxkite-graph-analytics/src/lynxkite_graph_analytics/core.py
CHANGED
|
@@ -158,11 +158,10 @@ async def execute(ws):
|
|
| 158 |
if all(input in outputs for input in inputs):
|
| 159 |
# All inputs for this node are ready, we can compute the output.
|
| 160 |
inputs = [outputs[input] for input in inputs]
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
op = catalog.get(data.title)
|
| 164 |
if not op:
|
| 165 |
-
|
| 166 |
failed += 1
|
| 167 |
continue
|
| 168 |
try:
|
|
@@ -177,16 +176,11 @@ async def execute(ws):
|
|
| 177 |
result = op(*inputs, **params)
|
| 178 |
except Exception as e:
|
| 179 |
traceback.print_exc()
|
| 180 |
-
|
| 181 |
failed += 1
|
| 182 |
continue
|
| 183 |
-
if len(op.inputs) == 1 and op.inputs.get("multi") == "*":
|
| 184 |
-
# It's a flexible input. Create n+1 handles.
|
| 185 |
-
data.inputs = {f"input{i}": None for i in range(len(inputs) + 1)}
|
| 186 |
-
data.error = None
|
| 187 |
outputs[node.id] = result.output
|
| 188 |
-
|
| 189 |
-
data.display = result.display
|
| 190 |
|
| 191 |
|
| 192 |
def df_for_frontend(df: pd.DataFrame, limit: int) -> pd.DataFrame:
|
|
|
|
| 158 |
if all(input in outputs for input in inputs):
|
| 159 |
# All inputs for this node are ready, we can compute the output.
|
| 160 |
inputs = [outputs[input] for input in inputs]
|
| 161 |
+
params = {**node.data.params}
|
| 162 |
+
op = catalog.get(node.data.title)
|
|
|
|
| 163 |
if not op:
|
| 164 |
+
node.publish_error("Operation not found in catalog")
|
| 165 |
failed += 1
|
| 166 |
continue
|
| 167 |
try:
|
|
|
|
| 176 |
result = op(*inputs, **params)
|
| 177 |
except Exception as e:
|
| 178 |
traceback.print_exc()
|
| 179 |
+
node.publish_error(e)
|
| 180 |
failed += 1
|
| 181 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
outputs[node.id] = result.output
|
| 183 |
+
node.publish_result(result)
|
|
|
|
| 184 |
|
| 185 |
|
| 186 |
def df_for_frontend(df: pd.DataFrame, limit: int) -> pd.DataFrame:
|
lynxkite-graph-analytics/src/lynxkite_graph_analytics/lynxkite_ops.py
CHANGED
|
@@ -152,7 +152,7 @@ def _map_color(value):
|
|
| 152 |
if pd.api.types.is_numeric_dtype(value):
|
| 153 |
cmap = matplotlib.cm.get_cmap("viridis")
|
| 154 |
value = (value - value.min()) / (value.max() - value.min())
|
| 155 |
-
rgba = cmap(value)
|
| 156 |
return [
|
| 157 |
"#{:02x}{:02x}{:02x}".format(int(r * 255), int(g * 255), int(b * 255))
|
| 158 |
for r, g, b in rgba[:, :3]
|
|
|
|
| 152 |
if pd.api.types.is_numeric_dtype(value):
|
| 153 |
cmap = matplotlib.cm.get_cmap("viridis")
|
| 154 |
value = (value - value.min()) / (value.max() - value.min())
|
| 155 |
+
rgba = cmap(value.values)
|
| 156 |
return [
|
| 157 |
"#{:02x}{:02x}{:02x}".format(int(r * 255), int(g * 255), int(b * 255))
|
| 158 |
for r, g, b in rgba[:, :3]
|