darabos commited on
Commit
c0aa740
·
2 Parent(s): 07f0e14 c3ebd8b

Merge pull request #1 from biggraph/darabos-lynxscribe

Browse files
requirements.txt CHANGED
@@ -2,6 +2,7 @@ fastapi
2
  matplotlib
3
  networkx
4
  numpy
 
5
  pandas
6
  scipy
7
  uvicorn[standard]
 
2
  matplotlib
3
  networkx
4
  numpy
5
+ orjson
6
  pandas
7
  scipy
8
  uvicorn[standard]
server/executors/one_by_one.py CHANGED
@@ -1,8 +1,8 @@
1
  from .. import ops
2
  from .. import workspace
3
- import fastapi
4
- import json
5
  import pandas as pd
 
6
  import traceback
7
  import inspect
8
  import typing
@@ -37,7 +37,7 @@ def register(env: str, cache: bool = True):
37
  ops.EXECUTORS[env] = lambda ws: execute(ws, ops.CATALOGS[env], cache=cache)
38
 
39
  def get_stages(ws, catalog):
40
- '''Inputs on top are batch inputs. We decompose the graph into a DAG of components along these edges.'''
41
  nodes = {n.id: n for n in ws.nodes}
42
  batch_inputs = {}
43
  inputs = {}
@@ -46,7 +46,7 @@ def get_stages(ws, catalog):
46
  node = nodes[edge.target]
47
  op = catalog[node.data.title]
48
  i = op.inputs[edge.targetHandle]
49
- if i.position == 'top':
50
  batch_inputs.setdefault(edge.target, []).append(edge.source)
51
  stages = []
52
  for bt, bss in batch_inputs.items():
@@ -63,6 +63,15 @@ def get_stages(ws, catalog):
63
  stages.append(set(nodes))
64
  return stages
65
 
 
 
 
 
 
 
 
 
 
66
  EXECUTOR_OUTPUT_CACHE = {}
67
 
68
  def execute(ws, catalog, cache=None):
@@ -77,7 +86,7 @@ def execute(ws, catalog, cache=None):
77
  node.data.error = None
78
  op = catalog[node.data.title]
79
  # Start tasks for nodes that have no non-batch inputs.
80
- if all([i.position == 'top' for i in op.inputs.values()]):
81
  tasks[node.id] = [NO_INPUT]
82
  batch_inputs = {}
83
  # Run the rest until we run out of tasks.
@@ -99,12 +108,12 @@ def execute(ws, catalog, cache=None):
99
  for task in ts:
100
  try:
101
  inputs = [
102
- batch_inputs[(n, i.name)] if i.position == 'top' else task
103
  for i in op.inputs.values()]
104
- if cache:
105
- key = json.dumps(fastapi.encoders.jsonable_encoder((inputs, params)))
106
  if key not in cache:
107
- cache[key] = op.func(*inputs, **params)
108
  result = cache[key]
109
  else:
110
  result = op(*inputs, **params)
@@ -126,8 +135,9 @@ def execute(ws, catalog, cache=None):
126
  t = nodes[edge.target]
127
  op = catalog[t.data.title]
128
  i = op.inputs[edge.targetHandle]
129
- if i.position == 'top':
130
  batch_inputs.setdefault((edge.target, edge.targetHandle), []).extend(results)
131
  else:
132
  tasks.setdefault(edge.target, []).extend(results)
133
  tasks = next_stage
 
 
1
  from .. import ops
2
  from .. import workspace
3
+ import orjson
 
4
  import pandas as pd
5
+ import pydantic
6
  import traceback
7
  import inspect
8
  import typing
 
37
  ops.EXECUTORS[env] = lambda ws: execute(ws, ops.CATALOGS[env], cache=cache)
38
 
39
  def get_stages(ws, catalog):
40
+ '''Inputs on top/bottom are batch inputs. We decompose the graph into a DAG of components along these edges.'''
41
  nodes = {n.id: n for n in ws.nodes}
42
  batch_inputs = {}
43
  inputs = {}
 
46
  node = nodes[edge.target]
47
  op = catalog[node.data.title]
48
  i = op.inputs[edge.targetHandle]
49
+ if i.position in 'top or bottom':
50
  batch_inputs.setdefault(edge.target, []).append(edge.source)
51
  stages = []
52
  for bt, bss in batch_inputs.items():
 
63
  stages.append(set(nodes))
64
  return stages
65
 
66
+
67
+ def _default_serializer(obj):
68
+ if isinstance(obj, pydantic.BaseModel):
69
+ return obj.dict()
70
+ return {"__nonserializable__": id(obj)}
71
+
72
+ def make_cache_key(obj):
73
+ return orjson.dumps(obj, default=_default_serializer)
74
+
75
  EXECUTOR_OUTPUT_CACHE = {}
76
 
77
  def execute(ws, catalog, cache=None):
 
86
  node.data.error = None
87
  op = catalog[node.data.title]
88
  # Start tasks for nodes that have no non-batch inputs.
89
+ if all([i.position in 'top or bottom' for i in op.inputs.values()]):
90
  tasks[node.id] = [NO_INPUT]
91
  batch_inputs = {}
92
  # Run the rest until we run out of tasks.
 
108
  for task in ts:
109
  try:
110
  inputs = [
111
+ batch_inputs[(n, i.name)] if i.position in 'top or bottom' else task
112
  for i in op.inputs.values()]
113
+ if cache is not None:
114
+ key = make_cache_key((inputs, params))
115
  if key not in cache:
116
+ cache[key] = op(*inputs, **params)
117
  result = cache[key]
118
  else:
119
  result = op(*inputs, **params)
 
135
  t = nodes[edge.target]
136
  op = catalog[t.data.title]
137
  i = op.inputs[edge.targetHandle]
138
+ if i.position in 'top or bottom':
139
  batch_inputs.setdefault((edge.target, edge.targetHandle), []).extend(results)
140
  else:
141
  tasks.setdefault(edge.target, []).extend(results)
142
  tasks = next_stage
143
+ return contexts
server/main.py CHANGED
@@ -1,15 +1,18 @@
1
  import dataclasses
2
  import fastapi
 
3
  import pathlib
4
  import pkgutil
5
  from . import ops
6
  from . import workspace
7
 
8
  here = pathlib.Path(__file__).parent
 
9
  for _, name, _ in pkgutil.iter_modules([str(here)]):
10
  if name.endswith('_ops') and not name.startswith('test_'):
11
  print(f'Importing {name}')
12
- __import__(f'server.{name}')
 
13
 
14
  app = fastapi.FastAPI()
15
 
@@ -67,3 +70,9 @@ def make_dir(req: dict):
67
  assert not path.exists()
68
  path.mkdir()
69
  return list_dir(path.parent)
 
 
 
 
 
 
 
1
  import dataclasses
2
  import fastapi
3
+ import importlib
4
  import pathlib
5
  import pkgutil
6
  from . import ops
7
  from . import workspace
8
 
9
  here = pathlib.Path(__file__).parent
10
+ lynxkite_modules = {}
11
  for _, name, _ in pkgutil.iter_modules([str(here)]):
12
  if name.endswith('_ops') and not name.startswith('test_'):
13
  print(f'Importing {name}')
14
+ name = f'server.{name}'
15
+ lynxkite_modules[name] = importlib.import_module(name)
16
 
17
  app = fastapi.FastAPI()
18
 
 
70
  assert not path.exists()
71
  path.mkdir()
72
  return list_dir(path.parent)
73
+
74
+ @app.post("/api/service")
75
+ async def service(req: dict):
76
+ '''Executors can provide extra HTTP APIs through the /api/service endpoint.'''
77
+ module = lynxkite_modules[req['module']]
78
+ return await module.api_service(req)
web/package-lock.json CHANGED
@@ -25,6 +25,7 @@
25
  "sass": "^1.77.2",
26
  "svelte": "^4.2.12",
27
  "svelte-check": "^3.6.9",
 
28
  "tslib": "^2.6.2",
29
  "typescript": "^5.4.4",
30
  "unplugin-icons": "^0.18.5",
@@ -1081,6 +1082,13 @@
1081
  "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz",
1082
  "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg=="
1083
  },
 
 
 
 
 
 
 
1084
  "node_modules/@types/pug": {
1085
  "version": "2.0.10",
1086
  "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz",
@@ -1844,6 +1852,19 @@
1844
  "@jridgewell/sourcemap-codec": "^1.4.15"
1845
  }
1846
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
1847
  "node_modules/mdn-data": {
1848
  "version": "2.0.30",
1849
  "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
@@ -2460,6 +2481,20 @@
2460
  "svelte": "^3.19.0 || ^4.0.0"
2461
  }
2462
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2463
  "node_modules/svelte-preprocess": {
2464
  "version": "5.1.4",
2465
  "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.4.tgz",
 
25
  "sass": "^1.77.2",
26
  "svelte": "^4.2.12",
27
  "svelte-check": "^3.6.9",
28
+ "svelte-markdown": "^0.4.1",
29
  "tslib": "^2.6.2",
30
  "typescript": "^5.4.4",
31
  "unplugin-icons": "^0.18.5",
 
1082
  "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz",
1083
  "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg=="
1084
  },
1085
+ "node_modules/@types/marked": {
1086
+ "version": "5.0.2",
1087
+ "resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz",
1088
+ "integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==",
1089
+ "dev": true,
1090
+ "license": "MIT"
1091
+ },
1092
  "node_modules/@types/pug": {
1093
  "version": "2.0.10",
1094
  "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz",
 
1852
  "@jridgewell/sourcemap-codec": "^1.4.15"
1853
  }
1854
  },
1855
+ "node_modules/marked": {
1856
+ "version": "5.1.2",
1857
+ "resolved": "https://registry.npmjs.org/marked/-/marked-5.1.2.tgz",
1858
+ "integrity": "sha512-ahRPGXJpjMjwSOlBoTMZAK7ATXkli5qCPxZ21TG44rx1KEo44bii4ekgTDQPNRQ4Kh7JMb9Ub1PVk1NxRSsorg==",
1859
+ "dev": true,
1860
+ "license": "MIT",
1861
+ "bin": {
1862
+ "marked": "bin/marked.js"
1863
+ },
1864
+ "engines": {
1865
+ "node": ">= 16"
1866
+ }
1867
+ },
1868
  "node_modules/mdn-data": {
1869
  "version": "2.0.30",
1870
  "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
 
2481
  "svelte": "^3.19.0 || ^4.0.0"
2482
  }
2483
  },
2484
+ "node_modules/svelte-markdown": {
2485
+ "version": "0.4.1",
2486
+ "resolved": "https://registry.npmjs.org/svelte-markdown/-/svelte-markdown-0.4.1.tgz",
2487
+ "integrity": "sha512-pOlLY6EruKJaWI9my/2bKX8PdTeP5CM0s4VMmwmC2prlOkjAf+AOmTM4wW/l19Y6WZ87YmP8+ZCJCCwBChWjYw==",
2488
+ "dev": true,
2489
+ "license": "MIT",
2490
+ "dependencies": {
2491
+ "@types/marked": "^5.0.1",
2492
+ "marked": "^5.1.2"
2493
+ },
2494
+ "peerDependencies": {
2495
+ "svelte": "^4.0.0"
2496
+ }
2497
+ },
2498
  "node_modules/svelte-preprocess": {
2499
  "version": "5.1.4",
2500
  "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.4.tgz",
web/package.json CHANGED
@@ -15,6 +15,7 @@
15
  "sass": "^1.77.2",
16
  "svelte": "^4.2.12",
17
  "svelte-check": "^3.6.9",
 
18
  "tslib": "^2.6.2",
19
  "typescript": "^5.4.4",
20
  "unplugin-icons": "^0.18.5",
 
15
  "sass": "^1.77.2",
16
  "svelte": "^4.2.12",
17
  "svelte-check": "^3.6.9",
18
+ "svelte-markdown": "^0.4.1",
19
  "tslib": "^2.6.2",
20
  "typescript": "^5.4.4",
21
  "unplugin-icons": "^0.18.5",