graflo 1.3.3__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 (64) hide show
  1. graflo/README.md +18 -0
  2. graflo/__init__.py +70 -0
  3. graflo/architecture/__init__.py +38 -0
  4. graflo/architecture/actor.py +1120 -0
  5. graflo/architecture/actor_util.py +450 -0
  6. graflo/architecture/edge.py +297 -0
  7. graflo/architecture/onto.py +374 -0
  8. graflo/architecture/resource.py +161 -0
  9. graflo/architecture/schema.py +136 -0
  10. graflo/architecture/transform.py +292 -0
  11. graflo/architecture/util.py +93 -0
  12. graflo/architecture/vertex.py +586 -0
  13. graflo/caster.py +655 -0
  14. graflo/cli/__init__.py +14 -0
  15. graflo/cli/ingest.py +194 -0
  16. graflo/cli/manage_dbs.py +197 -0
  17. graflo/cli/plot_schema.py +132 -0
  18. graflo/cli/xml2json.py +93 -0
  19. graflo/data_source/__init__.py +48 -0
  20. graflo/data_source/api.py +339 -0
  21. graflo/data_source/base.py +97 -0
  22. graflo/data_source/factory.py +298 -0
  23. graflo/data_source/file.py +133 -0
  24. graflo/data_source/memory.py +72 -0
  25. graflo/data_source/registry.py +82 -0
  26. graflo/data_source/sql.py +185 -0
  27. graflo/db/__init__.py +44 -0
  28. graflo/db/arango/__init__.py +22 -0
  29. graflo/db/arango/conn.py +1026 -0
  30. graflo/db/arango/query.py +180 -0
  31. graflo/db/arango/util.py +88 -0
  32. graflo/db/conn.py +377 -0
  33. graflo/db/connection/__init__.py +6 -0
  34. graflo/db/connection/config_mapping.py +18 -0
  35. graflo/db/connection/onto.py +688 -0
  36. graflo/db/connection/wsgi.py +29 -0
  37. graflo/db/manager.py +119 -0
  38. graflo/db/neo4j/__init__.py +16 -0
  39. graflo/db/neo4j/conn.py +639 -0
  40. graflo/db/postgres/__init__.py +156 -0
  41. graflo/db/postgres/conn.py +425 -0
  42. graflo/db/postgres/resource_mapping.py +139 -0
  43. graflo/db/postgres/schema_inference.py +245 -0
  44. graflo/db/postgres/types.py +148 -0
  45. graflo/db/tigergraph/__init__.py +9 -0
  46. graflo/db/tigergraph/conn.py +2212 -0
  47. graflo/db/util.py +49 -0
  48. graflo/filter/__init__.py +21 -0
  49. graflo/filter/onto.py +525 -0
  50. graflo/logging.conf +22 -0
  51. graflo/onto.py +190 -0
  52. graflo/plot/__init__.py +17 -0
  53. graflo/plot/plotter.py +556 -0
  54. graflo/util/__init__.py +23 -0
  55. graflo/util/chunker.py +751 -0
  56. graflo/util/merge.py +150 -0
  57. graflo/util/misc.py +37 -0
  58. graflo/util/onto.py +332 -0
  59. graflo/util/transform.py +448 -0
  60. graflo-1.3.3.dist-info/METADATA +190 -0
  61. graflo-1.3.3.dist-info/RECORD +64 -0
  62. graflo-1.3.3.dist-info/WHEEL +4 -0
  63. graflo-1.3.3.dist-info/entry_points.txt +5 -0
  64. graflo-1.3.3.dist-info/licenses/LICENSE +126 -0
@@ -0,0 +1,586 @@
1
+ """Vertex configuration and management for graph databases.
2
+
3
+ This module provides classes and utilities for managing vertices in graph databases.
4
+ It handles vertex configuration, field management, indexing, and filtering operations.
5
+ The module supports both ArangoDB and Neo4j through the DBFlavor enum.
6
+
7
+ Key Components:
8
+ - Vertex: Represents a vertex with its fields and indexes
9
+ - VertexConfig: Manages collections of vertices and their configurations
10
+
11
+ Example:
12
+ >>> vertex = Vertex(name="user", fields=["id", "name"])
13
+ >>> config = VertexConfig(vertices=[vertex])
14
+ >>> fields = config.fields("user", with_aux=True)
15
+ """
16
+
17
+ import dataclasses
18
+ import logging
19
+ from typing import TYPE_CHECKING
20
+
21
+ from graflo.architecture.onto import Index
22
+ from graflo.filter.onto import Expression
23
+ from graflo.onto import BaseDataclass, BaseEnum, DBFlavor
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class FieldType(BaseEnum):
29
+ """Supported field types for graph databases.
30
+
31
+ These types are primarily used for TigerGraph, which requires explicit field types.
32
+ Other databases (ArangoDB, Neo4j) may use different type systems or not require types.
33
+
34
+ Attributes:
35
+ INT: Integer type
36
+ UINT: Unsigned integer type
37
+ FLOAT: Floating point type
38
+ DOUBLE: Double precision floating point type
39
+ BOOL: Boolean type
40
+ STRING: String type
41
+ DATETIME: DateTime type
42
+ """
43
+
44
+ INT = "INT"
45
+ UINT = "UINT"
46
+ FLOAT = "FLOAT"
47
+ DOUBLE = "DOUBLE"
48
+ BOOL = "BOOL"
49
+ STRING = "STRING"
50
+ DATETIME = "DATETIME"
51
+
52
+
53
+ if TYPE_CHECKING:
54
+ # For type checking: after __post_init__, fields is always list[Field]
55
+ # Using string literal to avoid forward reference issues
56
+ _FieldsType = list["Field"]
57
+ # For type checking: after __post_init__, type is always FieldType | None
58
+ _FieldTypeType = FieldType | None
59
+ else:
60
+ # For runtime: accept flexible input types, will be normalized in __post_init__
61
+ _FieldsType = list[str] | list["Field"] | list[dict]
62
+ # For runtime: accept FieldType, str, or None (strings converted in __post_init__)
63
+ _FieldTypeType = FieldType | str | None
64
+
65
+
66
+ @dataclasses.dataclass
67
+ class Field(BaseDataclass):
68
+ """Represents a typed field in a vertex.
69
+
70
+ Field objects behave like strings for backward compatibility. They can be used
71
+ in sets, as dictionary keys, and in string comparisons. The type information
72
+ is preserved for databases that need it (like TigerGraph).
73
+
74
+ Attributes:
75
+ name: Name of the field
76
+ type: Optional type of the field. Can be FieldType enum, str, or None at construction.
77
+ Strings are converted to FieldType enum in __post_init__.
78
+ After initialization, this is always FieldType | None (type checker sees this).
79
+ None is allowed (most databases like ArangoDB don't require types).
80
+ Defaults to None.
81
+ """
82
+
83
+ name: str
84
+ type: _FieldTypeType = None
85
+
86
+ def __post_init__(self):
87
+ """Validate and normalize type if specified."""
88
+ if self.type is not None:
89
+ # Convert string to FieldType enum if it's a string
90
+ if isinstance(self.type, str):
91
+ type_upper = self.type.upper()
92
+ # Validate and convert to FieldType enum
93
+ if type_upper not in FieldType:
94
+ allowed_types = sorted(ft.value for ft in FieldType)
95
+ raise ValueError(
96
+ f"Field type '{self.type}' is not allowed. "
97
+ f"Allowed types are: {', '.join(allowed_types)}"
98
+ )
99
+ self.type = FieldType(type_upper)
100
+ # If it's already a FieldType, validate it's a valid enum member
101
+ elif isinstance(self.type, FieldType):
102
+ # Already a FieldType enum, no conversion needed
103
+ pass
104
+ else:
105
+ allowed_types = sorted(ft.value for ft in FieldType)
106
+ raise ValueError(
107
+ f"Field type must be FieldType enum, str, or None, got {type(self.type)}. "
108
+ f"Allowed types are: {', '.join(allowed_types)}"
109
+ )
110
+
111
+ def __str__(self) -> str:
112
+ """Return field name as string for backward compatibility."""
113
+ return self.name
114
+
115
+ def __repr__(self) -> str:
116
+ """Return representation including type information."""
117
+ if self.type:
118
+ return f"Field(name='{self.name}', type='{self.type}')"
119
+ return f"Field(name='{self.name}')"
120
+
121
+ def __hash__(self) -> int:
122
+ """Hash by name only, allowing Field objects to work in sets and as dict keys."""
123
+ return hash(self.name)
124
+
125
+ def __eq__(self, other) -> bool:
126
+ """Compare equal to strings with same name, or other Field objects with same name."""
127
+ if isinstance(other, Field):
128
+ return self.name == other.name
129
+ if isinstance(other, str):
130
+ return self.name == other
131
+ return False
132
+
133
+ def __ne__(self, other) -> bool:
134
+ """Compare not equal."""
135
+ return not self.__eq__(other)
136
+
137
+ # Field objects are hashable (via __hash__) and comparable to strings (via __eq__)
138
+ # This allows them to work in sets, as dict keys, and in membership tests
139
+
140
+
141
+ @dataclasses.dataclass
142
+ class Vertex(BaseDataclass):
143
+ """Represents a vertex in the graph database.
144
+
145
+ A vertex is a fundamental unit in the graph that can have fields, indexes,
146
+ and filters. Fields can be specified as strings, Field objects, or dicts.
147
+ Internally, fields are stored as Field objects but behave like strings
148
+ for backward compatibility.
149
+
150
+ Attributes:
151
+ name: Name of the vertex
152
+ fields: List of field names (str), Field objects, or dicts.
153
+ Will be normalized to Field objects internally in __post_init__.
154
+ After initialization, this is always list[Field] (type checker sees this).
155
+ fields_aux: List of auxiliary field names for weight passing
156
+ indexes: List of indexes for the vertex
157
+ filters: List of filter expressions
158
+ dbname: Optional database name (defaults to vertex name)
159
+
160
+ Examples:
161
+ >>> # Backward compatible: list of strings
162
+ >>> v1 = Vertex(name="user", fields=["id", "name"])
163
+
164
+ >>> # Typed fields: list of Field objects
165
+ >>> v2 = Vertex(name="user", fields=[
166
+ ... Field(name="id", type="INT"),
167
+ ... Field(name="name", type="STRING")
168
+ ... ])
169
+
170
+ >>> # From dicts (e.g., from YAML/JSON)
171
+ >>> v3 = Vertex(name="user", fields=[
172
+ ... {"name": "id", "type": "INT"},
173
+ ... {"name": "name"} # defaults to None type
174
+ ... ])
175
+ """
176
+
177
+ name: str
178
+ fields: _FieldsType = dataclasses.field(default_factory=list)
179
+ fields_aux: list[str] = dataclasses.field(
180
+ default_factory=list
181
+ ) # temporary field necessary to pass weights to edges
182
+ indexes: list[Index] = dataclasses.field(default_factory=list)
183
+ filters: list[Expression] = dataclasses.field(default_factory=list)
184
+ dbname: str | None = None
185
+
186
+ def _normalize_fields(
187
+ self, fields: list[str] | list[Field] | list[dict]
188
+ ) -> list[Field]:
189
+ """Normalize fields to Field objects.
190
+
191
+ Converts strings, Field objects, or dicts to Field objects.
192
+ Field objects behave like strings for backward compatibility.
193
+
194
+ Args:
195
+ fields: List of strings, Field objects, or dicts
196
+
197
+ Returns:
198
+ list[Field]: Normalized list of Field objects (preserving order)
199
+ """
200
+ normalized = []
201
+ for field in fields:
202
+ if isinstance(field, Field):
203
+ normalized.append(field)
204
+ elif isinstance(field, str):
205
+ # Backward compatibility: string becomes Field with None type
206
+ # (most databases like ArangoDB don't require types)
207
+ normalized.append(Field(name=field, type=None))
208
+ elif isinstance(field, dict):
209
+ # From dict (e.g., from YAML/JSON)
210
+ # Extract name and optional type
211
+ name = field.get("name")
212
+ if name is None:
213
+ raise ValueError(f"Field dict must have 'name' key: {field}")
214
+ field_type = field.get("type")
215
+ normalized.append(Field(name=name, type=field_type))
216
+ else:
217
+ raise TypeError(f"Field must be str, Field, or dict, got {type(field)}")
218
+ return normalized
219
+
220
+ @property
221
+ def field_names(self) -> list[str]:
222
+ """Get list of field names (as strings).
223
+
224
+ Returns:
225
+ list[str]: List of field names
226
+ """
227
+ return [field.name for field in self.fields]
228
+
229
+ @property
230
+ def fields_all(self):
231
+ """Get all fields including auxiliary fields.
232
+
233
+ Returns:
234
+ list[Field]: Combined list of regular and auxiliary fields.
235
+ Field objects behave like strings, so this is backward compatible.
236
+ """
237
+ # fields_aux are still strings, convert to Field objects with None type
238
+ aux_fields = [Field(name=name, type=None) for name in self.fields_aux]
239
+ return self.fields + aux_fields
240
+
241
+ def get_fields_with_defaults(
242
+ self, db_flavor: DBFlavor | None = None, with_aux: bool = False
243
+ ) -> list[Field]:
244
+ """Get fields with default types applied based on database flavor.
245
+
246
+ For TigerGraph, fields with None type will default to "STRING".
247
+ Other databases keep None types as-is.
248
+
249
+ Args:
250
+ db_flavor: Optional database flavor. If None, returns fields as-is.
251
+ with_aux: Whether to include auxiliary fields
252
+
253
+ Returns:
254
+ list[Field]: List of Field objects with default types applied
255
+ """
256
+ fields = self.fields_all if with_aux else self.fields
257
+
258
+ if db_flavor == DBFlavor.TIGERGRAPH:
259
+ # For TigerGraph, default None types to STRING
260
+ return [
261
+ Field(name=f.name, type=f.type if f.type is not None else "STRING")
262
+ for f in fields
263
+ ]
264
+
265
+ # For other databases or None, return fields as-is
266
+ return fields
267
+
268
+ def __post_init__(self):
269
+ """Initialize the vertex after dataclass initialization.
270
+
271
+ Sets the database name if not provided, normalizes fields to Field objects,
272
+ and updates fields based on indexes. Field objects behave like strings,
273
+ maintaining backward compatibility.
274
+ """
275
+ if self.dbname is None:
276
+ self.dbname = self.name
277
+
278
+ # Normalize fields to Field objects (preserve order)
279
+ self.fields = self._normalize_fields(self.fields)
280
+
281
+ # Normalize indexes to Index objects if they're dicts
282
+ normalized_indexes = []
283
+ for idx in self.indexes:
284
+ if isinstance(idx, dict):
285
+ normalized_indexes.append(Index.from_dict(idx))
286
+ else:
287
+ normalized_indexes.append(idx)
288
+ self.indexes = normalized_indexes
289
+
290
+ if not self.indexes:
291
+ # Index expects list[str], but Field objects convert to strings automatically
292
+ # via __str__, so we extract names
293
+ self.indexes = [Index(fields=self.field_names)]
294
+
295
+ # Collect field names from existing fields (preserve order)
296
+ seen_names = {f.name for f in self.fields}
297
+ # Add index fields that aren't already present (preserve original order, append new)
298
+ for idx in self.indexes:
299
+ for field_name in idx.fields:
300
+ if field_name not in seen_names:
301
+ # Add new field, preserving order by adding to end
302
+ self.fields.append(Field(name=field_name, type=None))
303
+ seen_names.add(field_name)
304
+
305
+ def update_aux_fields(self, fields_aux: list):
306
+ """Update auxiliary fields.
307
+
308
+ Args:
309
+ fields_aux: List of new auxiliary fields to add
310
+
311
+ Returns:
312
+ Vertex: Self for method chaining
313
+ """
314
+ self.fields_aux = list(set(self.fields_aux) | set(fields_aux))
315
+ return self
316
+
317
+ @classmethod
318
+ def from_dict(cls, data: dict):
319
+ """Create Vertex from dictionary, handling field normalization.
320
+
321
+ Overrides parent to properly handle fields that may be strings, dicts, or Field objects.
322
+ JSONWizard may incorrectly deserialize dicts in fields, so we need to handle them manually.
323
+
324
+ Args:
325
+ data: Dictionary containing vertex data
326
+
327
+ Returns:
328
+ Vertex: New Vertex instance
329
+ """
330
+ # Extract and preserve fields before JSONWizard processes them
331
+ fields_data = data.get("fields", [])
332
+ # Create a copy without fields to let JSONWizard handle the rest
333
+ data_copy = {k: v for k, v in data.items() if k != "fields"}
334
+
335
+ # Call parent from_dict (JSONWizard)
336
+ instance = super().from_dict(data_copy)
337
+
338
+ # Now manually set fields (could be strings, dicts, or already Field objects)
339
+ # __post_init__ will normalize them properly
340
+ instance.fields = fields_data
341
+ # Trigger normalization again
342
+ instance.fields = instance._normalize_fields(instance.fields)
343
+ return instance
344
+
345
+
346
+ @dataclasses.dataclass
347
+ class VertexConfig(BaseDataclass):
348
+ """Configuration for managing collections of vertices.
349
+
350
+ This class manages a collection of vertices, providing methods for accessing
351
+ and manipulating vertex configurations.
352
+
353
+ Attributes:
354
+ vertices: List of vertex configurations
355
+ blank_vertices: List of blank vertex names
356
+ force_types: Dictionary mapping vertex names to type lists
357
+ db_flavor: Database flavor (ARANGO or NEO4J)
358
+ """
359
+
360
+ vertices: list[Vertex]
361
+ blank_vertices: list[str] = dataclasses.field(default_factory=list)
362
+ force_types: dict[str, list] = dataclasses.field(default_factory=dict)
363
+ db_flavor: DBFlavor = DBFlavor.ARANGO
364
+
365
+ def __post_init__(self):
366
+ """Initialize the vertex configuration.
367
+
368
+ Creates internal mappings and validates blank vertices.
369
+
370
+ Raises:
371
+ ValueError: If blank vertices are not defined in the configuration
372
+ """
373
+ self._vertices_map: dict[str, Vertex] = {
374
+ item.name: item for item in self.vertices
375
+ }
376
+
377
+ # TODO replace by types
378
+ # vertex_collection_name -> [numeric fields]
379
+ self._vcollection_numeric_fields_map = {}
380
+
381
+ if set(self.blank_vertices) - set(self.vertex_set):
382
+ raise ValueError(
383
+ f" Blank collections {self.blank_vertices} are not defined"
384
+ " as vertex collections"
385
+ )
386
+
387
+ @property
388
+ def vertex_set(self):
389
+ """Get set of vertex names.
390
+
391
+ Returns:
392
+ set[str]: Set of vertex names
393
+ """
394
+ return set(self._vertices_map.keys())
395
+
396
+ @property
397
+ def vertex_list(self):
398
+ """Get list of vertex configurations.
399
+
400
+ Returns:
401
+ list[Vertex]: List of vertex configurations
402
+ """
403
+ return list(self._vertices_map.values())
404
+
405
+ def _get_vertex_by_name_or_dbname(self, identifier: str) -> Vertex:
406
+ """Get vertex by name or dbname.
407
+
408
+ Args:
409
+ identifier: Vertex name or dbname
410
+
411
+ Returns:
412
+ Vertex: The vertex object
413
+
414
+ Raises:
415
+ KeyError: If vertex is not found by name or dbname
416
+ """
417
+ # First try by name (most common case)
418
+ if identifier in self._vertices_map:
419
+ return self._vertices_map[identifier]
420
+
421
+ # Try by dbname
422
+ for vertex in self._vertices_map.values():
423
+ if vertex.dbname == identifier:
424
+ return vertex
425
+
426
+ # Not found
427
+ available_names = list(self._vertices_map.keys())
428
+ available_dbnames = [v.dbname for v in self._vertices_map.values()]
429
+ raise KeyError(
430
+ f"Vertex '{identifier}' not found by name or dbname. "
431
+ f"Available names: {available_names}, "
432
+ f"Available dbnames: {available_dbnames}"
433
+ )
434
+
435
+ def vertex_dbname(self, vertex_name):
436
+ """Get database name for a vertex.
437
+
438
+ Args:
439
+ vertex_name: Name of the vertex
440
+
441
+ Returns:
442
+ str: Database name for the vertex
443
+
444
+ Raises:
445
+ KeyError: If vertex is not found
446
+ """
447
+ try:
448
+ value = self._vertices_map[vertex_name].dbname
449
+ except KeyError as e:
450
+ logger.error(
451
+ "Available vertex collections :"
452
+ f" {self._vertices_map.keys()}; vertex collection"
453
+ f" requested : {vertex_name}"
454
+ )
455
+ raise e
456
+ return value
457
+
458
+ def index(self, vertex_name) -> Index:
459
+ """Get primary index for a vertex.
460
+
461
+ Args:
462
+ vertex_name: Name of the vertex
463
+
464
+ Returns:
465
+ Index: Primary index for the vertex
466
+ """
467
+ return self._vertices_map[vertex_name].indexes[0]
468
+
469
+ def indexes(self, vertex_name) -> list[Index]:
470
+ """Get all indexes for a vertex.
471
+
472
+ Args:
473
+ vertex_name: Name of the vertex
474
+
475
+ Returns:
476
+ list[Index]: List of indexes for the vertex
477
+ """
478
+ return self._vertices_map[vertex_name].indexes
479
+
480
+ def fields(
481
+ self,
482
+ vertex_name: str,
483
+ with_aux=False,
484
+ as_names=True,
485
+ db_flavor: DBFlavor | None = None,
486
+ ) -> list[Field]:
487
+ """Get fields for a vertex.
488
+
489
+ Args:
490
+ vertex_name: Name of the vertex or dbname
491
+ with_aux: Whether to include auxiliary fields
492
+ as_names: If True (default), return field names as strings for backward compatibility.
493
+ If False, return Field objects.
494
+ db_flavor: Optional database flavor. If provided, applies default types
495
+ (e.g., TigerGraph defaults None types to "STRING").
496
+
497
+ Returns:
498
+ list[str] | list[Field]: List of field names or Field objects
499
+ """
500
+ # Get vertex by name or dbname
501
+ vertex = self._get_vertex_by_name_or_dbname(vertex_name)
502
+
503
+ # Get fields with defaults applied if db_flavor is provided
504
+ if db_flavor is not None:
505
+ fields = vertex.get_fields_with_defaults(db_flavor, with_aux=with_aux)
506
+ elif with_aux:
507
+ fields = vertex.fields_all
508
+ else:
509
+ fields = vertex.fields
510
+
511
+ if as_names:
512
+ # Return as strings for backward compatibility
513
+ return [field.name for field in fields]
514
+ # Return Field objects
515
+ return fields
516
+
517
+ def numeric_fields_list(self, vertex_name):
518
+ """Get list of numeric fields for a vertex.
519
+
520
+ Args:
521
+ vertex_name: Name of the vertex
522
+
523
+ Returns:
524
+ tuple: Tuple of numeric field names
525
+
526
+ Raises:
527
+ ValueError: If vertex is not defined in config
528
+ """
529
+ if vertex_name in self.vertex_set:
530
+ if vertex_name in self._vcollection_numeric_fields_map:
531
+ return self._vcollection_numeric_fields_map[vertex_name]
532
+ else:
533
+ return ()
534
+ else:
535
+ raise ValueError(
536
+ " Accessing vertex collection numeric fields: vertex"
537
+ f" collection {vertex_name} was not defined in config"
538
+ )
539
+
540
+ def filters(self, vertex_name) -> list[Expression]:
541
+ """Get filter expressions for a vertex.
542
+
543
+ Args:
544
+ vertex_name: Name of the vertex
545
+
546
+ Returns:
547
+ list[Expression]: List of filter expressions
548
+ """
549
+ if vertex_name in self._vertices_map:
550
+ return self._vertices_map[vertex_name].filters
551
+ else:
552
+ return []
553
+
554
+ def update_vertex(self, v: Vertex):
555
+ """Update vertex configuration.
556
+
557
+ Args:
558
+ v: Vertex configuration to update
559
+ """
560
+ self._vertices_map[v.name] = v
561
+
562
+ def __getitem__(self, key: str):
563
+ """Get vertex configuration by name.
564
+
565
+ Args:
566
+ key: Vertex name
567
+
568
+ Returns:
569
+ Vertex: Vertex configuration
570
+
571
+ Raises:
572
+ KeyError: If vertex is not found
573
+ """
574
+ if key in self._vertices_map:
575
+ return self._vertices_map[key]
576
+ else:
577
+ raise KeyError(f"Vertex {key} absent")
578
+
579
+ def __setitem__(self, key: str, value: Vertex):
580
+ """Set vertex configuration by name.
581
+
582
+ Args:
583
+ key: Vertex name
584
+ value: Vertex configuration
585
+ """
586
+ self._vertices_map[key] = value