spanner-graph-notebook 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. assets/authentication.png +0 -0
  2. assets/full_viz.png +0 -0
  3. assets/hero.png +0 -0
  4. assets/hero_with_properties.png +0 -0
  5. assets/jupyter-spin-up.png +0 -0
  6. assets/load_ext.png +0 -0
  7. assets/mock_data_result.png +0 -0
  8. assets/new_notebook.png +0 -0
  9. assets/notebook_package_load.png +0 -0
  10. assets/query_graph.png +0 -0
  11. assets/sample_jupyter.png +0 -0
  12. spanner_graph_notebook-1.0.0.dist-info/LICENSE +202 -0
  13. spanner_graph_notebook-1.0.0.dist-info/METADATA +169 -0
  14. spanner_graph_notebook-1.0.0.dist-info/RECORD +40 -0
  15. spanner_graph_notebook-1.0.0.dist-info/WHEEL +5 -0
  16. spanner_graph_notebook-1.0.0.dist-info/top_level.txt +2 -0
  17. spanner_graphs/__init__.py +19 -0
  18. spanner_graphs/conversion.py +299 -0
  19. spanner_graphs/database.py +199 -0
  20. spanner_graphs/graph_entities.py +405 -0
  21. spanner_graphs/graph_mock_data.csv +51 -0
  22. spanner_graphs/graph_mock_schema.json +681 -0
  23. spanner_graphs/magics.py +211 -0
  24. spanner_graphs/schema_manager.py +60 -0
  25. templates/assets/images/graph-bg.svg +4 -0
  26. templates/spanner-graph/models/edge.js +77 -0
  27. templates/spanner-graph/models/graph-object.js +64 -0
  28. templates/spanner-graph/models/node.js +77 -0
  29. templates/spanner-graph/models/schema.js +327 -0
  30. templates/spanner-graph/spanner-config.js +304 -0
  31. templates/spanner-graph/spanner-store.js +382 -0
  32. templates/spanner-graph/visualization/spanner-forcegraph.js +1380 -0
  33. templates/spanner-graph/visualization/spanner-sidebar.js +904 -0
  34. templates/template-spannergraph.html +210 -0
  35. tests/__init__.py +13 -0
  36. tests/conversion_test.py +163 -0
  37. tests/database_test.py +62 -0
  38. tests/graph_entities_test.py +124 -0
  39. tests/schema_manager_test.py +115 -0
  40. tests/test_notebook.json +23 -0
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Represents the schema of the data.
3
+ * @class
4
+ */
5
+ class Schema {
6
+ /**
7
+ * @typedef {Object} PropertyDefinition
8
+ * @property {string} propertyDeclarationName
9
+ * @property {string} valueExpressionSql
10
+ * @property {string} nodeTableName
11
+ */
12
+
13
+ /**
14
+ * @typedef {Object} EdgeDestinationNode
15
+ * @property {Array<string>} edgeTableColumns
16
+ * @property {Array<string>} nodeTableColumns
17
+ * @property {string} nodeTableName
18
+ */
19
+
20
+ /**
21
+ * @typedef {Object} EdgeTable
22
+ * @property {string} baseCatalogName
23
+ * @property {string} baseSchemaName
24
+ * @property {string} baseTableName
25
+ * @property {EdgeDestinationNode} destinationNodeTable
26
+ * @property {Array<string>} keyColumns
27
+ * @property {string} kind
28
+ * @property {Array<string>} labelNames
29
+ * @property {string} name
30
+ * @property {Array<PropertyDefinition>} propertyDefinitions
31
+ * @property {EdgeDestinationNode} sourceNodeTable
32
+ */
33
+
34
+ /**
35
+ * @typedef {Object} NodeTable
36
+ * @property {string} baseCatalogName
37
+ * @property {string} baseSchemaName
38
+ * @property {string} baseTableName
39
+ * @property {Array<string>} keyColumns
40
+ * @property {string} kind
41
+ * @property {Array<string>} labelNames
42
+ * @property {string} name
43
+ * @property {Array<PropertyDefinition>} propertyDefinitions
44
+ */
45
+
46
+ /**
47
+ * @typedef PropertyDeclarationType
48
+ * @param {'INT64'|'STRING'|'FLOAT64'|'TIMESTAMP'|'BOOL'}
49
+ */
50
+
51
+ /**
52
+ * @type {PropertyDeclarationType}
53
+ */
54
+ propertyDeclarationTypes = [
55
+ 'INT64', 'STRING', 'FLOAT64', 'TIMESTAMP', 'BOOL'
56
+ ];
57
+
58
+ /**
59
+ * @typedef PropertyDeclaration
60
+ * @param {string} name
61
+ * @param {PropertyDeclarationType} type
62
+ */
63
+
64
+ /**
65
+ * @typedef {Object} RawSchema The raw schema object returned from Cloud Spanner
66
+ * @property {string} catalog
67
+ * @property {Array<EdgeTable>} edgeTables
68
+ * @property {number} labels
69
+ * @property {string} name
70
+ * @property {Array<NodeTable>} nodeTables
71
+ * @property {Array<PropertyDeclaration>} propertyDeclarations
72
+ * @property {string} schema
73
+ */
74
+
75
+ /**
76
+ * @type {RawSchema}
77
+ */
78
+ rawSchema;
79
+
80
+ /**
81
+ * @param {RawSchema} rawSchemaObject
82
+ */
83
+ constructor(rawSchemaObject) {
84
+ this.rawSchema = rawSchemaObject;
85
+ }
86
+
87
+ /**
88
+ * @param {Array<EdgeTable|NodeTable>} tables
89
+ * @returns {Array<string>}
90
+ */
91
+ getNamesOfTables(tables) {
92
+ const names = {};
93
+
94
+ if (!this.rawSchema) {
95
+ console.error('No schema found');
96
+ return [];
97
+ }
98
+
99
+ for (let i = 0; i < tables.length; i++) {
100
+ const table = tables[i];
101
+
102
+ if (!table.name) {
103
+ console.error('name of nodeTable is not declared');
104
+ continue;
105
+ }
106
+
107
+ if (typeof table.name != 'string') {
108
+ console.error('name of nodeTable is not a string');
109
+ continue;
110
+ }
111
+
112
+ names[table.name] = '';
113
+ }
114
+
115
+ return Object.keys(names);
116
+ }
117
+
118
+ /**
119
+ * We are only returning the first label as a stopgap
120
+ * until the Spanner Backend settles on a solution.
121
+ * @param {Array<EdgeTable|NodeTable>} tables
122
+ * @returns {Array<string>}
123
+ */
124
+ getUniqueLabels(tables) {
125
+ /**
126
+ * @type {Array<string>}
127
+ */
128
+ const labels = [];
129
+
130
+ for (let i = 0; i < tables.length; i++) {
131
+ const table = tables[i];
132
+ if (!(table instanceof Object) ||
133
+ !(table.labelNames instanceof Array) ||
134
+ table.labelNames.length === 0) {
135
+ continue;
136
+ }
137
+
138
+ labels.push(tables[i].labelNames[0]);
139
+ }
140
+
141
+ return labels;
142
+ }
143
+
144
+ /**
145
+ * @returns {Array<string>}
146
+ */
147
+ getNodeNames() {
148
+ return this.getUniqueLabels(this.rawSchema.nodeTables);
149
+ }
150
+
151
+ /**
152
+ * @returns {Array<string>}
153
+ */
154
+ getEdgeNames() {
155
+ return this.getUniqueLabels(this.rawSchema.edgeTables);
156
+ }
157
+
158
+ /**
159
+ * @returns {{nodes: Array<string>, edges: Array<string>}}
160
+ */
161
+ getTableNames() {
162
+ return {
163
+ edges: this.getEdgeNames(),
164
+ nodes: this.getNodeNames()
165
+ };
166
+ }
167
+
168
+ /**
169
+ * @param {EdgeTable|NodeTable} table
170
+ * @returns {{name: string, type: PropertyDeclarationType}} The keys are the property names, and the values are the value types (int, float, etc.)
171
+ */
172
+ getPropertiesOfTable(table){
173
+ const properties = {}
174
+
175
+ const getPropertyType = (name) => {
176
+ for (let j = 0; j < this.rawSchema.propertyDeclarations.length; j++) {
177
+ const declaration = this.rawSchema.propertyDeclarations[j];
178
+ if (declaration.name === name) {
179
+ return declaration.type;
180
+ }
181
+ }
182
+ }
183
+
184
+ for (let i = 0; i < table.propertyDefinitions.length; i++) {
185
+ const propertyDefinition = table.propertyDefinitions[i];
186
+
187
+ const propertyType = getPropertyType(propertyDefinition.propertyDeclarationName);
188
+
189
+ if (!propertyType) {
190
+ console.error(`Property Declaration does not contain Property Definition: ${propertyDefinition.propertyDeclarationName}`);
191
+ continue;
192
+ }
193
+
194
+ properties[propertyDefinition.propertyDeclarationName] = propertyType;
195
+ }
196
+
197
+ return properties;
198
+ }
199
+
200
+ /**
201
+ * @param {NodeTable} nodeTable
202
+ * @returns {Array<EdgeTable>} Edges
203
+ */
204
+ getEdgesOfNode(nodeTable){
205
+ return this.rawSchema.edgeTables.filter(edgeTable =>
206
+ edgeTable.sourceNodeTable.nodeTableName === nodeTable.name ||
207
+ edgeTable.destinationNodeTable.nodeTableName === nodeTable.name);
208
+ }
209
+
210
+ /**
211
+ * @param edgeTable
212
+ * @returns {{
213
+ * to: NodeTable
214
+ * from: NodeTable
215
+ * }}
216
+ */
217
+ getNodesOfEdges(edgeTable) {
218
+ /**
219
+ * @type {{to: {nodeTable}, from: {nodeTable}}}
220
+ */
221
+ const nodes = {};
222
+ for (let i = 0; i < this.rawSchema.nodeTables.length; i++) {
223
+ const nodeTable = this.rawSchema.nodeTables[i];
224
+ if (edgeTable.sourceNodeTable.nodeTableName === nodeTable.name) {
225
+ nodes.from = nodeTable;
226
+ }
227
+
228
+ if (edgeTable.destinationNodeTable.nodeTableName === nodeTable.name) {
229
+ nodes.to = nodeTable;
230
+ }
231
+
232
+ if (nodes.from && nodes.to) {
233
+ break;
234
+ }
235
+ }
236
+
237
+ if (!nodes.to || !nodes.from) {
238
+ console.error('EdgeTable does not have a source or destination node', edgeTable);
239
+ }
240
+
241
+ return nodes;
242
+ }
243
+
244
+ /**
245
+ * @param {String} name
246
+ * @returns {EdgeTable}
247
+ */
248
+ getEdgeFromName(name) {
249
+ const edges = this.rawSchema.edgeTables.filter(edgeTable =>
250
+ edgeTable.name === name);
251
+
252
+ if (edges.length > 0) {
253
+ return edges[0];
254
+ }
255
+
256
+ console.error(`No edgeTable associated with name ${name}`);
257
+ }
258
+
259
+ /**
260
+ * @param {String} name
261
+ * @returns {NodeTable}
262
+ */
263
+ getNodeFromName(name) {
264
+ const nodes = this.rawSchema.nodeTables.filter(nodeTable =>
265
+ nodeTable.name === name);
266
+
267
+ if (nodes.length > 0) {
268
+ return nodes[0];
269
+ }
270
+
271
+ console.error(`No nodeTable associated with name ${name}`);
272
+ }
273
+
274
+ /**
275
+ * @param {EdgeTable} edgeTable
276
+ * @returns {number}
277
+ */
278
+ getEdgeTableId(edgeTable) {
279
+ return this.rawSchema.edgeTables.indexOf(edgeTable);
280
+ }
281
+
282
+ /**
283
+ * @param {NodeTable} nodeTable
284
+ * @returns {number}
285
+ */
286
+ getNodeTableId(nodeTable) {
287
+ return this.rawSchema.nodeTables.indexOf(nodeTable);
288
+ }
289
+
290
+ /**
291
+ * @param {String} nodeName
292
+ * @param {String} edgeName
293
+ * @returns {{isConnected: Boolean, isSource: Boolean}}
294
+ */
295
+ nodeIsConnectedToEdge(nodeName, edgeName) {
296
+ const connection = {
297
+ isConnected: false,
298
+ isSource: false
299
+ };
300
+
301
+ const node = this.getNodeFromName(nodeName);
302
+ if (!node) {
303
+ console.error(`No node found from name ${nodeName}`);
304
+ return connection;
305
+ }
306
+
307
+ const edge = this.getEdgeFromName(edgeName);
308
+ if (!edge) {
309
+ console.error(`No edge found from name ${edgeName}`);
310
+ return connection;
311
+ }
312
+
313
+ connection.isConnected = true;
314
+ connection.isSource = edge.sourceNodeTable.nodeTableName === node.name
315
+ return connection;
316
+ }
317
+
318
+ /**
319
+ * @param {NodeTable|EdgeTable} table
320
+ * @return {string}
321
+ */
322
+ getDisplayName(table) {
323
+ return table.labelNames[0];
324
+ }
325
+ }
326
+
327
+ window[namespace].Schema = Schema;
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Copyright 2024 Google LLC
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+
16
+ class GraphConfig {
17
+
18
+ /**
19
+ * The array of node objects to be rendered. 123123123
20
+ * @type {Schema}
21
+ */
22
+ schema = null;
23
+
24
+ /**
25
+ * The array of node objects to be rendered.
26
+ * @type {Array<Node>}
27
+ */
28
+ schemaNodes = [];
29
+
30
+ /**
31
+ * The array of edge objects to be rendered.
32
+ * @type {Array<Edge>}
33
+ */
34
+ schemaEdges = [];
35
+
36
+ /**
37
+ * The array of node objects to be rendered.
38
+ * @type {Array<Node>}
39
+ */
40
+ nodes = [];
41
+
42
+ /**
43
+ * The array of edge objects to be rendered.
44
+ * @type {Array<Edge>}
45
+ */
46
+ edges = [];
47
+
48
+ /**
49
+ * Raw data of rows from Spanner Graph
50
+ * @type {Array<any>}
51
+ */
52
+ rowsData = [];
53
+
54
+ /**
55
+ * The currently focused GraphObject. This is usually the
56
+ * node or edge that the user is hovering their mouse over.
57
+ * @type {GraphObject}
58
+ * @default null
59
+ */
60
+ focusedGraphObject = null;
61
+
62
+ /**
63
+ * The currently selected GraphObject. This is usually
64
+ * the node or edge that the user has clicked on.
65
+ * @type {GraphObject}
66
+ * @default null
67
+ */
68
+ selectedGraphObject = null;
69
+
70
+ /**
71
+ * The color scheme to use for nodes.
72
+ * @type {GraphConfig.ColorScheme}
73
+ * @default GraphConfig.ColorScheme.NEIGHBORHOOD
74
+ */
75
+ colorScheme = GraphConfig.ColorScheme.NEIGHBORHOOD;
76
+
77
+ colorPalette = [
78
+ '#1A73E8', '#E52592', '#12A4AF', '#F4511E',
79
+ '#9334E6', '#689F38', '#3949AB', '#546E7A',
80
+ '#EF6C00', '#D93025', '#1E8E3E', '#039BE5'
81
+ ];
82
+
83
+ // [label: string]: colorString
84
+ nodeColors = {};
85
+ // [label: string]: colorString
86
+ schemaNodeColors = {};
87
+
88
+ edgeDesign = {
89
+ default: {
90
+ color: '#DADCE0',
91
+ width: 2,
92
+ shadowWidth: 0,
93
+ shadowColor: '#000000'
94
+ },
95
+ focused: {
96
+ color: '#80868B',
97
+ width: 4,
98
+ shadowWidth: 6,
99
+ shadowColor: '#E8EAED'
100
+ },
101
+ selected: {
102
+ color: '#1A73E8',
103
+ width: 4,
104
+ shadowWidth: 8,
105
+ shadowColor: 'rgba(26, 115, 232, 0.25)'
106
+ }
107
+ };
108
+
109
+ static ColorScheme = Object.freeze({
110
+ NEIGHBORHOOD: Symbol('neighborhood'),
111
+ LABEL: Symbol('label')
112
+ });
113
+
114
+ /**
115
+ * Constructs a new GraphConfig instance.
116
+ * @constructor
117
+ * @param {Object} config - The configuration object.
118
+ * @param {Array} config.nodesData - An array of data objects for nodes.
119
+ * @param {Array} config.edgesData - An array of data objects for edges.
120
+ * @param {Array} [config.colorPalette] - An optional array of colors to use as the color palette.
121
+ * @param {GraphConfig.ColorScheme} [config.colorScheme] - Color scheme can be optionally declared.
122
+ * @param {Array} [config.rowsData] - Raw row data from Spanner
123
+ * @param {RawSchema} config.schemaData - Raw schema data from Spanner
124
+ */
125
+ constructor({ nodesData, edgesData, colorPalette, colorScheme, rowsData, schemaData}) {
126
+ this.nodes = this.parseNodes(nodesData);
127
+ this.edges = this.parseEdges(edgesData);
128
+ this.nodeColors = this.assignColors(this.nodes);
129
+ this.parseSchema(schemaData);
130
+
131
+ if (colorPalette && Array.isArray(colorPalette)) {
132
+ this.colorPalette = colorPalette;
133
+ }
134
+
135
+ if (colorScheme) {
136
+ this.colorScheme = colorScheme;
137
+ }
138
+
139
+ this.rowsData = rowsData;
140
+ }
141
+
142
+ /**
143
+ * @param nodes
144
+ * @returns {{}} Color map by the node's label
145
+ */
146
+ assignColors(nodes) {
147
+ const colors = {};
148
+ const colorPalette = this.colorPalette.map(color => color);
149
+
150
+ if (!nodes || !nodes instanceof Array) {
151
+ console.error('Nodes must be array', nodes);
152
+ throw Error('Nodes must be an array');
153
+ }
154
+
155
+ nodes.forEach(node => {
156
+ if (colorPalette.length === 0) {
157
+ console.error('Node labels exceed the color palette. Assigning default color.');
158
+ return;
159
+ }
160
+
161
+ if (!node || !node instanceof Node) {
162
+ console.error('Object is not an instance of Node', node);
163
+ return;
164
+ }
165
+
166
+ const label = node.label;
167
+ if (!label || !label instanceof String) {
168
+ console.error('Node does not have a label', node);
169
+ return;
170
+ }
171
+
172
+ if (!colors[label]) {
173
+ colors[label] = colorPalette.shift();
174
+ }
175
+ });
176
+
177
+ return colors;
178
+ }
179
+
180
+ /**
181
+ * Parses schema data into nodes and edges
182
+ * @param {RawSchema} schemaData - The raw data representing a schema
183
+ * @throws {Error} Throws an error if the schema data can not be parsed
184
+ */
185
+ parseSchema(schemaData) {
186
+ this.schema = new Schema(schemaData);
187
+
188
+ const nodesData = this.schema.rawSchema.nodeTables.map(
189
+ /**
190
+ * @param {NodeTable} nodeTable
191
+ * @returns {NodeData}
192
+ */
193
+ (nodeTable, i) => {
194
+ const name = this.schema.getDisplayName(nodeTable)
195
+
196
+ /**
197
+ * @type {NodeData}
198
+ */
199
+ return {
200
+ label: name,
201
+ properties: this.schema.getPropertiesOfTable(nodeTable),
202
+ color: 'rgb(0, 0, 100)', // this isn't used
203
+ key_property_names: ['id'],
204
+ id: this.schema.getNodeTableId(nodeTable)
205
+ };
206
+ }
207
+ );
208
+ this.schemaNodes = this.parseNodes(nodesData);
209
+
210
+ const edgesData = this.schema.rawSchema.edgeTables.map(
211
+ /**
212
+ * @param {EdgeTable} edgeTable
213
+ * @returns {EdgeData}
214
+ */
215
+ (edgeTable, i) => {
216
+ const connectedNodes = this.schema.getNodesOfEdges(edgeTable);
217
+ const name = this.schema.getDisplayName(edgeTable)
218
+
219
+ /**
220
+ * @type {EdgeData}
221
+ */
222
+ return {
223
+ label: name,
224
+ properties: this.schema.getPropertiesOfTable(edgeTable),
225
+ color: 'rgb(0, 0, 100)', // this isn't used
226
+ to: this.schema.getNodeTableId(connectedNodes.to),
227
+ from: this.schema.getNodeTableId(connectedNodes.from),
228
+ key_property_names: ['id'],
229
+ id: this.schema.getEdgeTableId(edgeTable)
230
+ };
231
+ });
232
+ this.schemaEdges = this.parseEdges(edgesData);
233
+ this.schemaNodeColors = this.assignColors(this.schemaNodes);
234
+ }
235
+
236
+ /**
237
+ * Parses an array of node data, instantiates nodes, and adds them to the graph.
238
+ * @param {Array<NodeData>} nodesData - An array of objects representing the data for each node.
239
+ * @throws {Error} Throws an error if `nodesData` is not an array.
240
+ */
241
+ parseNodes(nodesData) {
242
+ if (!Array.isArray(nodesData)) {
243
+ console.error('Nodes must be an array', nodesData)
244
+ throw Error('Nodes must be an array');
245
+ }
246
+
247
+ /** @type {Node[]} */
248
+ const nodes = []
249
+ nodesData.forEach(nodeData => {
250
+ if (!(nodeData instanceof Object)) {
251
+ console.error('Node data is not an object', nodeData);
252
+ return;
253
+ }
254
+
255
+ // Try to create a Node
256
+ const node = new Node(nodeData);
257
+ if (!node || !node.instantiated) {
258
+ console.error('Unable to instantiate node', node.instantiationErrorReason);
259
+ return;
260
+ }
261
+ if (node instanceof Node) {
262
+ nodes.push(node);
263
+ } else {
264
+ node.instantiationErrorReason = 'Could not construct an instance of Node';
265
+ console.error(node.instantiationErrorReason, { nodeData, node });
266
+ }
267
+ });
268
+
269
+ return nodes;
270
+ }
271
+
272
+ parseEdges(edgesData) {
273
+ if (!Array.isArray(edgesData)) {
274
+ console.error('Edges must be an array', edgesData)
275
+ throw Error('Edges must be an array');
276
+ }
277
+
278
+ /** @type {Edge[]} */
279
+ const edges = []
280
+ edgesData.forEach(edgeData => {
281
+ if (!(edgeData instanceof Object)) {
282
+ console.error('Edge data is not an object', edgeData);
283
+ return;
284
+ }
285
+
286
+ // Try to create an Edge
287
+ const edge = new Edge(edgeData);
288
+ if (!edge || !edge.instantiated) {
289
+ console.error('Unable to instantiate edge', edge.instantiationErrorReason);
290
+ return;
291
+ }
292
+ if (edge instanceof Edge) {
293
+ edges.push(edge);
294
+ } else {
295
+ edge.instantiationErrorReason = 'Could not construct an instance of Edge';
296
+ console.error(edge.instantiationErrorReason, { edgeData, edge });
297
+ }
298
+ });
299
+
300
+ return edges;
301
+ }
302
+ }
303
+
304
+ window[namespace].GraphConfig = GraphConfig;