real-ladybug 0.0.1.dev1__cp311-cp311-win_amd64.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.

Potentially problematic release.


This version of real-ladybug might be problematic. Click here for more details.

Files changed (114) hide show
  1. real_ladybug/__init__.py +83 -0
  2. real_ladybug/_lbug.cp311-win_amd64.pyd +0 -0
  3. real_ladybug/_lbug.exp +0 -0
  4. real_ladybug/_lbug.lib +0 -0
  5. real_ladybug/async_connection.py +226 -0
  6. real_ladybug/connection.py +323 -0
  7. real_ladybug/constants.py +7 -0
  8. real_ladybug/database.py +307 -0
  9. real_ladybug/prepared_statement.py +51 -0
  10. real_ladybug/py.typed +0 -0
  11. real_ladybug/query_result.py +511 -0
  12. real_ladybug/torch_geometric_feature_store.py +185 -0
  13. real_ladybug/torch_geometric_graph_store.py +131 -0
  14. real_ladybug/torch_geometric_result_converter.py +282 -0
  15. real_ladybug/types.py +39 -0
  16. real_ladybug-0.0.1.dev1.dist-info/METADATA +88 -0
  17. real_ladybug-0.0.1.dev1.dist-info/RECORD +114 -0
  18. real_ladybug-0.0.1.dev1.dist-info/WHEEL +5 -0
  19. real_ladybug-0.0.1.dev1.dist-info/licenses/LICENSE +21 -0
  20. real_ladybug-0.0.1.dev1.dist-info/top_level.txt +3 -0
  21. real_ladybug-0.0.1.dev1.dist-info/zip-safe +1 -0
  22. real_ladybug-source/scripts/antlr4/hash.py +2 -0
  23. real_ladybug-source/scripts/antlr4/keywordhandler.py +47 -0
  24. real_ladybug-source/scripts/collect-extensions.py +68 -0
  25. real_ladybug-source/scripts/collect-single-file-header.py +126 -0
  26. real_ladybug-source/scripts/export-dbs.py +101 -0
  27. real_ladybug-source/scripts/export-import-test.py +345 -0
  28. real_ladybug-source/scripts/extension/purge-beta.py +34 -0
  29. real_ladybug-source/scripts/generate-cpp-docs/collect_files.py +122 -0
  30. real_ladybug-source/scripts/generate-tinysnb.py +34 -0
  31. real_ladybug-source/scripts/get-clangd-diagnostics.py +233 -0
  32. real_ladybug-source/scripts/migrate-lbug-db.py +308 -0
  33. real_ladybug-source/scripts/multiplatform-test-helper/collect-results.py +71 -0
  34. real_ladybug-source/scripts/multiplatform-test-helper/notify-discord.py +68 -0
  35. real_ladybug-source/scripts/pip-package/package_tar.py +90 -0
  36. real_ladybug-source/scripts/pip-package/setup.py +130 -0
  37. real_ladybug-source/scripts/run-clang-format.py +408 -0
  38. real_ladybug-source/scripts/setup-extension-repo.py +67 -0
  39. real_ladybug-source/scripts/test-simsimd-dispatch.py +45 -0
  40. real_ladybug-source/scripts/update-nightly-build-version.py +81 -0
  41. real_ladybug-source/third_party/brotli/scripts/dictionary/step-01-download-rfc.py +16 -0
  42. real_ladybug-source/third_party/brotli/scripts/dictionary/step-02-rfc-to-bin.py +34 -0
  43. real_ladybug-source/third_party/brotli/scripts/dictionary/step-03-validate-bin.py +35 -0
  44. real_ladybug-source/third_party/brotli/scripts/dictionary/step-04-generate-java-literals.py +85 -0
  45. real_ladybug-source/third_party/pybind11/tools/codespell_ignore_lines_from_errors.py +35 -0
  46. real_ladybug-source/third_party/pybind11/tools/libsize.py +36 -0
  47. real_ladybug-source/third_party/pybind11/tools/make_changelog.py +63 -0
  48. real_ladybug-source/tools/python_api/build/real_ladybug/__init__.py +83 -0
  49. real_ladybug-source/tools/python_api/build/real_ladybug/async_connection.py +226 -0
  50. real_ladybug-source/tools/python_api/build/real_ladybug/connection.py +323 -0
  51. real_ladybug-source/tools/python_api/build/real_ladybug/constants.py +7 -0
  52. real_ladybug-source/tools/python_api/build/real_ladybug/database.py +307 -0
  53. real_ladybug-source/tools/python_api/build/real_ladybug/prepared_statement.py +51 -0
  54. real_ladybug-source/tools/python_api/build/real_ladybug/py.typed +0 -0
  55. real_ladybug-source/tools/python_api/build/real_ladybug/query_result.py +511 -0
  56. real_ladybug-source/tools/python_api/build/real_ladybug/torch_geometric_feature_store.py +185 -0
  57. real_ladybug-source/tools/python_api/build/real_ladybug/torch_geometric_graph_store.py +131 -0
  58. real_ladybug-source/tools/python_api/build/real_ladybug/torch_geometric_result_converter.py +282 -0
  59. real_ladybug-source/tools/python_api/build/real_ladybug/types.py +39 -0
  60. real_ladybug-source/tools/python_api/src_py/__init__.py +83 -0
  61. real_ladybug-source/tools/python_api/src_py/async_connection.py +226 -0
  62. real_ladybug-source/tools/python_api/src_py/connection.py +323 -0
  63. real_ladybug-source/tools/python_api/src_py/constants.py +7 -0
  64. real_ladybug-source/tools/python_api/src_py/database.py +307 -0
  65. real_ladybug-source/tools/python_api/src_py/prepared_statement.py +51 -0
  66. real_ladybug-source/tools/python_api/src_py/py.typed +0 -0
  67. real_ladybug-source/tools/python_api/src_py/query_result.py +511 -0
  68. real_ladybug-source/tools/python_api/src_py/torch_geometric_feature_store.py +185 -0
  69. real_ladybug-source/tools/python_api/src_py/torch_geometric_graph_store.py +131 -0
  70. real_ladybug-source/tools/python_api/src_py/torch_geometric_result_converter.py +282 -0
  71. real_ladybug-source/tools/python_api/src_py/types.py +39 -0
  72. real_ladybug-source/tools/python_api/test/conftest.py +230 -0
  73. real_ladybug-source/tools/python_api/test/disabled_test_extension.py +73 -0
  74. real_ladybug-source/tools/python_api/test/ground_truth.py +430 -0
  75. real_ladybug-source/tools/python_api/test/test_arrow.py +694 -0
  76. real_ladybug-source/tools/python_api/test/test_async_connection.py +159 -0
  77. real_ladybug-source/tools/python_api/test/test_blob_parameter.py +145 -0
  78. real_ladybug-source/tools/python_api/test/test_connection.py +49 -0
  79. real_ladybug-source/tools/python_api/test/test_database.py +234 -0
  80. real_ladybug-source/tools/python_api/test/test_datatype.py +372 -0
  81. real_ladybug-source/tools/python_api/test/test_df.py +564 -0
  82. real_ladybug-source/tools/python_api/test/test_dict.py +112 -0
  83. real_ladybug-source/tools/python_api/test/test_exception.py +54 -0
  84. real_ladybug-source/tools/python_api/test/test_fsm.py +227 -0
  85. real_ladybug-source/tools/python_api/test/test_get_header.py +49 -0
  86. real_ladybug-source/tools/python_api/test/test_helper.py +8 -0
  87. real_ladybug-source/tools/python_api/test/test_issue.py +147 -0
  88. real_ladybug-source/tools/python_api/test/test_iteration.py +96 -0
  89. real_ladybug-source/tools/python_api/test/test_networkx.py +437 -0
  90. real_ladybug-source/tools/python_api/test/test_parameter.py +340 -0
  91. real_ladybug-source/tools/python_api/test/test_prepared_statement.py +117 -0
  92. real_ladybug-source/tools/python_api/test/test_query_result.py +54 -0
  93. real_ladybug-source/tools/python_api/test/test_query_result_close.py +44 -0
  94. real_ladybug-source/tools/python_api/test/test_scan_pandas.py +676 -0
  95. real_ladybug-source/tools/python_api/test/test_scan_pandas_pyarrow.py +714 -0
  96. real_ladybug-source/tools/python_api/test/test_scan_polars.py +165 -0
  97. real_ladybug-source/tools/python_api/test/test_scan_pyarrow.py +167 -0
  98. real_ladybug-source/tools/python_api/test/test_timeout.py +11 -0
  99. real_ladybug-source/tools/python_api/test/test_torch_geometric.py +640 -0
  100. real_ladybug-source/tools/python_api/test/test_torch_geometric_remote_backend.py +111 -0
  101. real_ladybug-source/tools/python_api/test/test_udf.py +207 -0
  102. real_ladybug-source/tools/python_api/test/test_version.py +6 -0
  103. real_ladybug-source/tools/python_api/test/test_wal.py +80 -0
  104. real_ladybug-source/tools/python_api/test/type_aliases.py +10 -0
  105. real_ladybug-source/tools/rust_api/update_version.py +47 -0
  106. real_ladybug-source/tools/shell/test/conftest.py +218 -0
  107. real_ladybug-source/tools/shell/test/test_helper.py +60 -0
  108. real_ladybug-source/tools/shell/test/test_shell_basics.py +325 -0
  109. real_ladybug-source/tools/shell/test/test_shell_commands.py +656 -0
  110. real_ladybug-source/tools/shell/test/test_shell_control_edit.py +438 -0
  111. real_ladybug-source/tools/shell/test/test_shell_control_search.py +468 -0
  112. real_ladybug-source/tools/shell/test/test_shell_esc_edit.py +232 -0
  113. real_ladybug-source/tools/shell/test/test_shell_esc_search.py +162 -0
  114. real_ladybug-source/tools/shell/test/test_shell_flags.py +645 -0
@@ -0,0 +1,437 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from typing import Any
5
+
6
+ from pandas import Timedelta, Timestamp
7
+ from type_aliases import ConnDB
8
+ from real_ladybug.constants import LABEL
9
+
10
+
11
+ def test_to_networkx_node(conn_db_readonly: ConnDB) -> None:
12
+ conn, _ = conn_db_readonly
13
+ query = "MATCH (p:person) return p"
14
+
15
+ res = conn.execute(query)
16
+ nx_graph = res.get_as_networkx()
17
+ nodes = list(nx_graph.nodes(data=True))
18
+ assert len(nodes) == 8
19
+
20
+ ground_truth: dict[str, list[Any]] = {
21
+ "ID": [0, 2, 3, 5, 7, 8, 9, 10],
22
+ "fName": [
23
+ "Alice",
24
+ "Bob",
25
+ "Carol",
26
+ "Dan",
27
+ "Elizabeth",
28
+ "Farooq",
29
+ "Greg",
30
+ "Hubert Blaine Wolfeschlegelsteinhausenbergerdorff",
31
+ ],
32
+ "gender": [1, 2, 1, 2, 1, 2, 2, 2],
33
+ "isStudent": [True, True, False, False, False, True, False, False],
34
+ "eyeSight": [5.0, 5.1, 5.0, 4.8, 4.7, 4.5, 4.9, 4.9],
35
+ "birthdate": [
36
+ datetime.date(1900, 1, 1),
37
+ datetime.date(1900, 1, 1),
38
+ datetime.date(1940, 6, 22),
39
+ datetime.date(1950, 7, 23),
40
+ datetime.date(1980, 10, 26),
41
+ datetime.date(1980, 10, 26),
42
+ datetime.date(1980, 10, 26),
43
+ datetime.date(1990, 11, 27),
44
+ ],
45
+ "registerTime": [
46
+ Timestamp("2011-08-20 11:25:30"),
47
+ Timestamp("2008-11-03 15:25:30.000526"),
48
+ Timestamp("1911-08-20 02:32:21"),
49
+ Timestamp("2031-11-30 12:25:30"),
50
+ Timestamp("1976-12-23 11:21:42"),
51
+ Timestamp("1972-07-31 13:22:30.678559"),
52
+ Timestamp("1976-12-23 04:41:42"),
53
+ Timestamp("2023-02-21 13:25:30"),
54
+ ],
55
+ "lastJobDuration": [
56
+ Timedelta("1082 days 13:02:00"),
57
+ Timedelta("3750 days 13:00:00.000024"),
58
+ Timedelta("2 days 00:24:11"),
59
+ Timedelta("3750 days 13:00:00.000024"),
60
+ Timedelta("2 days 00:24:11"),
61
+ Timedelta("0 days 00:18:00.024000"),
62
+ Timedelta("3750 days 13:00:00.000024"),
63
+ Timedelta("1082 days 13:02:00"),
64
+ ],
65
+ "workedHours": [
66
+ [10, 5],
67
+ [12, 8],
68
+ [4, 5],
69
+ [1, 9],
70
+ [2],
71
+ [3, 4, 5, 6, 7],
72
+ [1],
73
+ [10, 11, 12, 3, 4, 5, 6, 7],
74
+ ],
75
+ "usedNames": [
76
+ ["Aida"],
77
+ ["Bobby"],
78
+ ["Carmen", "Fred"],
79
+ ["Wolfeschlegelstein", "Daniel"],
80
+ ["Ein"],
81
+ ["Fesdwe"],
82
+ ["Grad"],
83
+ ["Ad", "De", "Hi", "Kye", "Orlan"],
84
+ ],
85
+ "courseScoresPerTerm": [
86
+ [[10, 8], [6, 7, 8]],
87
+ [[8, 9], [9, 10]],
88
+ [[8, 10]],
89
+ [[7, 4], [8, 8], [9]],
90
+ [[6], [7], [8]],
91
+ [[8]],
92
+ [[10]],
93
+ [[7], [10], [6, 7]],
94
+ ],
95
+ "grades": [
96
+ [96, 54, 86, 92],
97
+ [98, 42, 93, 88],
98
+ [91, 75, 21, 95],
99
+ [76, 88, 99, 89],
100
+ [96, 59, 65, 88],
101
+ [80, 78, 34, 83],
102
+ [43, 83, 67, 43],
103
+ [77, 64, 100, 54],
104
+ ],
105
+ LABEL: [
106
+ "person",
107
+ "person",
108
+ "person",
109
+ "person",
110
+ "person",
111
+ "person",
112
+ "person",
113
+ "person",
114
+ ],
115
+ }
116
+ for i in range(len(nodes)):
117
+ node_id, node = nodes[i]
118
+ assert node_id == f"{node[LABEL]}_{node['ID']}"
119
+ for key in ground_truth:
120
+ assert node[key] == ground_truth[key][i]
121
+
122
+
123
+ def test_networkx_undirected(conn_db_readonly: ConnDB) -> None:
124
+ conn, _ = conn_db_readonly
125
+ res = conn.execute("MATCH (p1:person)-[r:knows]->(p2:person) WHERE p1.ID <= 3 RETURN p1, r, p2")
126
+
127
+ nx_graph = res.get_as_networkx(directed=False)
128
+ assert not nx_graph.is_directed()
129
+
130
+ nodes = list(nx_graph.nodes(data=True))
131
+ assert len(nodes) == 4
132
+
133
+ edges = list(nx_graph.edges(data=True))
134
+
135
+ ground_truth_p: dict[str, list[Any]] = {
136
+ "ID": [0, 2, 3, 5, 7, 8, 9, 10],
137
+ "fName": [
138
+ "Alice",
139
+ "Bob",
140
+ "Carol",
141
+ "Dan",
142
+ "Elizabeth",
143
+ "Farooq",
144
+ "Greg",
145
+ "Hubert Blaine Wolfeschlegelsteinhausenbergerdorff",
146
+ ],
147
+ "gender": [1, 2, 1, 2, 1, 2, 2, 2],
148
+ "isStudent": [True, True, False, False, False, True, False, False],
149
+ "eyeSight": [5.0, 5.1, 5.0, 4.8, 4.7, 4.5, 4.9, 4.9],
150
+ "birthdate": [
151
+ datetime.date(1900, 1, 1),
152
+ datetime.date(1900, 1, 1),
153
+ datetime.date(1940, 6, 22),
154
+ datetime.date(1950, 7, 23),
155
+ datetime.date(1980, 10, 26),
156
+ datetime.date(1980, 10, 26),
157
+ datetime.date(1980, 10, 26),
158
+ datetime.date(1990, 11, 27),
159
+ ],
160
+ "registerTime": [
161
+ Timestamp("2011-08-20 11:25:30"),
162
+ Timestamp("2008-11-03 15:25:30.000526"),
163
+ Timestamp("1911-08-20 02:32:21"),
164
+ Timestamp("2031-11-30 12:25:30"),
165
+ Timestamp("1976-12-23 11:21:42"),
166
+ Timestamp("1972-07-31 13:22:30.678559"),
167
+ Timestamp("1976-12-23 04:41:42"),
168
+ Timestamp("2023-02-21 13:25:30"),
169
+ ],
170
+ "lastJobDuration": [
171
+ Timedelta("1082 days 13:02:00"),
172
+ Timedelta("3750 days 13:00:00.000024"),
173
+ Timedelta("2 days 00:24:11"),
174
+ Timedelta("3750 days 13:00:00.000024"),
175
+ Timedelta("2 days 00:24:11"),
176
+ Timedelta("0 days 00:18:00.024000"),
177
+ Timedelta("3750 days 13:00:00.000024"),
178
+ Timedelta("1082 days 13:02:00"),
179
+ ],
180
+ "workedHours": [
181
+ [10, 5],
182
+ [12, 8],
183
+ [4, 5],
184
+ [1, 9],
185
+ [2],
186
+ [3, 4, 5, 6, 7],
187
+ [1],
188
+ [10, 11, 12, 3, 4, 5, 6, 7],
189
+ ],
190
+ "usedNames": [
191
+ ["Aida"],
192
+ ["Bobby"],
193
+ ["Carmen", "Fred"],
194
+ ["Wolfeschlegelstein", "Daniel"],
195
+ ["Ein"],
196
+ ["Fesdwe"],
197
+ ["Grad"],
198
+ ["Ad", "De", "Hi", "Kye", "Orlan"],
199
+ ],
200
+ "courseScoresPerTerm": [
201
+ [[10, 8], [6, 7, 8]],
202
+ [[8, 9], [9, 10]],
203
+ [[8, 10]],
204
+ [[7, 4], [8, 8], [9]],
205
+ [[6], [7], [8]],
206
+ [[8]],
207
+ [[10]],
208
+ [[7], [10], [6, 7]],
209
+ ],
210
+ "grades": [
211
+ [96, 54, 86, 92],
212
+ [98, 42, 93, 88],
213
+ [91, 75, 21, 95],
214
+ [76, 88, 99, 89],
215
+ [96, 59, 65, 88],
216
+ [80, 78, 34, 83],
217
+ [43, 83, 67, 43],
218
+ [77, 64, 100, 54],
219
+ ],
220
+ LABEL: [
221
+ "person",
222
+ "person",
223
+ "person",
224
+ "person",
225
+ "person",
226
+ "person",
227
+ "person",
228
+ "person",
229
+ ],
230
+ }
231
+ for node_id, node in nodes:
232
+ assert node_id == f"{node[LABEL]}_{node['ID']}"
233
+
234
+ for _, node in nodes:
235
+ found = False
236
+ for i in range(len(ground_truth_p["ID"])):
237
+ if node["ID"] != ground_truth_p["ID"][i]:
238
+ continue
239
+ found = True
240
+ for key in ground_truth_p:
241
+ assert node[key] == ground_truth_p[key][i]
242
+ assert found
243
+
244
+ assert len(edges) == 9
245
+ # This should be a complete graph, so we check if an edge exists between
246
+ # every pair of nodes and that there are no self-loops
247
+ for i in range(len(nodes)):
248
+ assert not nx_graph.has_edge(nodes[i][0], nodes[i][0])
249
+ for j in range(i + 1, len(nodes)):
250
+ assert nx_graph.has_edge(nodes[i][0], nodes[j][0])
251
+
252
+
253
+ def test_networkx_directed(conn_db_readonly: ConnDB) -> None:
254
+ conn, _ = conn_db_readonly
255
+ res = conn.execute("MATCH (p:person)-[r:workAt]->(o:organisation) RETURN p, r, o")
256
+
257
+ nx_graph = res.get_as_networkx(directed=True)
258
+ assert nx_graph.is_directed()
259
+
260
+ nodes = list(nx_graph.nodes(data=True))
261
+ assert len(nodes) == 5
262
+
263
+ edges = list(nx_graph.edges(data=True))
264
+
265
+ ground_truth_p: dict[str, list[Any]] = {
266
+ "ID": [3, 5, 7],
267
+ "fName": ["Carol", "Dan", "Elizabeth"],
268
+ "gender": [1, 2, 1],
269
+ "isStudent": [False, False, False],
270
+ "eyeSight": [5.0, 4.8, 4.7],
271
+ "birthdate": [
272
+ datetime.date(1940, 6, 22),
273
+ datetime.date(1950, 7, 23),
274
+ datetime.date(1980, 10, 26),
275
+ ],
276
+ "registerTime": [
277
+ Timestamp("1911-08-20 02:32:21"),
278
+ Timestamp("2031-11-30 12:25:30"),
279
+ Timestamp("1976-12-23 11:21:42"),
280
+ ],
281
+ "lastJobDuration": [
282
+ Timedelta("48 hours 24 minutes 11 seconds"),
283
+ Timedelta("3750 days 13:00:00.000024"),
284
+ Timedelta("2 days 00:24:11"),
285
+ ],
286
+ "workedHours": [[4, 5], [1, 9], [2]],
287
+ "usedNames": [["Carmen", "Fred"], ["Wolfeschlegelstein", "Daniel"], ["Ein"]],
288
+ "courseScoresPerTerm": [[[8, 10]], [[7, 4], [8, 8], [9]], [[6], [7], [8]]],
289
+ "grades": [[91, 75, 21, 95], [76, 88, 99, 89], [96, 59, 65, 88]],
290
+ LABEL: ["person", "person", "person"],
291
+ }
292
+
293
+ for node_id, node in nodes:
294
+ assert node_id == f"{node[LABEL]}_{node['ID']}"
295
+
296
+ for _, node in nodes:
297
+ if "person" not in node:
298
+ continue
299
+ found = False
300
+ for i in range(len(ground_truth_p["ID"])):
301
+ if node["ID"] != ground_truth_p["ID"][i]:
302
+ continue
303
+ found = True
304
+ for key in ground_truth_p:
305
+ assert node[key] == ground_truth_p[key][i]
306
+ assert found
307
+
308
+ ground_truth_o: dict[str, list[Any]] = {
309
+ "ID": [4, 6],
310
+ "name": ["CsWork", "DEsWork"],
311
+ "orgCode": [934, 824],
312
+ "mark": [4.1, 4.1],
313
+ "score": [-100, 7],
314
+ "history": ["2 years 4 days 10 hours", "2 years 4 hours 22 us 34 minutes"],
315
+ "licenseValidInterval": [
316
+ Timedelta(days=9414),
317
+ Timedelta(days=3, seconds=36000, microseconds=100000),
318
+ ],
319
+ "rating": [0.78, 0.52],
320
+ LABEL: ["organisation", "organisation"],
321
+ }
322
+
323
+ for _, node in nodes:
324
+ if "organisation" not in node:
325
+ continue
326
+ found = False
327
+ for i in range(len(ground_truth_o["ID"])):
328
+ if node["ID"] != ground_truth_o["ID"][i]:
329
+ continue
330
+ found = True
331
+ for key in ground_truth_o:
332
+ assert node[key] == ground_truth_o[key][i]
333
+ assert found
334
+
335
+ nodes_dict = dict(nx_graph.nodes(data=True))
336
+ edges = list(nx_graph.edges(data=True))
337
+ assert len(edges) == 3
338
+
339
+ years_ground_truth = [2010, 2015, 2015]
340
+ for src, dst, edge in edges:
341
+ assert nodes_dict[dst][LABEL] == "organisation"
342
+ assert nodes_dict[dst]["ID"] in [4, 6]
343
+ assert nodes_dict[src][LABEL] == "person"
344
+ assert nodes_dict[src]["ID"] in [3, 5, 7]
345
+ assert edge["year"] in years_ground_truth
346
+
347
+ # If the edge is found, remove it from ground truth so we can check
348
+ # that all edges were found and no extra edges were found
349
+ del years_ground_truth[years_ground_truth.index(edge["year"])]
350
+ del nodes_dict[src]
351
+
352
+ assert len(years_ground_truth) == 0
353
+ assert len(nodes_dict) == 2 # Only the organisation node should be left
354
+
355
+
356
+ def test_networkx_optional_match(conn_db_readonly: ConnDB) -> None:
357
+ conn, _ = conn_db_readonly
358
+ res = conn.execute(
359
+ """
360
+ MATCH (u:User)
361
+ OPTIONAL MATCH (u)-[f:Follows]->(u1:User)
362
+ RETURN u, f, u1;
363
+ """
364
+ )
365
+
366
+ nx_graph = res.get_as_networkx(directed=True)
367
+ assert nx_graph.is_directed()
368
+
369
+ ground_truth_users = [
370
+ {
371
+ "name": "Adam",
372
+ "age": 30,
373
+ },
374
+ {
375
+ "name": "Karissa",
376
+ "age": 40,
377
+ },
378
+ {
379
+ "name": "Zhang",
380
+ "age": 50,
381
+ },
382
+ {
383
+ "name": "Noura",
384
+ "age": 25,
385
+ },
386
+ ]
387
+
388
+ ground_truth_follows = [
389
+ {
390
+ "src": "Adam",
391
+ "dst": "Karissa",
392
+ "since": 2020,
393
+ },
394
+ {
395
+ "src": "Adam",
396
+ "dst": "Zhang",
397
+ "since": 2020,
398
+ },
399
+ {
400
+ "src": "Karissa",
401
+ "dst": "Zhang",
402
+ "since": 2021,
403
+ },
404
+ {
405
+ "src": "Zhang",
406
+ "dst": "Noura",
407
+ "since": 2022,
408
+ },
409
+ ]
410
+
411
+ nodes = list(nx_graph.nodes(data=True))
412
+ assert len(nodes) == 4
413
+ for node_id, node in nodes:
414
+ assert node_id == f"User_{node['name']}"
415
+ for user in ground_truth_users:
416
+ if user["name"] == node["name"]:
417
+ for key in user:
418
+ assert node[key] == user[key]
419
+ assert node["User"]
420
+
421
+ for src, dst, data in nx_graph.edges(data=True):
422
+ assert src in nx_graph.nodes
423
+ assert dst in nx_graph.nodes
424
+ src_node = nx_graph.nodes[src]
425
+ dst_node = nx_graph.nodes[dst]
426
+ src_name = src_node["name"]
427
+ dst_name = dst_node["name"]
428
+ for follows in ground_truth_follows:
429
+ if follows["src"] == src_name and follows["dst"] == dst_name:
430
+ assert data["since"] == follows["since"]
431
+ break
432
+ else:
433
+ msg = f"Edge {src_name} -> {dst_name} not found in ground truth"
434
+ raise AssertionError(msg)
435
+
436
+ edges = list(nx_graph.edges(data=True))
437
+ assert len(edges) == 4