lionagi 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. lionagi/core/agent/base_agent.py +2 -3
  2. lionagi/core/branch/base.py +1 -1
  3. lionagi/core/branch/branch.py +2 -1
  4. lionagi/core/branch/flow_mixin.py +1 -1
  5. lionagi/core/branch/util.py +1 -1
  6. lionagi/core/execute/base_executor.py +1 -4
  7. lionagi/core/execute/branch_executor.py +66 -3
  8. lionagi/core/execute/instruction_map_executor.py +48 -0
  9. lionagi/core/execute/neo4j_executor.py +381 -0
  10. lionagi/core/execute/structure_executor.py +99 -3
  11. lionagi/core/flow/monoflow/ReAct.py +18 -18
  12. lionagi/core/flow/monoflow/chat_mixin.py +1 -1
  13. lionagi/core/flow/monoflow/followup.py +11 -12
  14. lionagi/core/flow/polyflow/__init__.py +1 -1
  15. lionagi/core/generic/component.py +0 -2
  16. lionagi/core/generic/condition.py +1 -1
  17. lionagi/core/generic/edge.py +52 -0
  18. lionagi/core/mail/mail_manager.py +3 -2
  19. lionagi/core/session/session.py +1 -1
  20. lionagi/experimental/__init__.py +0 -0
  21. lionagi/experimental/directive/__init__.py +0 -0
  22. lionagi/experimental/directive/evaluator/__init__.py +0 -0
  23. lionagi/experimental/directive/evaluator/ast_evaluator.py +115 -0
  24. lionagi/experimental/directive/evaluator/base_evaluator.py +202 -0
  25. lionagi/experimental/directive/evaluator/sandbox_.py +14 -0
  26. lionagi/experimental/directive/evaluator/script_engine.py +83 -0
  27. lionagi/experimental/directive/parser/__init__.py +0 -0
  28. lionagi/experimental/directive/parser/base_parser.py +215 -0
  29. lionagi/experimental/directive/schema.py +36 -0
  30. lionagi/experimental/directive/template_/__init__.py +0 -0
  31. lionagi/experimental/directive/template_/base_template.py +63 -0
  32. lionagi/experimental/tool/__init__.py +0 -0
  33. lionagi/experimental/tool/function_calling.py +43 -0
  34. lionagi/experimental/tool/manual.py +66 -0
  35. lionagi/experimental/tool/schema.py +59 -0
  36. lionagi/experimental/tool/tool_manager.py +138 -0
  37. lionagi/experimental/tool/util.py +16 -0
  38. lionagi/experimental/work/__init__.py +0 -0
  39. lionagi/experimental/work/_logger.py +25 -0
  40. lionagi/experimental/work/exchange.py +0 -0
  41. lionagi/experimental/work/schema.py +30 -0
  42. lionagi/experimental/work/tests.py +72 -0
  43. lionagi/experimental/work/util.py +0 -0
  44. lionagi/experimental/work/work_function.py +89 -0
  45. lionagi/experimental/work/worker.py +12 -0
  46. lionagi/integrations/bridge/autogen_/__init__.py +0 -0
  47. lionagi/integrations/bridge/autogen_/autogen_.py +124 -0
  48. lionagi/integrations/bridge/llamaindex_/get_index.py +294 -0
  49. lionagi/integrations/bridge/llamaindex_/llama_pack.py +227 -0
  50. lionagi/integrations/bridge/transformers_/__init__.py +0 -0
  51. lionagi/integrations/bridge/transformers_/install_.py +36 -0
  52. lionagi/integrations/config/oai_configs.py +1 -1
  53. lionagi/integrations/config/ollama_configs.py +1 -1
  54. lionagi/integrations/config/openrouter_configs.py +1 -1
  55. lionagi/integrations/storage/__init__.py +3 -0
  56. lionagi/integrations/storage/neo4j.py +673 -0
  57. lionagi/integrations/storage/storage_util.py +289 -0
  58. lionagi/integrations/storage/to_csv.py +63 -0
  59. lionagi/integrations/storage/to_excel.py +67 -0
  60. lionagi/libs/ln_knowledge_graph.py +405 -0
  61. lionagi/libs/ln_queue.py +101 -0
  62. lionagi/libs/ln_tokenizer.py +57 -0
  63. lionagi/libs/sys_util.py +1 -1
  64. lionagi/lions/__init__.py +0 -0
  65. lionagi/lions/coder/__init__.py +0 -0
  66. lionagi/lions/coder/add_feature.py +20 -0
  67. lionagi/lions/coder/base_prompts.py +22 -0
  68. lionagi/lions/coder/coder.py +121 -0
  69. lionagi/lions/coder/util.py +91 -0
  70. lionagi/lions/researcher/__init__.py +0 -0
  71. lionagi/lions/researcher/data_source/__init__.py +0 -0
  72. lionagi/lions/researcher/data_source/finhub_.py +191 -0
  73. lionagi/lions/researcher/data_source/google_.py +199 -0
  74. lionagi/lions/researcher/data_source/wiki_.py +96 -0
  75. lionagi/lions/researcher/data_source/yfinance_.py +21 -0
  76. lionagi/tests/libs/test_queue.py +67 -0
  77. lionagi/tests/test_core/test_branch.py +0 -1
  78. lionagi/version.py +1 -1
  79. {lionagi-0.1.0.dist-info → lionagi-0.1.1.dist-info}/METADATA +1 -1
  80. {lionagi-0.1.0.dist-info → lionagi-0.1.1.dist-info}/RECORD +83 -29
  81. {lionagi-0.1.0.dist-info → lionagi-0.1.1.dist-info}/LICENSE +0 -0
  82. {lionagi-0.1.0.dist-info → lionagi-0.1.1.dist-info}/WHEEL +0 -0
  83. {lionagi-0.1.0.dist-info → lionagi-0.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,673 @@
1
+ from neo4j import AsyncGraphDatabase
2
+
3
+ from lionagi.integrations.storage.storage_util import output_node_list, output_edge_list
4
+
5
+
6
+ class Neo4j:
7
+ """
8
+ Manages interactions with a Neo4j graph database, facilitating the creation, retrieval, and management
9
+ of graph nodes and relationships asynchronously.
10
+
11
+ Provides methods to add various types of nodes and relationships, query the graph based on specific criteria,
12
+ and enforce database constraints to ensure data integrity.
13
+
14
+ Attributes:
15
+ database (str): The name of the database to connect to.
16
+ driver (neo4j.AsyncGraphDatabase.driver): The Neo4j driver for asynchronous database operations.
17
+ """
18
+
19
+ def __init__(self, uri, user, password, database):
20
+ """
21
+ Initializes the Neo4j database connection using provided credentials and database information.
22
+
23
+ Args:
24
+ uri (str): The URI for the Neo4j database.
25
+ user (str): The username for database authentication.
26
+ password (str): The password for database authentication.
27
+ database (str): The name of the database to use.
28
+ """
29
+ self.database = database
30
+ self.driver = AsyncGraphDatabase.driver(uri, auth=(user, password))
31
+
32
+ # ---------------------to_neo4j---------------------------------
33
+ @staticmethod
34
+ async def add_structure_node(tx, node, name):
35
+ """
36
+ Asynchronously adds a structure node to the graph.
37
+
38
+ Args:
39
+ tx: The Neo4j transaction.
40
+ node (dict): The properties of the node to be added, including 'id' and 'timestamp'.
41
+ name (str): The name of the structure, which is set on the node.
42
+ """
43
+ query = """
44
+ MERGE (n:Structure:LionNode {id:$id})
45
+ SET n.timestamp = $timestamp
46
+ SET n.name = $name
47
+ """
48
+ await tx.run(query, id=node["id"], timestamp=node["timestamp"], name=name)
49
+ # heads=node['head_nodes'],
50
+ # nodes=node['nodes'],
51
+ # edges=node['edges'])
52
+
53
+ @staticmethod
54
+ async def add_system_node(tx, node):
55
+ """
56
+ Asynchronously adds a system node to the graph.
57
+
58
+ Args:
59
+ tx: The Neo4j transaction.
60
+ node (dict): The properties of the system node including 'id', 'timestamp', 'content', 'sender', and 'recipient'.
61
+ """
62
+ query = """
63
+ MERGE (n:System:LionNode {id: $id})
64
+ SET n.timestamp = $timestamp
65
+ SET n.content = $content
66
+ SET n.sender = $sender
67
+ SET n.recipient = $recipient
68
+ """
69
+ await tx.run(
70
+ query,
71
+ id=node["id"],
72
+ timestamp=node["timestamp"],
73
+ content=node["content"],
74
+ sender=node["sender"],
75
+ recipient=node["recipient"],
76
+ )
77
+
78
+ @staticmethod
79
+ async def add_instruction_node(tx, node):
80
+ """
81
+ Asynchronously adds an instruction node to the graph.
82
+
83
+ Args:
84
+ tx: The Neo4j transaction.
85
+ node (dict): The properties of the instruction node including 'id', 'timestamp', 'content', 'sender', and 'recipient'.
86
+ """
87
+ query = """
88
+ MERGE (n:Instruction:LionNode {id: $id})
89
+ SET n.timestamp = $timestamp
90
+ SET n.content = $content
91
+ SET n.sender = $sender
92
+ SET n.recipient = $recipient
93
+ """
94
+ await tx.run(
95
+ query,
96
+ id=node["id"],
97
+ timestamp=node["timestamp"],
98
+ content=node["content"],
99
+ sender=node["sender"],
100
+ recipient=node["recipient"],
101
+ )
102
+
103
+ # TODO: tool.manual
104
+ @staticmethod
105
+ async def add_tool_node(tx, node):
106
+ """
107
+ Asynchronously adds a tool node to the graph.
108
+
109
+ Args:
110
+ tx: The Neo4j transaction.
111
+ node (dict): The properties of the tool node including 'id', 'timestamp', 'function', and 'parser'.
112
+ """
113
+ query = """
114
+ MERGE (n:Tool:LionNode {id: $id})
115
+ SET n.timestamp = $timestamp
116
+ SET n.function = $function
117
+ SET n.parser = $parser
118
+ """
119
+ await tx.run(
120
+ query,
121
+ id=node["id"],
122
+ timestamp=node["timestamp"],
123
+ function=node["function"],
124
+ parser=node["parser"],
125
+ )
126
+
127
+ @staticmethod
128
+ async def add_actionSelection_node(tx, node):
129
+ """
130
+ Asynchronously adds an action selection node to the graph.
131
+
132
+ Args:
133
+ tx: The Neo4j transaction.
134
+ node (dict): The properties of the action selection node including 'id', 'action', and 'actionKwargs'.
135
+ """
136
+ query = """
137
+ MERGE (n:ActionSelection:LionNode {id: $id})
138
+ SET n.action = $action
139
+ SET n.actionKwargs = $actionKwargs
140
+ """
141
+ await tx.run(
142
+ query,
143
+ id=node["id"],
144
+ action=node["action"],
145
+ actionKwargs=node["action_kwargs"],
146
+ )
147
+
148
+ @staticmethod
149
+ async def add_baseAgent_node(tx, node):
150
+ """
151
+ Asynchronously adds an agent node to the graph.
152
+
153
+ Args:
154
+ tx: The Neo4j transaction.
155
+ node (dict): The properties of the agent node including 'id', 'timestamp', 'structureId', and 'outputParser'.
156
+ """
157
+ query = """
158
+ MERGE (n:Agent:LionNode {id:$id})
159
+ SET n.timestamp = $timestamp
160
+ SET n.structureId = $structureId
161
+ SET n.outputParser = $outputParser
162
+ """
163
+ await tx.run(
164
+ query,
165
+ id=node["id"],
166
+ timestamp=node["timestamp"],
167
+ structureId=node["structure_id"],
168
+ outputParser=node["output_parser"],
169
+ )
170
+
171
+ @staticmethod
172
+ async def add_forward_edge(tx, edge):
173
+ """
174
+ Asynchronously adds a forward relationship between two nodes in the graph.
175
+
176
+ Args:
177
+ tx: The Neo4j transaction.
178
+ edge (dict): The properties of the edge including 'id', 'timestamp', 'head', 'tail', 'label', and 'condition'.
179
+ """
180
+ query = """
181
+ MATCH (m:LionNode) WHERE m.id = $head
182
+ MATCH (n:LionNode) WHERE n.id = $tail
183
+ MERGE (m)-[r:FORWARD]->(n)
184
+ SET r.id = $id
185
+ SET r.timestamp = $timestamp
186
+ SET r.label = $label
187
+ SET r.condition = $condition
188
+ """
189
+ await tx.run(
190
+ query,
191
+ id=edge["id"],
192
+ timestamp=edge["timestamp"],
193
+ head=edge["head"],
194
+ tail=edge["tail"],
195
+ label=edge["label"],
196
+ condition=edge["condition"],
197
+ )
198
+
199
+ @staticmethod
200
+ async def add_bundle_edge(tx, edge):
201
+ """
202
+ Asynchronously adds a bundle relationship between two nodes in the graph.
203
+
204
+ Args:
205
+ tx: The Neo4j transaction.
206
+ edge (dict): The properties of the edge including 'id', 'timestamp', 'head', 'tail', 'label', and 'condition'.
207
+ """
208
+ query = """
209
+ MATCH (m:LionNode) WHERE m.id = $head
210
+ MATCH (n:LionNode) WHERE n.id = $tail
211
+ MERGE (m)-[r:BUNDLE]->(n)
212
+ SET r.id = $id
213
+ SET r.timestamp = $timestamp
214
+ SET r.label = $label
215
+ SET r.condition = $condition
216
+ """
217
+ await tx.run(
218
+ query,
219
+ id=edge["id"],
220
+ timestamp=edge["timestamp"],
221
+ head=edge["head"],
222
+ tail=edge["tail"],
223
+ label=edge["label"],
224
+ condition=edge["condition"],
225
+ )
226
+
227
+ @staticmethod
228
+ async def add_head_edge(tx, structure):
229
+ """
230
+ Asynchronously adds head relationships from a structure node to its head nodes.
231
+
232
+ Args:
233
+ tx: The Neo4j transaction.
234
+ structure: The structure node from which head relationships are established.
235
+ """
236
+ for head in structure.get_heads():
237
+ head_id = head.id_
238
+ query = """
239
+ MATCH (m:Structure) WHERE m.id = $structureId
240
+ MATCH (n:LionNode) WHERE n.id = $headId
241
+ MERGE (m)-[:HEAD]->(n)
242
+ """
243
+ await tx.run(query, structureId=structure.id_, headId=head_id)
244
+
245
+ @staticmethod
246
+ async def add_single_condition_cls(tx, condCls):
247
+ """
248
+ Asynchronously adds a condition class node to the graph.
249
+
250
+ Args:
251
+ tx: The Neo4j transaction.
252
+ condCls (dict): The properties of the condition class node including 'className' and 'code'.
253
+ """
254
+ query = """
255
+ MERGE (n:Condition:LionNode {className: $className})
256
+ SET n.code = $code
257
+ """
258
+ await tx.run(query, className=condCls["class_name"], code=condCls["class"])
259
+
260
+ async def add_node(self, tx, node_dict, structure_name):
261
+ """
262
+ Asynchronously adds various types of nodes to the Neo4j graph based on the provided dictionary of node lists.
263
+
264
+ This method iterates through a dictionary where each key is a node type and each value is a list of nodes.
265
+ It adds each node to the graph according to its type.
266
+
267
+ Args:
268
+ tx: The Neo4j transaction.
269
+ node_dict (dict): A dictionary where keys are node type strings and values are lists of node properties dictionaries.
270
+ structure_name (str): The name of the structure to which these nodes belong, used specifically for 'StructureExecutor' nodes.
271
+
272
+ Raises:
273
+ ValueError: If an unsupported node type is detected in the dictionary.
274
+ """
275
+ for node in node_dict:
276
+ node_list = node_dict[node]
277
+ if node == "StructureExecutor":
278
+ [
279
+ await self.add_structure_node(tx, i, structure_name)
280
+ for i in node_list
281
+ ]
282
+ elif node == "System":
283
+ [await self.add_system_node(tx, i) for i in node_list]
284
+ elif node == "Instruction":
285
+ [await self.add_instruction_node(tx, i) for i in node_list]
286
+ elif node == "Tool":
287
+ [await self.add_tool_node(tx, i) for i in node_list]
288
+ elif node == "ActionSelection":
289
+ [await self.add_actionSelection_node(tx, i) for i in node_list]
290
+ elif node == "BaseAgent":
291
+ [await self.add_baseAgent_node(tx, i) for i in node_list]
292
+ else:
293
+ raise ValueError("Not supported node type detected")
294
+
295
+ async def add_edge(self, tx, edge_list):
296
+ """
297
+ Asynchronously adds edges to the Neo4j graph based on a list of edge properties.
298
+
299
+ This method processes a list of edges, each represented by a dictionary of properties, and creates either
300
+ 'BUNDLE' or 'FORWARD' relationships in the graph based on the 'bundle' property of each edge.
301
+
302
+ Args:
303
+ tx: The Neo4j transaction.
304
+ edge_list (list[dict]): A list of dictionaries where each dictionary contains properties of an edge including
305
+ its type, identifiers of the head and tail nodes, and additional attributes like label and condition.
306
+
307
+ Raises:
308
+ ValueError: If an edge dictionary is missing required properties or contains invalid values.
309
+ """
310
+ for edge in edge_list:
311
+ if edge["bundle"] == "True":
312
+ await self.add_bundle_edge(tx, edge)
313
+ else:
314
+ await self.add_forward_edge(tx, edge)
315
+
316
+ async def add_condition_cls(self, tx, edge_cls_list):
317
+ """
318
+ Asynchronously adds condition class nodes to the Neo4j graph.
319
+
320
+ This method iterates over a list of condition class definitions and adds each as a node in the graph.
321
+ Each condition class is typically used to define logic or conditions within the graph structure.
322
+
323
+ Args:
324
+ tx: The Neo4j transaction.
325
+ edge_cls_list (list[dict]): A list of dictionaries where each dictionary represents the properties of a
326
+ condition class, including the class name and its corresponding code.
327
+
328
+ Raises:
329
+ ValueError: If any condition class dictionary is missing required properties or the properties do not adhere
330
+ to expected formats.
331
+ """
332
+ for cls in edge_cls_list:
333
+ await self.add_single_condition_cls(tx, cls)
334
+
335
+ @staticmethod
336
+ async def check_id_constraint(tx):
337
+ """
338
+ Asynchronously applies a unique constraint on the 'id' attribute for all nodes of type 'LionNode' in the graph.
339
+
340
+ This constraint ensures that each node in the graph has a unique identifier.
341
+
342
+ Args:
343
+ tx: The Neo4j transaction.
344
+ """
345
+ query = """
346
+ CREATE CONSTRAINT node_id IF NOT EXISTS
347
+ FOR (n:LionNode) REQUIRE (n.id) IS UNIQUE
348
+ """
349
+ await tx.run(query)
350
+
351
+ @staticmethod
352
+ async def check_structure_name_constraint(tx):
353
+ """
354
+ Asynchronously applies a unique constraint on the 'name' attribute for all nodes of type 'Structure' in the graph.
355
+
356
+ This constraint ensures that each structure in the graph can be uniquely identified by its name.
357
+
358
+ Args:
359
+ tx: The Neo4j transaction.
360
+ """
361
+ query = """
362
+ CREATE CONSTRAINT structure_name IF NOT EXISTS
363
+ FOR (n:Structure) REQUIRE (n.name) IS UNIQUE
364
+ """
365
+ await tx.run(query)
366
+
367
+ async def store(self, structure, structure_name):
368
+ """
369
+ Asynchronously stores a structure and its components in the Neo4j graph.
370
+
371
+ This method orchestrates the storage of nodes, edges, and other related elements that make up a structure,
372
+ ensuring all elements are added transactionally.
373
+
374
+ Args:
375
+ structure: The structure object containing the nodes and edges to be stored.
376
+ structure_name (str): The name of the structure, used to uniquely identify it in the graph.
377
+
378
+ Raises:
379
+ ValueError: If the transaction fails due to an exception, indicating an issue with the data or constraints.
380
+ """
381
+ node_list, node_dict = output_node_list(structure)
382
+ edge_list, edge_cls_list = output_edge_list(structure)
383
+
384
+ async with self.driver as driver:
385
+ async with driver.session(database=self.database) as session:
386
+ # constraint
387
+ tx = await session.begin_transaction()
388
+ try:
389
+ await self.check_id_constraint(tx)
390
+ await self.check_structure_name_constraint(tx)
391
+ await tx.commit()
392
+ except Exception as e:
393
+ raise ValueError(f"transaction rolled back due to exception: {e}")
394
+ finally:
395
+ await tx.close()
396
+
397
+ # query
398
+ tx = await session.begin_transaction()
399
+ try:
400
+ await self.add_node(tx, node_dict, structure_name)
401
+ await self.add_edge(tx, edge_list)
402
+ await self.add_condition_cls(tx, edge_cls_list)
403
+ await self.add_head_edge(tx, structure)
404
+ await tx.commit()
405
+ except Exception as e:
406
+ raise ValueError(f"transaction rolled back due to exception: {e}")
407
+ finally:
408
+ await tx.close()
409
+
410
+ # ---------------------frpm_neo4j---------------------------------
411
+ @staticmethod
412
+ async def match_node(tx, node_id):
413
+ """
414
+ Asynchronously retrieves a node from the graph based on its identifier.
415
+
416
+ Args:
417
+ tx: The Neo4j transaction.
418
+ node_id (str): The unique identifier of the node to retrieve.
419
+
420
+ Returns:
421
+ A dictionary containing the properties of the node if found, otherwise None.
422
+ """
423
+ query = """
424
+ MATCH (n:LionNode) WHERE n.id = $id
425
+ RETURN labels(n), n{.*}
426
+ """
427
+ result = await tx.run(query, id=node_id)
428
+ result = [record.values() async for record in result]
429
+ if result:
430
+ return result[0]
431
+ else:
432
+ return None
433
+
434
+ @staticmethod
435
+ async def match_structure_id(tx, name):
436
+ """
437
+ Asynchronously retrieves the identifier of a structure based on its name.
438
+
439
+ Args:
440
+ tx: The Neo4j transaction.
441
+ name (str): The name of the structure to retrieve the identifier for.
442
+
443
+ Returns:
444
+ A list containing the identifier(s) of the matching structure(s).
445
+ """
446
+ query = """
447
+ MATCH (n:Structure) WHERE n.name = $name
448
+ RETURN n.id
449
+ """
450
+ result = await tx.run(query, name=name)
451
+ result = [record.values() async for record in result]
452
+ result = sum(result, [])
453
+ return result
454
+
455
+ @staticmethod
456
+ async def head(tx, node_id):
457
+ """
458
+ Asynchronously retrieves the head nodes associated with a structure node in the graph.
459
+
460
+ Args:
461
+ tx: The Neo4j transaction.
462
+ node_id (str): The identifier of the structure node whose head nodes are to be retrieved.
463
+
464
+ Returns:
465
+ A list of dictionaries representing the properties and labels of each head node connected to the structure.
466
+ """
467
+ query = """
468
+ MATCH (n:Structure)-[r:HEAD]->(m) WHERE n.id = $nodeId
469
+ RETURN r{.*}, labels(m), m{.*}
470
+ """
471
+ result = await tx.run(query, nodeId=node_id)
472
+ result = [record.values() async for record in result]
473
+ return result
474
+
475
+ @staticmethod
476
+ async def forward(tx, node_id):
477
+ """
478
+ Asynchronously retrieves all forward relationships and their target nodes for a given node.
479
+
480
+ Args:
481
+ tx: The Neo4j transaction.
482
+ node_id (str): The identifier of the node from which to retrieve forward relationships.
483
+
484
+ Returns:
485
+ A list of dictionaries representing the properties and labels of each node connected by a forward relationship.
486
+ """
487
+ query = """
488
+ MATCH (n:LionNode)-[r:FORWARD]->(m) WHERE n.id = $nodeId
489
+ RETURN r{.*}, labels(m), m{.*}
490
+ """
491
+ result = await tx.run(query, nodeId=node_id)
492
+ result = [record.values() async for record in result]
493
+ return result
494
+
495
+ @staticmethod
496
+ async def bundle(tx, node_id):
497
+ """
498
+ Asynchronously retrieves all bundle relationships and their target nodes for a given node.
499
+
500
+ Args:
501
+ tx: The Neo4j transaction.
502
+ node_id (str): The identifier of the node from which to retrieve bundle relationships.
503
+
504
+ Returns:
505
+ A list of dictionaries representing the properties and labels of each node connected by a bundle relationship.
506
+ """
507
+ query = """
508
+ MATCH (n:LionNode)-[r:BUNDLE]->(m) WHERE n.id = $nodeId
509
+ RETURN labels(m), m{.*}
510
+ """
511
+ result = await tx.run(query, nodeId=node_id)
512
+ result = [record.values() async for record in result]
513
+ return result
514
+
515
+ @staticmethod
516
+ async def match_condition_class(tx, name):
517
+ """
518
+ Asynchronously retrieves the code for a condition class based on its class name.
519
+
520
+ Args:
521
+ tx: The Neo4j transaction.
522
+ name (str): The class name of the condition to retrieve the code for.
523
+
524
+ Returns:
525
+ The code of the condition class if found, otherwise None.
526
+ """
527
+ query = """
528
+ MATCH (n:Condition) WHERE n.className = $name
529
+ RETURN n.code
530
+ """
531
+ result = await tx.run(query, name=name)
532
+ result = [record.values() async for record in result]
533
+ if result:
534
+ return result[0][0]
535
+ else:
536
+ return None
537
+
538
+ async def locate_structure(
539
+ self, tx, structure_name: str = None, structure_id: str = None
540
+ ):
541
+ """
542
+ Asynchronously locates a structure by its name or ID in the Neo4j graph.
543
+
544
+ This method is designed to find a structure either by its name or by a specific identifier,
545
+ returning the identifier if found.
546
+
547
+ Args:
548
+ tx: The Neo4j transaction.
549
+ structure_name (str, optional): The name of the structure to locate.
550
+ structure_id (str, optional): The unique identifier of the structure to locate.
551
+
552
+ Returns:
553
+ str: The identifier of the located structure.
554
+
555
+ Raises:
556
+ ValueError: If neither structure name nor ID is provided, or if the provided name or ID does not correspond
557
+ to any existing structure.
558
+ """
559
+ if not structure_name and not structure_id:
560
+ raise ValueError("Please provide the structure name or id")
561
+ if structure_name:
562
+ id = await self.match_structure_id(tx, structure_name)
563
+ if not id:
564
+ raise ValueError(f"Structure: {structure_name} is not found")
565
+ elif structure_id is not None and structure_id not in id:
566
+ raise ValueError(
567
+ f"{structure_name} and id {structure_id} does not match"
568
+ )
569
+ return id[0]
570
+ else:
571
+ result = await self.match_node(tx, structure_id)
572
+ if result:
573
+ return structure_id
574
+ else:
575
+ raise ValueError(f"Structure id {structure_id} is invalid")
576
+
577
+ async def get_heads(self, structure_name: str = None, structure_id: str = None):
578
+ """
579
+ Asynchronously retrieves the head nodes associated with a given structure in the graph.
580
+
581
+ Args:
582
+ structure_name (str, optional): The name of the structure whose head nodes are to be retrieved.
583
+ structure_id (str, optional): The identifier of the structure whose head nodes are to be retrieved.
584
+
585
+ Returns:
586
+ tuple: A tuple containing the structure identifier and a list of dictionaries, each representing a head node
587
+ connected to the structure.
588
+
589
+ Raises:
590
+ ValueError: If both structure name and ID are not provided, or if the specified structure cannot be found.
591
+ """
592
+ async with self.driver as driver:
593
+ async with driver.session(database=self.database) as session:
594
+ id = await session.execute_read(
595
+ self.locate_structure, structure_name, structure_id
596
+ )
597
+ result = await session.execute_read(self.head, id)
598
+ return id, result
599
+
600
+ async def get_bundle(self, node_id):
601
+ """
602
+ Asynchronously retrieves all nodes connected by a bundle relationship to a given node in the graph.
603
+
604
+ Args:
605
+ node_id (str): The identifier of the node from which bundle relationships are to be retrieved.
606
+
607
+ Returns:
608
+ list: A list of dictionaries representing each node connected by a bundle relationship from the specified node.
609
+ """
610
+ async with self.driver as driver:
611
+ async with driver.session(database=self.database) as session:
612
+ result = await session.execute_read(self.bundle, node_id)
613
+ return result
614
+
615
+ async def get_forwards(self, node_id):
616
+ """
617
+ Asynchronously retrieves all nodes connected by forward relationships to a given node in the graph.
618
+
619
+ Args:
620
+ node_id (str): The identifier of the node from which forward relationships are to be retrieved.
621
+
622
+ Returns:
623
+ list: A list of dictionaries representing each node connected by a forward relationship from the specified node.
624
+ """
625
+ async with self.driver as driver:
626
+ async with driver.session(database=self.database) as session:
627
+ result = await session.execute_read(self.forward, node_id)
628
+ return result
629
+
630
+ async def get_condition_cls_code(self, class_name):
631
+ """
632
+ Asynchronously retrieves the code associated with a specified condition class from the Neo4j graph.
633
+
634
+ This method queries the graph to find the code that defines the behavior or logic of a condition class by its name.
635
+
636
+ Args:
637
+ class_name (str): The name of the condition class whose code is to be retrieved.
638
+
639
+ Returns:
640
+ str: The code of the condition class if found, or None if the class does not exist in the graph.
641
+
642
+ Raises:
643
+ ValueError: If the class_name is not provided or if the query fails due to incorrect syntax or database issues.
644
+ """
645
+ async with self.driver as driver:
646
+ async with driver.session(database=self.database) as session:
647
+ result = await session.execute_read(
648
+ self.match_condition_class, class_name
649
+ )
650
+ return result
651
+
652
+ async def node_exist(self, node_id):
653
+ """
654
+ Asynchronously checks if a node with the specified identifier exists in the Neo4j graph.
655
+
656
+ This method is useful for validation checks before attempting operations that assume the existence of a node.
657
+
658
+ Args:
659
+ node_id (str): The unique identifier of the node to check for existence.
660
+
661
+ Returns:
662
+ bool: True if the node exists in the graph, False otherwise.
663
+
664
+ Raises:
665
+ ValueError: If the node_id is not provided or if the query fails due to incorrect syntax or database issues.
666
+ """
667
+ async with self.driver as driver:
668
+ async with driver.session(database=self.database) as session:
669
+ result = await session.execute_read(self.match_node, node_id)
670
+ if result:
671
+ return True
672
+ else:
673
+ return False