Skip to content

Commit 3a9ef35

Browse files
Add depends option to set edges without future inputs (#91)
* Add `depends` option to set edges without future inputs * Add a duplicate `depends` in test to make sure we do not make duplicate edges * Formatting * Handle null/undefined cases, include third edge arg, add example
1 parent 9b93945 commit 3a9ef35

File tree

5 files changed

+114
-8
lines changed

5 files changed

+114
-8
lines changed

examples/explicit-edges.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env -S npx ts-node --transpileOnly
2+
3+
import { Substrate, Box } from "substrate";
4+
5+
async function main() {
6+
const SUBSTRATE_API_KEY = process.env["SUBSTRATE_API_KEY"];
7+
8+
const substrate = new Substrate({ apiKey: SUBSTRATE_API_KEY });
9+
10+
// One way to see that the edges determine the node run order is to use the RunPython node to print the
11+
// timestamp of when the node was run. Because the RunPython node isn't available in the TypeScript SDK
12+
// I've taken a pickled python function that does a `print(time.time())` and am overriding the nodes
13+
// here to be RunPython nodes instead.
14+
const toRunPython = (node: Box) => {
15+
node.node = "RunPython";
16+
node.args = {
17+
pkl_function:
18+
"gAWV5QEAAAAAAACMF2Nsb3VkcGlja2xlLmNsb3VkcGlja2xllIwOX21ha2VfZnVuY3Rpb26Uk5QoaACMDV9idWlsdGluX3R5cGWUk5SMCENvZGVUeXBllIWUUpQoSwBLAEsASwFLAktDQxBkAWQAbAB9AHwAoAChAFMAlE5LAIaUjAR0aW1llIWUaAuMOC9Vc2Vycy9saWFtL3dvcmsvc3Vic3RyYXRlLXB5dGhvbi9leGFtcGxlcy9ydW5fcHl0aG9uLnB5lIwKcHJpbnRfdGltZZRLKEMECAEIAZQpKXSUUpR9lCiMC19fcGFja2FnZV9flE6MCF9fbmFtZV9flIwIX19tYWluX1+UjAhfX2ZpbGVfX5RoDHVOTk50lFKUaACMEl9mdW5jdGlvbl9zZXRzdGF0ZZSTlGgXfZR9lChoE2gNjAxfX3F1YWxuYW1lX1+UaA2MD19fYW5ub3RhdGlvbnNfX5R9lIwOX19rd2RlZmF1bHRzX1+UTowMX19kZWZhdWx0c19flE6MCl9fbW9kdWxlX1+UjAhfX21haW5fX5SMB19fZG9jX1+UTowLX19jbG9zdXJlX1+UTowXX2Nsb3VkcGlja2xlX3N1Ym1vZHVsZXOUXZSMC19fZ2xvYmFsc19flH2UdYaUhlIwLg==",
19+
kwargs: {},
20+
pip_install: null,
21+
python_version: "3.10.13",
22+
};
23+
return node;
24+
};
25+
26+
const a = toRunPython(new Box({ value: "" }, { id: "a" }));
27+
28+
const b = toRunPython(new Box({ value: "" }, { id: "b", depends: [a] }));
29+
30+
const c = toRunPython(new Box({ value: "" }, { id: "c", depends: [b] }));
31+
32+
const res = await substrate.run(c, a, b);
33+
console.log(res.json);
34+
}
35+
main();

src/Node.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export type Options = {
1919
cache_keys?: string[];
2020
/** Max number of times to retry this node if it fails. Default: null means no retries */
2121
max_retries?: number;
22+
/** Specify nodes that this node depends on. */
23+
depends?: Node[];
2224
};
2325

2426
export abstract class Node {
@@ -38,6 +40,8 @@ export abstract class Node {
3840
cache_keys?: string[];
3941
/** Max number of times to retry this node if it fails. Default: null means no retries */
4042
max_retries?: number;
43+
/** Specify nodes that this node depends on. */
44+
depends: Node[];
4145

4246
/** TODO this field stores the last response, but it's just temporary until the internals are refactored */
4347
protected _response: SubstrateResponse | undefined;
@@ -50,6 +54,7 @@ export abstract class Node {
5054
this.cache_age = opts?.cache_age;
5155
this.cache_keys = opts?.cache_keys;
5256
this.max_retries = opts?.max_retries;
57+
this.depends = opts?.depends ?? [];
5358
}
5459

5560
/**
@@ -107,7 +112,7 @@ export abstract class Node {
107112
return obj.toPlaceholder();
108113
}
109114

110-
if (typeof obj === "object") {
115+
if (obj && typeof obj === "object") {
111116
return Object.keys(obj).reduce((acc: any, k: any) => {
112117
acc[k] = withPlaceholders(obj[k]);
113118
return acc;
@@ -138,6 +143,16 @@ export abstract class Node {
138143

139144
nodes.add(this);
140145

146+
for (let node of this.depends) {
147+
const references = node.references();
148+
for (let node of references.nodes) {
149+
nodes.add(node);
150+
}
151+
for (let future of references.futures) {
152+
futures.add(future);
153+
}
154+
}
155+
141156
const collectFutures = (obj: any) => {
142157
if (Array.isArray(obj)) {
143158
for (let item of obj) {
@@ -155,7 +170,7 @@ export abstract class Node {
155170
return;
156171
}
157172

158-
if (typeof obj === "object") {
173+
if (obj && typeof obj === "object") {
159174
for (let key of Object.keys(obj)) {
160175
collectFutures(obj[key]);
161176
}

src/Substrate.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,21 @@ export class Substrate {
198198
}
199199
}
200200

201+
const allEdges: Record<string, Set<string>> = {};
202+
for (let n of allNodes) {
203+
allEdges[n.id] = new Set<string>();
204+
for (let d of n.depends) {
205+
allEdges[n.id]!.add(d.id);
206+
}
207+
}
208+
201209
return {
202210
nodes: Array.from(allNodes).map((node) => node.toJSON()),
203211
futures: Array.from(allFutures).map((future) => future.toJSON()),
204-
edges: [], // @deprecated
212+
edges: Object.keys(allEdges).flatMap((toId: string) => {
213+
let fromIds: string[] = Array.from(allEdges[toId] as Set<string>);
214+
return fromIds.map((fromId: string) => [fromId, toId, {}]);
215+
}),
205216
initial_args: {}, // @deprecated
206217
};
207218
}

tests/Node.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,18 @@ describe("Node", () => {
3131
});
3232

3333
test(".references", () => {
34-
const a = new FooNode({ x: "x" });
34+
const a = new FooNode({ x: "x" }, { id: "a" });
3535
const f1 = a.future.get("x");
3636
const f2 = a.future.get("y");
37-
const b = new FooNode({ x: f1, z: f2 });
37+
const b = new FooNode({ x: f1, z: f2 }, { id: "b" });
3838
const f3 = b.future.get("x");
39-
const c = new FooNode({ x: f3 });
39+
const c = new FooNode({ x: f3 }, { id: "c" });
40+
const d = new FooNode({}, { id: "d", depends: [c] });
4041

4142
// @ts-ignore (protected)
42-
const { nodes, futures } = c.references();
43+
const { nodes, futures } = d.references();
4344

44-
expect(nodes).toEqual(new Set([a, b, c]));
45+
expect(nodes).toEqual(new Set([a, b, c, d]));
4546
expect(futures).toEqual(new Set([f1, f2, f3]));
4647
});
4748
});

tests/Substrate.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,5 +144,49 @@ describe("Substrate", () => {
144144
],
145145
});
146146
});
147+
148+
test("when there are nodes and we use the `depends` key", () => {
149+
const a = new FooNode({ a: 123 }, { id: "a" });
150+
const b = new FooNode({ b: 456 }, { id: "b", depends: [a] });
151+
const c = new FooNode({ c: 789 }, { id: "c", depends: [a, b, b] }); // intentionally using b twice here
152+
153+
const result = Substrate.serialize(a, b, c);
154+
155+
expect(result).toEqual({
156+
edges: [
157+
["a", "b", {}],
158+
["a", "c", {}],
159+
["b", "c", {}],
160+
],
161+
initial_args: {},
162+
nodes: [
163+
{
164+
node: "FooNode",
165+
id: a.id,
166+
args: {
167+
a: 123,
168+
},
169+
_should_output_globally: true,
170+
},
171+
{
172+
node: "FooNode",
173+
id: b.id,
174+
args: {
175+
b: 456,
176+
},
177+
_should_output_globally: true,
178+
},
179+
{
180+
node: "FooNode",
181+
id: c.id,
182+
args: {
183+
c: 789,
184+
},
185+
_should_output_globally: true,
186+
},
187+
],
188+
futures: [],
189+
});
190+
});
147191
});
148192
});

0 commit comments

Comments
 (0)