graflo.architecture.schema.edge¶
Edge configuration and management for graph databases.
This module provides classes and utilities for managing edges in graph databases. It handles edge configuration, weight management, indexing, and relationship operations. The module supports both ArangoDB and Neo4j through the DBType enum.
Key Components
- Edge: Abstract graph edge kind (schema /
edge_configonly) - EdgeDerivation: Ingestion wiring (see
graflo.architecture.graph_types.edge_derivation) - EdgeConfig: Manages collections of edges and their configurations
- WeightConfig: DTO for DB projection helpers (e.g. effective weights); schema uses
properties
Direction semantics
Edge.directed is a statement about the model, not about storage: when
false, endpoint order carries no meaning and the two orientations denote one
relationship. Backends express that to very different degrees, and only
TigerGraph has an undirected edge type. What the flag costs elsewhere is a
read-path question — reaching an edge from its target endpoint — and the
per-backend answer is one table,
:mod:graflo.architecture.schema.edge_direction. Per-schema diagnostics
built on it are reported at schema-apply time by
:mod:graflo.db.edge_direction_support.
Example
edge = Edge(source="user", target="post") config = EdgeConfig(edges=[edge]) edge.finish_init(vertex_config=vertex_config)
Attributes¶
DEFAULT_TIGERGRAPH_RELATION = 'relates'
module-attribute
¶
DEFAULT_TIGERGRAPH_RELATION_WEIGHTNAME = 'relation'
module-attribute
¶
Classes¶
Edge
¶
Bases: ConfigBaseModel
Abstract graph edge kind (schema / edge_config only).
Ingestion-only behavior (location filters, relation column, relation from
key, etc.) belongs on :class:~graflo.architecture.graph_types.edge_derivation.EdgeDerivation
in pipeline edge steps, not on this model.
.. note::
identities keys are endpoint-order-sensitive even when directed
is false: the source and target tokens resolve positionally, so
(a, b) and (b, a) count as two identities on an edge whose whole
premise is that they are one. Canonical endpoint ordering for undirected
identity keys is not implemented — declare such edges from a consistent
side until it is.
Source code in graflo/architecture/schema/edge.py
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | |
Attributes¶
by = PydanticField(default=None, description='For INDIRECT edges: vertex type name used to define the edge.')
class-attribute
instance-attribute
¶
description = PydanticField(default=None, description='Optional semantic description of edge intent, direction semantics, and business meaning.')
class-attribute
instance-attribute
¶
directed = PydanticField(default=True, description='When True (default), source→target order is meaningful. When False, the edge is logically undirected: the two orientations denote one relationship, so inverse-edge ops must not duplicate it and traversal may follow it either way. This asserts something about the model, not about storage — only TigerGraph has an undirected edge type, and every other backend honours it to the extent it can (see ``graflo.db.edge_direction_support``). An undirected edge has no declared inverse, and so no native inverse either.')
class-attribute
instance-attribute
¶
edge_id
property
¶
Alias for edge_id.
edge_name_dyad
property
¶
Get the edge name as a dyad (source, target).
Returns:
| Type | Description |
|---|---|
|
tuple[str, str]: Source and target vertex names |
identities = PydanticField(default_factory=list, description='Logical uniqueness keys for this edge: each key names fields that, together with the resolved source and target vertex ids, must be unique (``source`` / ``target`` tokens stand for endpoints; other tokens are edge attributes). Multiple keys define multiple uniqueness constraints. Non-endpoint tokens are merged into ``properties`` during :meth:`finish_init` if not already declared (same idea as vertex identity).')
class-attribute
instance-attribute
¶
properties = PydanticField(default_factory=list, description='Edge property names/types (relationship properties). Vertex-derived bindings belong in ingestion (:class:`~graflo.architecture.contract.runtime.edge_derivation.EdgeDerivationRegistry`).')
class-attribute
instance-attribute
¶
property_names
property
¶
Declared materialized edge property names.
relation = PydanticField(default=None, description='Relation/edge type name (e.g. Neo4j relationship type). For ArangoDB used as weight.')
class-attribute
instance-attribute
¶
semantics = PydanticField(default=None, description='Optional external-vocabulary anchors for this edge type.')
class-attribute
instance-attribute
¶
source = PydanticField(..., description='Source vertex type name (e.g. user, company).')
class-attribute
instance-attribute
¶
target = PydanticField(..., description='Target vertex type name (e.g. post, company).')
class-attribute
instance-attribute
¶
type = PydanticField(default=EdgeType.DIRECT, description='Edge type: DIRECT (created during ingestion) or INDIRECT (pre-existing collection).')
class-attribute
instance-attribute
¶
Methods:¶
finish_init(vertex_config)
¶
Complete logical edge initialization with vertex configuration.
fold_duplicate_properties()
¶
Fold properties declared twice, refusing an incompatible redeclaration.
_normalize_direct_item maps each authored entry independently, so
properties: ["tags", {name: tags, type: LIST, item_type: STRING}]
yields two fields of one name and nothing downstream collapses them --
they reach DDL emission as two attributes. Vertices have folded
duplicates since they gained typed properties; edges are brought to the
same rule here, with the same merge and the same refusals.
Source code in graflo/architecture/schema/edge.py
normalize_identities(v)
classmethod
¶
Source code in graflo/architecture/schema/edge.py
normalize_identity_keys()
¶
Source code in graflo/architecture/schema/edge.py
normalize_properties(v)
classmethod
¶
EdgeConfig
¶
Bases: ConfigBaseModel
Configuration for managing collections of edges.
This class manages a collection of edges, providing methods for accessing and manipulating edge configurations.
Attributes:
| Name | Type | Description |
|---|---|---|
edges |
list[Edge]
|
List of edge configurations |
inverses |
list[EdgeInverse]
|
Declared inverse relation pairs (see :class: |
symmetric |
list[str]
|
Declared symmetric relations (each its own inverse) |
Source code in graflo/architecture/schema/edge.py
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 | |
Attributes¶
edges = PydanticField(default_factory=list, description='List of edge definitions (source, target, identities, properties, relation, etc.).')
class-attribute
instance-attribute
¶
inverses = PydanticField(default_factory=list, description='Declared inverse relation pairs, unordered. Logical only: a pair is realized either by explicit inverse edges or by a native inverse on the db_profile.')
class-attribute
instance-attribute
¶
symmetric = PydanticField(default_factory=list, description='Declared symmetric relations: each is its own inverse. Logical only: every edge naming one must be `directed: false`, which is its realization.')
class-attribute
instance-attribute
¶
vertices
property
¶
Get set of vertex names involved in edges.
Returns:
| Type | Description |
|---|---|
|
set[str]: Set of vertex names |
Methods:¶
__contains__(item)
¶
Check if edge exists in configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item
|
EdgeId | Edge
|
Edge ID or Edge instance to check |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
True if edge exists, False otherwise |
Source code in graflo/architecture/schema/edge.py
directed_for(relation)
¶
The directed value a new edge of relation must take to agree with the rest.
Follows the declared edges of the relation, then a symmetric
declaration, and defaults to directed.
Source code in graflo/architecture/schema/edge.py
edge_for(edge_id)
¶
Return the config-owned :class:Edge instance for edge_id after merges.
Pipeline actors may construct a partial :class:Edge that is merged into the
schema edge via :meth:update_edges. Callers that need properties, identities,
etc. must use this object (same reference as in :meth:items), not the
pre-merge actor copy.
Source code in graflo/architecture/schema/edge.py
finish_init(vc)
¶
Complete initialization of all logical edges.
inverse_advisories()
¶
Non-fatal findings about how declared pairs are realized as explicit edges.
Realizing a pair is optional, so nothing here is an error. What is worth
reporting is a realization that no longer matches itself: an inverse
edge whose properties or identity keys drifted from its forward edge, a
relation realized for some endpoint pairs but not others, and a pair
read from the same side ((S, T, a) next to (S, T, b)), which
usually means one of the two was modeled backwards.
The strings render the typed records of
:func:graflo.architecture.schema.inverse_realization.edge_inverse_findings,
which carry the relations, edges and severity a caller can act on.
Source code in graflo/architecture/schema/edge.py
inverse_of(relation)
¶
Declared inverse of relation: its pair partner, itself if symmetric, else None.
Source code in graflo/architecture/schema/edge.py
is_symmetric(relation)
¶
items()
¶
update_edges(edge, vertex_config)
¶
Update edge configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
edge
|
Edge
|
Edge configuration to update |
required |
vertex_config
|
VertexConfig
|
Vertex configuration |
required |
Source code in graflo/architecture/schema/edge.py
validate_directedness()
¶
Refuse a relation whose edges disagree on directed.
directed: false and symmetric state one fact at two
granularities -- per edge and per relation -- and they can only agree if
a relation is undirected everywhere or nowhere. A backend whose edge
type spans every endpoint pair of a relation cannot express the mix
either: it would need one type name both directed and undirected.
Raises:
| Type | Description |
|---|---|
ValueError
|
naming each mixed relation and its edges. |
Source code in graflo/architecture/schema/edge.py
validate_inverses()
¶
Check the declared inverses against the declared edges.
Raises:
| Type | Description |
|---|---|
ValueError
|
when a declaration names no edge at all, when a paired relation names an undirected edge (an undirected relationship already reads both ways), or when a symmetric relation names a directed edge (symmetric means both orientations are one fact). |
Source code in graflo/architecture/schema/edge.py
values()
¶
with_edges(edges)
¶
A config over edges that keeps the declared inverses.
Pairs whose two relations both vanished, and symmetric relations that vanished, are dropped: the declaration is about relations, and one naming nothing is dangling. Every rebuild of an edge config inside the evolution machinery must go through here, or the table is silently lost.
Source code in graflo/architecture/schema/edge.py
EdgeInverse
¶
Bases: ConfigBaseModel
Declared inverse pair: two relation names reading one fact from its two endpoints.
For every directed edge (S, T, a) the inverse is (T, S, b), and for
every (S, T, b) it is (T, S, a): the pair is unordered, so
{relation: a, inverse: b} and {relation: b, inverse: a} state the same
thing, and the table stores each pair once, its names in sorted order. A
relation that is its own inverse is symmetric and is declared in
edge_config.symmetric instead.
The declaration is purely logical and creates nothing. It is realized in one of two ways, never both for one relation:
- inverse edges: explicit logical edges
(T, S, b)inedge_config.edges, portable to every backend (add_inverse_edges); - a native inverse:
db_profile.native_inverses, where the database maintains the paired type itself (TigerGraph only).
Source code in graflo/architecture/schema/edge.py
Functions:¶
inverse_map(inverses, symmetric=())
¶
inv as a dict: both directions of every pair, and name -> name if symmetric.
Source code in graflo/architecture/schema/edge.py
normalize_inverse_table(pairs, symmetric, *, kind)
¶
The one consistency rule for declared inverses, and their canonical form.
Pairs and symmetric names together define inv over relation names, which
must be a function: every name has at most one inverse. Restating a pair in
either order, or a symmetric name twice, is the same statement and is kept
once. A chain (a-b with b-c), a name both paired and symmetric, or a
name paired with itself is refused.
Returns:
| Type | Description |
|---|---|
list[EdgeInverse]
|
Pairs sorted, each with its two names in sorted order, and the sorted |
list[str]
|
symmetric names -- so two tables stating the same map compare equal. |
Raises:
| Type | Description |
|---|---|
ValueError
|
naming every relation that would get two inverses. |
Source code in graflo/architecture/schema/edge.py
remap_inverses(inverses, symmetric, relation_map, *, kind)
¶
Carry the declared inverses through a relation rename or merge.
A merge (non-injective map) can collapse a pair onto one name. That is
refused rather than read as "now symmetric": merging a relation with its
inverse discards which way each edge was read, it does not make the
relationship symmetric. A merge that gives one name two inverses is refused
by :func:normalize_inverse_table.
Raises:
| Type | Description |
|---|---|
ValueError
|
naming the pairs the remap would corrupt. |
Source code in graflo/architecture/schema/edge.py
union_inverses(left, right)
¶
Union two declared inverse tables, refusing a relation with two inverses.
Raises:
| Type | Description |
|---|---|
ValueError
|
when the sides declare different inverses for one relation. |