structured2graph 0.1.1__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 (41) hide show
  1. __init__.py +47 -0
  2. core/__init__.py +23 -0
  3. core/hygm/__init__.py +74 -0
  4. core/hygm/hygm.py +2351 -0
  5. core/hygm/models/__init__.py +82 -0
  6. core/hygm/models/graph_models.py +667 -0
  7. core/hygm/models/llm_models.py +229 -0
  8. core/hygm/models/operations.py +176 -0
  9. core/hygm/models/sources.py +68 -0
  10. core/hygm/models/user_operations.py +139 -0
  11. core/hygm/strategies/__init__.py +17 -0
  12. core/hygm/strategies/base.py +36 -0
  13. core/hygm/strategies/deterministic.py +262 -0
  14. core/hygm/strategies/llm.py +904 -0
  15. core/hygm/validation/__init__.py +38 -0
  16. core/hygm/validation/base.py +194 -0
  17. core/hygm/validation/graph_schema_validator.py +687 -0
  18. core/hygm/validation/memgraph_data_validator.py +991 -0
  19. core/migration_agent.py +1369 -0
  20. core/schema/spec.json +155 -0
  21. core/utils/meta_graph.py +108 -0
  22. database/__init__.py +36 -0
  23. database/adapters/__init__.py +11 -0
  24. database/adapters/memgraph.py +318 -0
  25. database/adapters/mysql.py +311 -0
  26. database/adapters/postgresql.py +335 -0
  27. database/analyzer.py +396 -0
  28. database/factory.py +219 -0
  29. database/models.py +209 -0
  30. main.py +518 -0
  31. query_generation/__init__.py +20 -0
  32. query_generation/cypher_generator.py +129 -0
  33. query_generation/schema_utilities.py +88 -0
  34. structured2graph-0.1.1.dist-info/METADATA +197 -0
  35. structured2graph-0.1.1.dist-info/RECORD +41 -0
  36. structured2graph-0.1.1.dist-info/WHEEL +4 -0
  37. structured2graph-0.1.1.dist-info/entry_points.txt +2 -0
  38. structured2graph-0.1.1.dist-info/licenses/LICENSE +21 -0
  39. utils/__init__.py +57 -0
  40. utils/config.py +235 -0
  41. utils/environment.py +404 -0
core/schema/spec.json ADDED
@@ -0,0 +1,155 @@
1
+ {
2
+ "nodes": [
3
+ {
4
+ "labels": ["NodeLabel"],
5
+ "count": 1,
6
+ "properties": [
7
+ {
8
+ "key": "property_key_0",
9
+ "count": 1,
10
+ "filling_factor": 100.0,
11
+ "types": [
12
+ { "type": "Null", "count": 1, "examples": [] },
13
+ { "type": "String", "count": 1, "examples": [] },
14
+ { "type": "Boolean", "count": 1, "examples": [] },
15
+ { "type": "Integer", "count": 1, "examples": [] },
16
+ { "type": "Float", "count": 1, "examples": [] },
17
+ { "type": "List", "count": 1, "examples": [] },
18
+ { "type": "Map", "count": 1, "examples": [] },
19
+ { "type": "Duration", "count": 1, "examples": [] },
20
+ { "type": "Date", "count": 1, "examples": [] },
21
+ { "type": "LocalTime", "count": 1, "examples": [] },
22
+ { "type": "LocalDateTime", "count": 1, "examples": [] },
23
+ { "type": "ZonedDateTime", "count": 1, "examples": [] },
24
+ { "type": "Enum::Name", "count": 1, "examples": [] },
25
+ { "type": "Point2D", "count": 1, "examples": [] },
26
+ { "type": "Point3D", "count": 1, "examples": [] }
27
+ ],
28
+ "source": {
29
+ "field": "source_table.column_name",
30
+ "transformation": "UPPER(column_name) | null"
31
+ }
32
+ }
33
+ ],
34
+ "examples": [{}],
35
+ "source": {
36
+ "type": "table|view|file|api|manual",
37
+ "name": "source_table_name",
38
+ "location": "database.schema.table_name",
39
+ "mapping": {
40
+ "labels": ["source_table.type_column | NodeLabel"],
41
+ "id_field": "source_table.id"
42
+ }
43
+ }
44
+ }
45
+ ],
46
+
47
+ "edges": [
48
+ {
49
+ "edge_type": "RELATIONSHIP_TYPE",
50
+ "start_node_labels": ["StartNodeLabel"],
51
+ "end_node_labels": ["EndNodeLabel"],
52
+ "count": 1,
53
+ "properties": [
54
+ {
55
+ "key": "edge_property",
56
+ "count": 1,
57
+ "filling_factor": 100.0,
58
+ "types": [{ "type": "String", "count": 1, "examples": [] }],
59
+ "source": {
60
+ "field": "many_to_many_table.property_column",
61
+ "transformation": "DATE(created_at) | null"
62
+ }
63
+ }
64
+ ],
65
+ "examples": [{}],
66
+ "source": {
67
+ "type": "table|view|many_to_many|derived",
68
+ "name": "relationships_table",
69
+ "location": "database.schema.relationships_table",
70
+ "mapping": {
71
+ "start_node": "relationships_table.start_id",
72
+ "end_node": "relationships_table.end_id",
73
+ "edge_type": "RELATIONSHIP_TYPE | relationships_table.type_column"
74
+ }
75
+ }
76
+ }
77
+ ],
78
+
79
+ "node_indexes": [
80
+ {
81
+ "labels": ["NodeLabel"],
82
+ "properties": ["indexed_property"],
83
+ "count": 0,
84
+ "examples": [{}],
85
+ "type": "label|label+property|label+property_point|label_text|label+property_text|label+property_vector",
86
+ "source": {
87
+ "origin": "migration_requirement|performance_optimization|source_database_index",
88
+ "reason": "unique_constraint|query_performance|full_text_search|vector_similarity",
89
+ "created_by": "migration_agent|dba_team|application_requirement",
90
+ "index_name": "idx_node_property | null",
91
+ "migrated_from": "database.schema.table_name | null"
92
+ }
93
+ }
94
+ ],
95
+
96
+ "edge_indexes": [
97
+ {
98
+ "edge_type": "RELATIONSHIP_TYPE",
99
+ "properties": ["indexed_edge_property"],
100
+ "count": 0,
101
+ "examples": [{}],
102
+ "type": "edge_type|edge_type+property|edge_type+property_point|edge_type_text|edge_type+property_text|edge_type+property_vector",
103
+ "source": {
104
+ "origin": "migration_requirement|performance_optimization|source_database_index",
105
+ "reason": "temporal_queries|relationship_traversal|full_text_search|vector_similarity",
106
+ "created_by": "migration_agent|dba_team|application_requirement",
107
+ "index_name": "idx_edge_property | null",
108
+ "migrated_from": "database.schema.table_name | null"
109
+ }
110
+ }
111
+ ],
112
+
113
+ "node_constraints": [
114
+ {
115
+ "type": "unique|existence|data_type",
116
+ "labels": ["NodeLabel"],
117
+ "properties": ["constrained_property"],
118
+ "data_type": "String|Integer|Float|Boolean|Date|etc",
119
+ "source": {
120
+ "origin": "source_database_constraint|migration_requirement|data_integrity",
121
+ "constraint_name": "original_constraint_name | null",
122
+ "migrated_from": "database.schema.table_name | null",
123
+ "created_by": "migration_agent|dba_team|data_governance"
124
+ }
125
+ }
126
+ ],
127
+
128
+ "edge_constraints": [
129
+ {
130
+ "type": "unique|existence|data_type",
131
+ "edge_type": "RELATIONSHIP_TYPE",
132
+ "properties": ["constrained_edge_property"],
133
+ "data_type": "String|Integer|Float|Boolean|Date|etc",
134
+ "source": {
135
+ "origin": "source_database_constraint|migration_requirement|data_integrity",
136
+ "constraint_name": "original_constraint_name | null",
137
+ "migrated_from": "database.schema.table_name | null",
138
+ "created_by": "migration_agent|dba_team|data_governance"
139
+ }
140
+ }
141
+ ],
142
+
143
+ "enums": [
144
+ {
145
+ "name": "EnumName",
146
+ "values": [],
147
+ "source": {
148
+ "origin": "source_database_enum|application_requirement|manual_definition",
149
+ "enum_name": "original_enum_name | null",
150
+ "migrated_from": "database.schema.enum_type | null",
151
+ "created_by": "migration_agent|application_developer|data_architect"
152
+ }
153
+ }
154
+ ]
155
+ }
@@ -0,0 +1,108 @@
1
+ """Shared helpers for HyGM meta graph summaries and keys."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, List
6
+
7
+
8
+ def node_key(node: Any) -> str:
9
+ """Generate a stable key for a graph node definition."""
10
+ source = getattr(node, "source", None)
11
+ source_name = getattr(source, "name", None)
12
+ if source_name:
13
+ return f"source::{source_name}"
14
+ labels = sorted(getattr(node, "labels", []))
15
+ return "labels::" + "|".join(labels)
16
+
17
+
18
+ def relationship_key(rel: Any) -> str:
19
+ """Generate a stable key for a graph relationship."""
20
+ source = getattr(rel, "source", None)
21
+ source_name = getattr(source, "name", None)
22
+ if source_name:
23
+ return f"source::{source_name}"
24
+ edge_type = getattr(rel, "edge_type", "")
25
+ start = "|".join(sorted(getattr(rel, "start_node_labels", [])))
26
+ end = "|".join(sorted(getattr(rel, "end_node_labels", [])))
27
+ return f"edge::{edge_type}:{start}->{end}"
28
+
29
+
30
+ def summarize_node(node: Any) -> Dict[str, Any]:
31
+ """Create a JSON-serializable summary for a node definition."""
32
+ properties = sorted(
33
+ {prop.key for prop in getattr(node, "properties", []) if hasattr(prop, "key")}
34
+ )
35
+ node_source = getattr(node, "source", None)
36
+ mapping: Dict[str, Any] = {}
37
+ source_name = None
38
+ id_field = None
39
+ if node_source:
40
+ mapping = dict(getattr(node_source, "mapping", {}) or {})
41
+ source_name = getattr(node_source, "name", None)
42
+ id_field = mapping.get("id_field")
43
+ return {
44
+ "labels": sorted(getattr(node, "labels", [])),
45
+ "properties": properties,
46
+ "id_field": id_field,
47
+ "source": source_name,
48
+ "mapping": mapping,
49
+ }
50
+
51
+
52
+ def summarize_relationship(rel: Any) -> Dict[str, Any]:
53
+ """Create a JSON-serializable summary for a relationship."""
54
+ rel_source = getattr(rel, "source", None)
55
+ mapping: Dict[str, Any] = {}
56
+ source_name = None
57
+ source_type = None
58
+ if rel_source:
59
+ mapping = dict(getattr(rel_source, "mapping", {}) or {})
60
+ source_name = getattr(rel_source, "name", None)
61
+ source_type = getattr(rel_source, "type", None)
62
+ start_labels = sorted(getattr(rel, "start_node_labels", []))
63
+ end_labels = sorted(getattr(rel, "end_node_labels", []))
64
+ start_table = mapping.get("from_table")
65
+ end_table = mapping.get("to_table")
66
+ if not start_table and mapping.get("start_node"):
67
+ start_table = str(mapping["start_node"]).split(".")[0]
68
+ if not end_table and mapping.get("end_node"):
69
+ end_table = str(mapping["end_node"]).split(".")[0]
70
+ return {
71
+ "edge_type": getattr(rel, "edge_type", ""),
72
+ "start": start_labels,
73
+ "end": end_labels,
74
+ "source": source_name,
75
+ "source_type": source_type,
76
+ "mapping": mapping,
77
+ "start_table": start_table,
78
+ "end_table": end_table,
79
+ "join_table": mapping.get("join_table"),
80
+ }
81
+
82
+
83
+ def summarize_nodes(nodes: List[Any]) -> Dict[str, Dict[str, Any]]:
84
+ """Build summaries for all provided nodes."""
85
+ summaries: Dict[str, Dict[str, Any]] = {}
86
+ for node in nodes:
87
+ summaries[node_key(node)] = summarize_node(node)
88
+ return summaries
89
+
90
+
91
+ def summarize_relationships(
92
+ relationships: List[Any],
93
+ ) -> Dict[str, Dict[str, Any]]:
94
+ """Build summaries for all provided relationships."""
95
+ summaries: Dict[str, Dict[str, Any]] = {}
96
+ for relationship in relationships:
97
+ summaries[relationship_key(relationship)] = summarize_relationship(relationship)
98
+ return summaries
99
+
100
+
101
+ __all__ = [
102
+ "node_key",
103
+ "relationship_key",
104
+ "summarize_node",
105
+ "summarize_relationship",
106
+ "summarize_nodes",
107
+ "summarize_relationships",
108
+ ]
database/__init__.py ADDED
@@ -0,0 +1,36 @@
1
+ """
2
+ Database analysis package interface.
3
+
4
+ This module provides the main entry point for database analysis functionality,
5
+ importing and exposing all the necessary components.
6
+ """
7
+
8
+ # Core data models
9
+ from .models import (
10
+ TableType,
11
+ ColumnInfo,
12
+ ForeignKeyInfo,
13
+ TableInfo,
14
+ RelationshipInfo,
15
+ DatabaseStructure,
16
+ )
17
+
18
+ # Base interfaces
19
+ from .analyzer import DatabaseAnalyzer
20
+
21
+ # Factory (imported from existing factory module)
22
+ from .factory import DatabaseAnalyzerFactory
23
+
24
+ __all__ = [
25
+ # Data models
26
+ "TableType",
27
+ "ColumnInfo",
28
+ "ForeignKeyInfo",
29
+ "TableInfo",
30
+ "RelationshipInfo",
31
+ "DatabaseStructure",
32
+ # Interfaces
33
+ "DatabaseAnalyzer",
34
+ # Factory
35
+ "DatabaseAnalyzerFactory",
36
+ ]
@@ -0,0 +1,11 @@
1
+ """
2
+ Database adapter implementations for different database systems.
3
+ """
4
+
5
+ from .mysql import MySQLAnalyzer
6
+ from .postgresql import PostgreSQLAnalyzer
7
+
8
+ __all__ = [
9
+ "MySQLAnalyzer",
10
+ "PostgreSQLAnalyzer",
11
+ ]
@@ -0,0 +1,318 @@
1
+ """
2
+ Memgraph database adapter for schema validation.
3
+
4
+ This adapter provides connection and query capabilities for Memgraph
5
+ to support post-migration validation.
6
+ """
7
+
8
+ import logging
9
+ from typing import Dict, List, Any, Optional, Tuple
10
+ from dataclasses import dataclass
11
+
12
+ try:
13
+ import neo4j
14
+ except ImportError:
15
+ neo4j = None
16
+
17
+ try:
18
+ import mgclient
19
+ except ImportError:
20
+ mgclient = None
21
+
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ @dataclass
27
+ class MemgraphConnectionConfig:
28
+ """Configuration for Memgraph connection."""
29
+
30
+ host: str = "localhost"
31
+ port: int = 7687
32
+ username: str = ""
33
+ password: str = ""
34
+ use_ssl: bool = False
35
+
36
+
37
+ class MemgraphAdapter:
38
+ """
39
+ Adapter for connecting to and querying Memgraph database.
40
+
41
+ Supports both mgclient and neo4j driver connections.
42
+ """
43
+
44
+ def __init__(self, config: MemgraphConnectionConfig):
45
+ """
46
+ Initialize Memgraph adapter.
47
+
48
+ Args:
49
+ config: Connection configuration
50
+ """
51
+ self.config = config
52
+ self.connection = None
53
+ self.driver = None
54
+ self._connection_type = None
55
+
56
+ def connect(self) -> bool:
57
+ """
58
+ Establish connection to Memgraph.
59
+
60
+ Returns:
61
+ True if connection successful, False otherwise
62
+ """
63
+ # Try mgclient first (native Memgraph client)
64
+ if mgclient and self._try_mgclient_connection():
65
+ self._connection_type = "mgclient"
66
+ logger.info("Connected to Memgraph using mgclient")
67
+ return True
68
+
69
+ # Fallback to neo4j driver (compatible with Memgraph)
70
+ if neo4j and self._try_neo4j_connection():
71
+ self._connection_type = "neo4j"
72
+ logger.info("Connected to Memgraph using neo4j driver")
73
+ return True
74
+
75
+ logger.error("Failed to connect to Memgraph with available drivers")
76
+ return False
77
+
78
+ def _try_mgclient_connection(self) -> bool:
79
+ """Try to connect using mgclient."""
80
+ try:
81
+ self.connection = mgclient.connect(
82
+ host=self.config.host,
83
+ port=self.config.port,
84
+ username=self.config.username,
85
+ password=self.config.password,
86
+ sslmode=mgclient.SSLMode.REQUIRE
87
+ if self.config.use_ssl
88
+ else mgclient.SSLMode.DISABLE,
89
+ )
90
+ # Test the connection
91
+ cursor = self.connection.cursor()
92
+ cursor.execute("RETURN 1")
93
+ cursor.fetchall()
94
+ return True
95
+ except Exception as e:
96
+ logger.debug(f"mgclient connection failed: {e}")
97
+ return False
98
+
99
+ def _try_neo4j_connection(self) -> bool:
100
+ """Try to connect using neo4j driver."""
101
+ try:
102
+ uri = f"{'bolt+s' if self.config.use_ssl else 'bolt'}://{self.config.host}:{self.config.port}"
103
+ self.driver = neo4j.GraphDatabase.driver(
104
+ uri,
105
+ auth=(self.config.username, self.config.password)
106
+ if self.config.username
107
+ else None,
108
+ )
109
+ # Test the connection
110
+ with self.driver.session() as session:
111
+ session.run("RETURN 1")
112
+ return True
113
+ except Exception as e:
114
+ logger.debug(f"neo4j driver connection failed: {e}")
115
+ return False
116
+
117
+ def execute_query(self, query: str) -> List[Tuple]:
118
+ """
119
+ Execute a query and return results.
120
+
121
+ Args:
122
+ query: Cypher query to execute
123
+
124
+ Returns:
125
+ List of result tuples
126
+ """
127
+ if not self.connection and not self.driver:
128
+ raise RuntimeError("Not connected to Memgraph")
129
+
130
+ if self._connection_type == "mgclient":
131
+ return self._execute_mgclient_query(query)
132
+ elif self._connection_type == "neo4j":
133
+ return self._execute_neo4j_query(query)
134
+ else:
135
+ raise RuntimeError("Unknown connection type")
136
+
137
+ def _execute_mgclient_query(self, query: str) -> List[Tuple]:
138
+ """Execute query using mgclient."""
139
+ cursor = self.connection.cursor()
140
+ cursor.execute(query)
141
+ return cursor.fetchall()
142
+
143
+ def _execute_neo4j_query(self, query: str) -> List[Tuple]:
144
+ """Execute query using neo4j driver."""
145
+ with self.driver.session() as session:
146
+ result = session.run(query)
147
+ return [tuple(record.values()) for record in result]
148
+
149
+ def get_schema_info(self) -> List[Tuple]:
150
+ """
151
+ Get schema information from Memgraph.
152
+
153
+ Returns:
154
+ Schema information as list of tuples
155
+ """
156
+ try:
157
+ # Try SHOW SCHEMA INFO first (newer Memgraph versions)
158
+ return self.execute_query("SHOW SCHEMA INFO")
159
+ except Exception:
160
+ # Fallback to alternative schema queries
161
+ logger.warning(
162
+ "SHOW SCHEMA INFO not available, using alternative schema detection"
163
+ )
164
+ return self._get_schema_info_alternative()
165
+
166
+ def _get_schema_info_alternative(self) -> List[Tuple]:
167
+ """
168
+ Alternative method to get schema info for older Memgraph versions.
169
+
170
+ Returns:
171
+ Schema information as list of tuples
172
+ """
173
+ schema_info = []
174
+
175
+ # Get node labels and their properties
176
+ try:
177
+ node_labels_result = self.execute_query("CALL db.labels()")
178
+ for label_row in node_labels_result:
179
+ label = label_row[0]
180
+
181
+ # Get properties for this label
182
+ props_query = f"MATCH (n:{label}) RETURN DISTINCT keys(n) LIMIT 1"
183
+ props_result = self.execute_query(props_query)
184
+ properties = (
185
+ props_result[0][0] if props_result and props_result[0] else []
186
+ )
187
+
188
+ # Format as schema info tuple
189
+ schema_info.append(
190
+ ("node", label, {prop: "Unknown" for prop in properties})
191
+ )
192
+ except Exception as e:
193
+ logger.warning(f"Failed to get node labels: {e}")
194
+
195
+ # Get relationship types and their properties
196
+ try:
197
+ rel_types_result = self.execute_query("CALL db.relationshipTypes()")
198
+ for rel_row in rel_types_result:
199
+ rel_type = rel_row[0]
200
+
201
+ # Get properties for this relationship type
202
+ props_query = (
203
+ f"MATCH ()-[r:{rel_type}]-() RETURN DISTINCT keys(r) LIMIT 1"
204
+ )
205
+ props_result = self.execute_query(props_query)
206
+ properties = (
207
+ props_result[0][0] if props_result and props_result[0] else []
208
+ )
209
+
210
+ # Format as schema info tuple
211
+ schema_info.append(
212
+ ("relationship", rel_type, {prop: "Unknown" for prop in properties})
213
+ )
214
+ except Exception as e:
215
+ logger.warning(f"Failed to get relationship types: {e}")
216
+
217
+ return schema_info
218
+
219
+ def get_indexes(self) -> List[Dict[str, Any]]:
220
+ """
221
+ Get index information from Memgraph.
222
+
223
+ Returns:
224
+ List of index information dictionaries
225
+ """
226
+ try:
227
+ indexes_result = self.execute_query("SHOW INDEX INFO")
228
+ indexes = []
229
+
230
+ for index_row in indexes_result:
231
+ # Parse index information based on Memgraph's SHOW INDEX INFO format
232
+ if len(index_row) >= 3:
233
+ index_info = {
234
+ "name": index_row[0],
235
+ "type": index_row[1],
236
+ "properties": index_row[2] if len(index_row) > 2 else [],
237
+ }
238
+ indexes.append(index_info)
239
+
240
+ return indexes
241
+ except Exception as e:
242
+ logger.warning(f"Failed to get index information: {e}")
243
+ return []
244
+
245
+ def get_constraints(self) -> List[Dict[str, Any]]:
246
+ """
247
+ Get constraint information from Memgraph.
248
+
249
+ Returns:
250
+ List of constraint information dictionaries
251
+ """
252
+ try:
253
+ constraints_result = self.execute_query("SHOW CONSTRAINT INFO")
254
+ constraints = []
255
+
256
+ for constraint_row in constraints_result:
257
+ # Parse constraint information based on Memgraph's format
258
+ if len(constraint_row) >= 3:
259
+ constraint_info = {
260
+ "name": constraint_row[0],
261
+ "type": constraint_row[1],
262
+ "properties": constraint_row[2]
263
+ if len(constraint_row) > 2
264
+ else [],
265
+ }
266
+ constraints.append(constraint_info)
267
+
268
+ return constraints
269
+ except Exception as e:
270
+ logger.warning(f"Failed to get constraint information: {e}")
271
+ return []
272
+
273
+ def close(self):
274
+ """Close the database connection."""
275
+ if self.connection:
276
+ self.connection.close()
277
+ self.connection = None
278
+ if self.driver:
279
+ self.driver.close()
280
+ self.driver = None
281
+
282
+ logger.info("Memgraph connection closed")
283
+
284
+ def __enter__(self):
285
+ """Context manager entry."""
286
+ self.connect()
287
+ return self
288
+
289
+ def __exit__(self, exc_type, exc_val, exc_tb):
290
+ """Context manager exit."""
291
+ self.close()
292
+
293
+
294
+ def create_memgraph_connection(
295
+ host: str = "localhost",
296
+ port: int = 7687,
297
+ username: str = "",
298
+ password: str = "",
299
+ use_ssl: bool = False,
300
+ ) -> MemgraphAdapter:
301
+ """
302
+ Create a Memgraph connection adapter.
303
+
304
+ Args:
305
+ host: Memgraph host
306
+ port: Memgraph port
307
+ username: Username for authentication
308
+ password: Password for authentication
309
+ use_ssl: Whether to use SSL connection
310
+
311
+ Returns:
312
+ MemgraphAdapter instance
313
+ """
314
+ config = MemgraphConnectionConfig(
315
+ host=host, port=port, username=username, password=password, use_ssl=use_ssl
316
+ )
317
+
318
+ return MemgraphAdapter(config)