graflo.architecture.evolution.ops¶
Typed manifest evolution operations.
Two field names recur across the models and mean two different things:
into is where existing names collapse — the target of a merge, a
rename, or an equivalence (MergeVerticesOp, MergeEdgesOp,
VertexEquivalence, RelationEquivalence, PropertyEquivalence);
name is what a new thing is called — an attribute an alignment derives
(AlignmentAttribute, LocalKeySpec). A model never uses one for the
other.
Attributes¶
INGESTION_REWRITING_OPS = frozenset({'add_inverse_edges', 'add_resource_transforms', 'add_resources', 'canonicalize', 'remove_resources', 'ensure_extracted_fields', 'merge_edges', 'merge_vertices', 'project_manifest', 'remove_edge_properties', 'remove_edges', 'remove_vertex_properties', 'remove_vertices', 'rename_edge_properties', 'rename_relations', 'rename_resources', 'rename_vertex_properties', 'rename_vertices', 'replace_identity', 'retarget_edges', 'sanitize', 'set_inverse_emission'})
module-attribute
¶
IdentityBranchSpec = str | list[str]
module-attribute
¶
IdentityTarget = Annotated[NaturalIdentityTarget | HashIdentityTarget | FunnelIdentityTarget | AssignedIdentityTarget | BlankIdentityTarget, PydanticField(discriminator='mode')]
module-attribute
¶
ManifestOp = Annotated[RemoveVerticesOp | AddResourceTransformsOp | EnsureExtractedFieldsOp | AddResourcesOp | RemoveResourcesOp | AddVerticesOp | AddEdgesOp | RetargetEdgesOp | AddSecondaryIdentitiesOp | RemoveSecondaryIdentitiesOp | ReplaceEdgeIdentitiesOp | ChangeFieldTypesOp | AddVertexIndexesOp | RemoveVertexIndexesOp | AddEdgeIndexesOp | RemoveEdgeIndexesOp | SetEdgeDirectedOp | SetBindingsOp | SetDbProfileOp | SetVertexSemanticsOp | SetVertexDescriptionsOp | SetEdgeSemanticsOp | SetFieldSemanticsOp | MergeVerticesOp | CanonicalizeOp | RenameVertexPropertiesOp | RemoveVertexPropertiesOp | AddVertexPropertiesOp | RenameVerticesOp | RenameRelationsOp | RenameResourcesOp | RemoveEdgesOp | MergeEdgesOp | RenameEdgePropertiesOp | RemoveEdgePropertiesOp | AddEdgePropertiesOp | DeclareEdgeInversesOp | RetractEdgeInversesOp | AddInverseEdgesOp | SetNativeInversesOp | SetInverseEmissionOp | ProjectManifestOp | ReplaceIdentityOp | SanitizeOp | MergeManifestsOp, PydanticField(discriminator='op')]
module-attribute
¶
Classes¶
AddEdgeIndexesOp
¶
Bases: ConfigBaseModel
Author secondary indexes on edge physical specs.
Source code in graflo/architecture/evolution/ops.py
AddEdgePropertiesOp
¶
Bases: ConfigBaseModel
Add edge properties for each relation in schema/profile defaults.
An entry may be a bare name (an untyped property) or a full
:class:~graflo.architecture.schema.vertex.Field, as on
:class:AddVertexPropertiesOp, so a typed or grounded edge property is
one replayable step rather than an add followed by a type change.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
additions = PydanticField(..., description='Per-relation edge property additions: ``{relation_name: [field_name | Field, ...]}``.', min_length=1)
class-attribute
instance-attribute
¶
op = 'add_edge_properties'
class-attribute
instance-attribute
¶
Methods:¶
field_names(relation)
¶
The names added to relation, whichever shape they were written in.
Source code in graflo/architecture/evolution/ops.py
AddEdgesOp
¶
Bases: ConfigBaseModel
Introduce new logical edge relations between existing vertex types.
Source code in graflo/architecture/evolution/ops.py
AddInverseEdgesOp
¶
Bases: ConfigBaseModel
Realize declared inverse pairs as explicit logical edges (portable to every backend).
Edges are derived from the relation map: for each directed edge (S, T, r)
whose relation has a declared pair inv, adds the logical edge
(T, S, inv) unless it exists and its physical spec, and sets
emit_inverse on the edge steps that write r, so the same rows write
both. No step is generated: the inverse is mirrored at assembly, after the
relation is resolved, which covers every way a step can name its relation.
A resource that already writes the inverse with a step of its own is left
alone. The pair must be declared first (:class:DeclareEdgeInversesOp);
this op never declares. A symmetric relation has no inverse edge to add --
its edges are undirected. Refused for a relation whose inverse is native
(:class:SetNativeInversesOp), since both would store the same fact.
Withdraw with :class:RemoveEdgesOp on the inverse edges: removing an edge
clears the emit_inverse flags that fed it.
Source code in graflo/architecture/evolution/ops.py
AddResourceTransformsOp
¶
Bases: ConfigBaseModel
Append transform steps to named resources' pipelines.
The first op whose primary effect is ingestion: graph_schema is
untouched. Steps land at the root level of each pipeline unless at
names a deeper one; at whichever level they land, actor type-priority
sorting (transform runs before vertex extraction at the same level) makes
the position safe.
The level is load-bearing rather than cosmetic. An actor reads its
transform buffer at its own LocationIndex with no ancestor fallback,
and a descend subtree runs before its own level's transforms, so a
step appended at the root is invisible to a vertex produced under a
descend — and a transform whose declared inputs are missing skips
silently by default. Target the level that produces the vertex.
Steps may reference a registry transform via call.use (resolved
against the manifest's existing ingestion_model.transforms union the
op's own transforms) or carry a fully inline call
(module + foo + params), which cannot collide by name.
Source code in graflo/architecture/evolution/ops.py
1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 | |
Attributes¶
additions = PydanticField(..., description='Per-resource transform steps to append: ``{resource_name: [step_dict, ...]}``.', min_length=1)
class-attribute
instance-attribute
¶
at = PydanticField(default_factory=dict, description='Per-resource pipeline level to append into: ``{resource_name: [step_index, ...]}``. Each index must address a ``descend`` step, descending one level per element; an omitted or empty path means the root level.')
class-attribute
instance-attribute
¶
op = 'add_resource_transforms'
class-attribute
instance-attribute
¶
transforms = PydanticField(default_factory=list, description='Named transforms to register in ``ingestion_model.transforms`` for steps that reference them via ``call.use``. A name already registered with a different body is an error at apply time.')
class-attribute
instance-attribute
¶
AddResourcesOp
¶
Bases: ConfigBaseModel
Introduce ingestion resources, in the shape the ingestion block accepts.
The unary way to grow ingestion_model: without it a change set could
only ever rename or narrow the resources it started with, and the differ
had to report an added resource as inexpressible.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
op = 'add_resources'
class-attribute
instance-attribute
¶
resources = PydanticField(..., description='Full resource definitions.', min_length=1)
class-attribute
instance-attribute
¶
transforms = PydanticField(default_factory=list, description='Named transforms to register in ``ingestion_model.transforms`` for steps of the new resources that reference them via ``call.use``. Unioned by name exactly as ``add_resource_transforms`` does: an identical body already registered dedupes, a different one is an error at apply time.')
class-attribute
instance-attribute
¶
AddSecondaryIdentitiesOp
¶
Bases: ConfigBaseModel
Declare alternate lookup keys on existing vertices.
Secondary identities are lookup-only: upserts keep using the primary identity.
Each declared field-set automatically gains a non-unique index at
:meth:Schema.finish_init, so this op is how an edge-only source gains a way to
reference endpoints by a business key without touching the primary identity.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
additions = PydanticField(..., description='Per-vertex secondary identities to declare: ``{vertex_name: [{name, fields}, ...]}``. A bare field list is accepted for each entry and auto-named.', min_length=1)
class-attribute
instance-attribute
¶
op = 'add_secondary_identities'
class-attribute
instance-attribute
¶
AddVertexIndexesOp
¶
Bases: ConfigBaseModel
Author secondary indexes on vertices in the database profile.
Source code in graflo/architecture/evolution/ops.py
AddVertexPropertiesOp
¶
Bases: ConfigBaseModel
Add vertex properties to existing logical vertex types.
An entry may be a bare name or a full :class:~graflo.architecture.schema.vertex.Field.
Bare names were the original shape and still mean what they meant -- an
untyped property -- but they cannot express a property that arrives with a
type and a grounding, which is what any op stream that adds a measured or
temporal property has to say in one replayable step.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
additions = PydanticField(..., description='Per-vertex property additions: ``{vertex_name: [field_name | Field, ...]}``.', min_length=1)
class-attribute
instance-attribute
¶
op = 'add_vertex_properties'
class-attribute
instance-attribute
¶
Methods:¶
field_names(vertex)
¶
The names added to vertex, whichever shape they were written in.
AddVerticesOp
¶
Bases: ConfigBaseModel
Introduce new logical vertex types.
The unary counterpart to what :class:MergeManifestsOp can only do binarily.
A replayable change set that cannot introduce a type could only ever describe a
shrinking graph, which is why this exists alongside remove_vertices.
Source code in graflo/architecture/evolution/ops.py
AlignmentAttribute
¶
Bases: ConfigBaseModel
One aligned canonical attribute; list position = funnel priority.
Each entry lowers to one
:class:~graflo.architecture.schema.identity_funnel.IdentityBranch over
name, so the list order is the funnel order. name is what the
derived attribute is called — nothing collapses onto it, which is why it is
not into; into is accepted as a legacy alias.
sources is keyed by resource because derivation inputs are that
resource's raw column names. An entry takes one of three shapes:
- a single :class:
DerivationSpec— the resource produces one member of the cluster, or all its members share the key column; - a list of specs — the resource produces several members, each with
its own key column, and at most one spec yields a value for any
document (the others read an empty column). Lowers to scratch fields
plus a
coalesce_fieldsstep; - a dict keyed by member class — the resource produces several members
and which one a document is must decide the derivation (the members
share a column, or each carries its own marker). The lowering asks the
side manifest how the resource produces each member and guards the step
accordingly (
whenon the router's discriminator, or nothing for a plainvertexstep). A member is keyed by its own name on its side or, throughmerge_manifests, by its canonical name; - a :class:
SharedDerivation— the same dict, spelled once: one call shared by the listed members, with only the parameters that differ.
Behind a vertex_router the first two shapes are guarded as well: the
lowering reads which discriminator values route onto the class and puts
them in when, so the steps run for no other class's documents.
Source code in graflo/architecture/evolution/ops.py
1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 | |
Attributes¶
name = PydanticField(..., validation_alias=AliasChoices('name', 'into'), description='Canonical attribute name on the class; funnel branch id. ``into`` is accepted as a legacy alias.')
class-attribute
instance-attribute
¶
sources = PydanticField(..., min_length=1, description='Per-resource derivation: ``{resource: spec}``; ``{resource: [spec, ...]}`` when the members the resource produces carry different key columns; ``{resource: {member_class: spec}}`` when the member a document is must decide the derivation, or a ``SharedDerivation`` spelling that dict once.')
class-attribute
instance-attribute
¶
Methods:¶
members_for(resource)
¶
Member classes keying resource's specs, or None if unkeyed.
specs_for(resource)
¶
Derivations resource contributes to this attribute, in order.
Source code in graflo/architecture/evolution/ops.py
AssignedIdentityTarget
¶
Bases: ConfigBaseModel
Target an assigned identity: an intentional UUID primary key.
Source code in graflo/architecture/evolution/ops.py
BlankIdentityTarget
¶
Bases: ConfigBaseModel
Target a blank identity: an auto-generated placeholder ID.
Source code in graflo/architecture/evolution/ops.py
CanonicalMap
¶
Bases: ConfigBaseModel
Declared translation of a source vocabulary into canonical names.
A partial function on names, identity where unmapped: vertices maps
source class names to canonical class names, relations does the same
for relation names, and properties maps, per source class name,
source attribute names to canonical attribute names — including for
classes whose name does not change. Two sources sharing a target is a
merge and must be acknowledged with allow_merges.
It is a vocabulary, so it is idempotent: a canonical name is a fixed
point that no entry maps away from. A chain ({X: Z, Z: Q}) or a swap
is refused at construction — that shape is a relabel, which
:class:CanonicalizeOp expresses directly. The rule is what lets two
maps, or a map and an equivalence, be checked for agreement without
asking in which order they were written.
Used on its own through :func:~graflo.architecture.evolution.canonical.canonical_map_to_ops,
and on :attr:MergeManifestsOp.canonical_maps where it names the
merged classes and is checked against the declared equivalences.
Source code in graflo/architecture/evolution/ops.py
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 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 | |
Attributes¶
allow_dangling_entries = PydanticField(default=False, description='Accept entries that name nothing in the manifest the map is applied to, dropping and logging each one. A shared vocabulary map is legitimately broader than any single manifest. Off by default, because a misspelt class has exactly the same shape, and dropping it silently narrows the rename to less than the author asked for.')
class-attribute
instance-attribute
¶
allow_merges = PydanticField(default=False, description='Accept a non-injective ``vertices`` / ``relations`` map. Two sources sharing a canonical target is a *merge*, not a rename; it must be a stated intent because merging fuses entities and can create self-relations.')
class-attribute
instance-attribute
¶
properties = PydanticField(default_factory=dict, description='Per-source-class attribute rename map: ``{source_class: {source_attr: canonical_attr}}``.')
class-attribute
instance-attribute
¶
relation_targets
property
¶
Canonical relation names this map establishes.
relations = PydanticField(default_factory=dict, description='Relation rename map: ``{source_relation: canonical_relation}``.')
class-attribute
instance-attribute
¶
vertex_targets
property
¶
Canonical class names this map establishes (targets of a real rename).
vertices = PydanticField(default_factory=dict, description='Class rename map: ``{source_class: canonical_class}``.')
class-attribute
instance-attribute
¶
Methods:¶
canonical_class(source_class)
¶
canonical_property_names(canonical_class)
¶
Canonical attribute names the map establishes on canonical_class.
Source code in graflo/architecture/evolution/ops.py
canonical_relation(source_relation)
¶
Canonical name of source_relation (itself when unmapped).
CanonicalizeOp
¶
Bases: ConfigBaseModel
Relabel classes, attributes and relations by one vocabulary map, in one step.
The map is a partial function on names — identity where unmapped — applied
simultaneously over the original schema, so a chain ({X: Z, Z: Q}) and
a swap resolve without an intermediate state, and the fibers of the map
are exactly the groups that merge. A target that already exists and does
not move must be declared a member of its own group with a self entry
(Company: Company); otherwise the op refuses rather than merging into
it silently. properties is keyed by the source class name and is
applied before the class relabel.
This is the single lowering of a
:class:~graflo.architecture.evolution.canonical.CanonicalMap, and the
per-side step of
:func:~graflo.architecture.evolution.merge.merge_manifests.
Source code in graflo/architecture/evolution/ops.py
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 | |
Attributes¶
allow_merges = PydanticField(default=False, description='Accept a group of more than one class or relation collapsing onto one target. A merge fuses entities and can create self-relations, so it is acknowledged here rather than inferred from the map.')
class-attribute
instance-attribute
¶
allow_observation_fusion = PydanticField(default=False, description='Accept a merge whose sources are produced in one accumulator slot (the same pipeline level and the same ``role``, or both bare), fusing those observations into one node.')
class-attribute
instance-attribute
¶
allow_self_relations = PydanticField(default=False, description='Accept a merge whose sources are connected by an edge that becomes a self-relation once both endpoints land on the same class.')
class-attribute
instance-attribute
¶
merges
property
¶
Whether any class or relation group has more than one member.
op = 'canonicalize'
class-attribute
instance-attribute
¶
properties = PydanticField(default_factory=dict, description='Per-source-class attribute map: ``{source_class: {source_attr: canonical_attr}}``.')
class-attribute
instance-attribute
¶
relation_groups
property
¶
{target: [members]} over relations, self entries included.
relations = PydanticField(default_factory=dict, description='Relation map: ``{source_relation: canonical_relation}``.')
class-attribute
instance-attribute
¶
vertex_groups
property
¶
{target: [members]} over vertices, self entries included.
vertices = PydanticField(default_factory=dict, description='Class map: ``{source_class: canonical_class}``.')
class-attribute
instance-attribute
¶
ChangeFieldTypesOp
¶
Bases: ConfigBaseModel
Set the logical type of existing vertex or edge properties.
Makes the differ's CHANGE_VERTEX_FIELD_TYPE / CHANGE_EDGE_FIELD_TYPE
authorable. Targets are validated against the profile's db_flavor so an
unsupported type fails here rather than at define time.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
edges = PydanticField(default_factory=dict, description='``{relation_name: {field_name: {type, item_type}}}``.')
class-attribute
instance-attribute
¶
op = 'change_field_types'
class-attribute
instance-attribute
¶
vertices = PydanticField(default_factory=dict, description='``{vertex_name: {field_name: {type, item_type}}}``.')
class-attribute
instance-attribute
¶
DeclareEdgeInversesOp
¶
Bases: ConfigBaseModel
Declare inverse pairs and symmetric relations in edge_config.
Purely logical: it records how relation names read one fact from its two
endpoints and creates nothing. inverses pairs two distinct names; a pair
is unordered, so {a: b}, {b: a} and {a: b, b: a} declare the same
thing. symmetric names relations that are their own inverse. Together
they must give every relation at most one inverse (no a: b with
b: c), in the op and against what is already declared.
Realize a pair with :class:AddInverseEdgesOp (explicit, portable inverse
edges) or :class:SetNativeInversesOp (TigerGraph maintains the pair) --
never both for one relation. A symmetric relation is realized by its edges
being undirected (:class:SetEdgeDirectedOp), which the schema requires.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
inverses = PydanticField(default_factory=dict, description='Pairs to declare: ``{relation: inverse_relation}``, either order.')
class-attribute
instance-attribute
¶
op = 'declare_edge_inverses'
class-attribute
instance-attribute
¶
symmetric = PydanticField(default_factory=list, description='Relations to declare as their own inverse.')
class-attribute
instance-attribute
¶
DerivationSpec
¶
Bases: ConfigBaseModel
How one resource derives a canonical attribute from its raw doc fields.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
foo = PydanticField(default='gated_normalized_key', description='Function name; called as ``foo(*values, **params)``.')
class-attribute
instance-attribute
¶
input = PydanticField(..., min_length=1, description='RAW source-doc field names fed to the function, in order. Documents keep their original keys after property renames, so canonical property names are usually wrong here.')
class-attribute
instance-attribute
¶
module = PydanticField(default='graflo.util.transform', description='Module holding the derivation function.')
class-attribute
instance-attribute
¶
params = PydanticField(default_factory=dict, description='Keyword parameters for the function (gate prefix, ...).')
class-attribute
instance-attribute
¶
EdgeFieldSemanticsTarget
¶
Bases: ConfigBaseModel
One property of one edge triple, and the grounding to put on it.
Edge properties carry the same FieldSemantics as vertex properties --
a since on an edge has a unit as much as a temperature on a vertex
does -- but until this target existed no op could reach them.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
field = PydanticField(..., description='Property name on that edge.')
class-attribute
instance-attribute
¶
relation = PydanticField(default=None, description='Relation name; ``None`` matches the edge with no relation set.')
class-attribute
instance-attribute
¶
semantics = PydanticField(default=None, description='Grounding for the property; ``None`` clears it.')
class-attribute
instance-attribute
¶
source = PydanticField(..., description='Source vertex type name.')
class-attribute
instance-attribute
¶
target = PydanticField(..., description='Target vertex type name.')
class-attribute
instance-attribute
¶
Methods:¶
edge_id()
¶
EdgeIdentitiesEntry
¶
Bases: ConfigBaseModel
New uniqueness keys for one edge triple.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
identities = PydanticField(..., description='Replacement uniqueness keys. Each key lists fields that, together with the resolved endpoints, must be unique; the ``source`` / ``target`` tokens stand for the endpoints themselves. An empty list clears them.')
class-attribute
instance-attribute
¶
relation = PydanticField(default=None, description='Relation name; ``None`` matches the edge with no relation set.')
class-attribute
instance-attribute
¶
source = PydanticField(..., description='Source vertex type name.')
class-attribute
instance-attribute
¶
target = PydanticField(..., description='Target vertex type name.')
class-attribute
instance-attribute
¶
Methods:¶
EdgeIndexEntry
¶
Bases: ConfigBaseModel
Indexes for one edge physical spec.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
fields = PydanticField(default_factory=list, description='Field lists identifying indexes to remove from this spec.')
class-attribute
instance-attribute
¶
indexes = PydanticField(default_factory=list, description='Indexes to add to this spec.')
class-attribute
instance-attribute
¶
purpose = PydanticField(default=None, description='Physical variant purpose; ``None`` addresses the base spec.')
class-attribute
instance-attribute
¶
relation = PydanticField(default=None, description='Relation name; ``None`` matches the edge with no relation set.')
class-attribute
instance-attribute
¶
source = PydanticField(..., description='Source vertex type name.')
class-attribute
instance-attribute
¶
target = PydanticField(..., description='Target vertex type name.')
class-attribute
instance-attribute
¶
Methods:¶
EdgeRetargetEntry
¶
Bases: ConfigBaseModel
Repoint one edge triple at a different source and/or target vertex type.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
new_source = PydanticField(default=None, description='Replacement source vertex type; omit to keep the current one.')
class-attribute
instance-attribute
¶
new_target = PydanticField(default=None, description='Replacement target vertex type; omit to keep the current one.')
class-attribute
instance-attribute
¶
relation = PydanticField(default=None, description='Relation name; ``None`` matches the edge with no relation set.')
class-attribute
instance-attribute
¶
source = PydanticField(..., description='Current source vertex type name.')
class-attribute
instance-attribute
¶
target = PydanticField(..., description='Current target vertex type name.')
class-attribute
instance-attribute
¶
Methods:¶
edge_id()
¶
EdgeSelector
¶
Bases: ConfigBaseModel
Schema edge triple selector matching :data:~graflo.architecture.graph_types.EdgeId.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
relation = PydanticField(default=None, description='Relation name; ``None`` matches edges with no relation set.')
class-attribute
instance-attribute
¶
source = PydanticField(..., description='Source vertex type name.')
class-attribute
instance-attribute
¶
target = PydanticField(..., description='Target vertex type name.')
class-attribute
instance-attribute
¶
Methods:¶
EnsureExtractedFields
¶
Bases: ConfigBaseModel
Fields that must survive extraction for one vertex type at one level.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
at = PydanticField(default_factory=list, description='Pipeline level holding the producing step, as ``descend`` step indices. Empty means the root level.')
class-attribute
instance-attribute
¶
fields = PydanticField(..., min_length=1, description='Property names that must reach the extracted vertex document.')
class-attribute
instance-attribute
¶
vertex = PydanticField(..., description='The vertex type whose extraction must keep ``fields``.')
class-attribute
instance-attribute
¶
EnsureExtractedFieldsOp
¶
Bases: ConfigBaseModel
Widen a producing step's projection so named fields are not dropped.
Needed because a vertex_router delivers differently from a vertex
step. The router builds its child VertexActor at
lindex.extend((role, 0)), where the transform buffer is empty, so
derived fields reach the child only through the merged observation — that
is, through passthrough or from. A plain vertex step instead reads
the buffer directly, which bypasses keep_fields and
extraction_scope entirely.
So on a router, extraction_scope: mapped_only or a keep_fields list
that does not name the fields drops them silently. This op restores them:
keep_fields gains the names, and under mapped_only the per-type
vertex_from_map entry gains identity mappings — seeded from the
router-level from when the entry does not exist yet, since creating it
otherwise replaces the author's projection rather than extending it.
A plain vertex step, or a router that restricts nothing, is a no-op:
the fields already survive.
Source code in graflo/architecture/evolution/ops.py
FieldSemanticsTarget
¶
Bases: ConfigBaseModel
One property of one vertex, and the grounding to put on it.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
field = PydanticField(..., description='Property name on that vertex.')
class-attribute
instance-attribute
¶
semantics = PydanticField(default=None, description='Grounding for the property; ``None`` clears it.')
class-attribute
instance-attribute
¶
vertex = PydanticField(..., description='Vertex type name.')
class-attribute
instance-attribute
¶
Methods:¶
FieldTypeSpec
¶
Bases: ConfigBaseModel
Target logical type for one property.
Source code in graflo/architecture/evolution/ops.py
FunnelIdentityTarget
¶
Bases: ConfigBaseModel
Target an identity funnel: ordered fallback branches digested into id.
The general form of :class:HashIdentityTarget — a flat hash key is a funnel
with one branch. Both resolve to identity mode hash.
Source code in graflo/architecture/evolution/ops.py
HashIdentityTarget
¶
Bases: ConfigBaseModel
Target a hash identity: a deterministic synthetic id digested from fields.
Source code in graflo/architecture/evolution/ops.py
IdentityAlignment
¶
Bases: ConfigBaseModel
Cross-source identity alignment for one canonical class.
attributes order is funnel priority: a record keys by the highest-priority
aligned attribute it carries. Two records fuse when their strongest
present attribute coincides — a match on a lower-priority attribute does
NOT fuse records when one of them also carries a higher-priority one.
Source code in graflo/architecture/evolution/ops.py
2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 | |
Attributes¶
at = PydanticField(default_factory=dict, description='Per-resource pipeline level to derive at, as ``descend`` step indices. Omitted resources resolve to the single level producing ``vertex`` (for member-keyed sources: the level producing the member on its side); supply a path only when a resource produces it at more than one level.')
class-attribute
instance-attribute
¶
attributes = PydanticField(default_factory=list, validation_alias=AliasChoices('attributes', 'rows'), description='Aligned canonical attributes, in priority order. ``rows`` is accepted as a legacy alias.')
class-attribute
instance-attribute
¶
local_key = PydanticField(default=None, description='Fallback identity for records carrying no aligned attribute. Without it such records get no identity and are dropped.')
class-attribute
instance-attribute
¶
secondary_identities = PydanticField(default_factory=dict, description='Retired side keys kept as lookup-only secondary identities: ``{name: [field, ...]}``.')
class-attribute
instance-attribute
¶
vertex = PydanticField(..., description='The canonical class whose identity is being aligned.')
class-attribute
instance-attribute
¶
IdentityReplacement
¶
Bases: ConfigBaseModel
New identity policy for one vertex, plus what becomes of the old one.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
endpoints = PydanticField(default='follow_new', description='How edge steps that match this vertex on its primary identity behave afterwards. ``follow_new`` (default) leaves them on the primary, so they match the new identity. ``pin_to_retired`` rewrites them to select the demoted secondary identity, preserving the previous matching behaviour for sources that only carry the old key. Requires ``retire: demote``.')
class-attribute
instance-attribute
¶
retire = PydanticField(default='demote', description='What happens to the old identity field-set. ``demote`` turns it into a secondary identity (lookup index follows automatically), ``keep`` leaves the fields as plain properties, ``drop`` removes them. Demotion is downgraded to ``keep`` when the old identity was synthetic (hash / assigned / blank) or already equals the new one.')
class-attribute
instance-attribute
¶
retire_as = PydanticField(default=None, description='Name for the demoted secondary identity. Defaults to ``retired_identity``. Only meaningful with ``retire: demote``.')
class-attribute
instance-attribute
¶
to = PydanticField(..., description='The identity policy this vertex should have after the op.')
class-attribute
instance-attribute
¶
LocalKeySource
¶
Bases: ConfigBaseModel
Where one resource's side-local key comes from, and its namespace tag.
The tag is what keeps records of different sources apart once they fail to
fuse: f2 from one source and f2 from another are different
entities, and a:f2 / b:f2 say so. It is required so that opting
out is a statement, not an omission: tag=None (stored as "", the
neutral element, so it survives serialization) keeps the raw value as the
local key with no separator — the author's claim that the values are
already unique across every source of the class (UUIDs, IRIs, ids the
source itself prefixes).
Source code in graflo/architecture/evolution/ops.py
Attributes¶
field = PydanticField(..., description='RAW doc field carrying the side-local key.')
class-attribute
instance-attribute
¶
gate = PydanticField(default=None, description="Optional RAW doc field deciding whether this source applies — the router's discriminator when one resource contributes several local keys. Omit when ``field`` is empty for the other branches, which already selects.")
class-attribute
instance-attribute
¶
gate_prefix = PydanticField(default='', description='Required prefix of the ``gate`` value; ``""`` always passes. Meaningless without ``gate``.')
class-attribute
instance-attribute
¶
tag = PydanticField(..., description='Namespace tag: tag \'a\' turns \'f2\' into \'a:f2\'. ``None`` or ``""`` keeps the raw value, no separator — only for values already unique across every source of the class.')
class-attribute
instance-attribute
¶
LocalKeySpec
¶
Bases: ConfigBaseModel
The canonical fallback identity attribute for non-aligned records.
sources takes the same three shapes as
:attr:AlignmentAttribute.sources: one source, a list (one per member,
each reading its own column), or a dict keyed by member class (the member
decides; the gate is derived from how the resource produces it, so a
member-keyed source must not set gate).
Source code in graflo/architecture/evolution/ops.py
2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 | |
Attributes¶
name = PydanticField(default='local_key', validation_alias=AliasChoices('name', 'into'), description='Canonical fallback property name on the class. ``into`` is accepted as a legacy alias.')
class-attribute
instance-attribute
¶
sep = PydanticField(default=':', description='Separator between tag and key.')
class-attribute
instance-attribute
¶
sources = PydanticField(..., min_length=1, description='Per-resource local-key wiring: ``{resource: source}``; ``{resource: [source, ...]}`` when the members carry different key columns; ``{resource: {member_class: source}}`` when the member decides.')
class-attribute
instance-attribute
¶
Methods:¶
members_for(resource)
¶
Member classes keying resource's sources, or None if unkeyed.
sources_for(resource)
¶
Local-key sources resource contributes, in order.
Source code in graflo/architecture/evolution/ops.py
MergeEdgesOp
¶
Bases: ConfigBaseModel
Merge source relation names into a canonical relation name.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
into = PydanticField(..., description='Canonical relation name that receives all source relations.')
class-attribute
instance-attribute
¶
op = 'merge_edges'
class-attribute
instance-attribute
¶
sources = PydanticField(..., description='Relation names to merge away. Must not include ``into``.', min_length=1)
class-attribute
instance-attribute
¶
MergeManifestsOp
¶
Bases: ConfigBaseModel
Merge two full GraphManifests using explicit equivalence maps.
Binary only — apply via :func:~graflo.architecture.evolution.merge.merge_manifests.
Unary :func:~graflo.architecture.evolution.apply.apply_evolution rejects this op.
Empty vertex_equivalences / relation_equivalences yields a disjoint
union (schema + resources + bindings), subject to name_conflict /
resource_renames.
identity_alignments are applied to the merged union before return
(canonical attributes → resource derivations → priority funnel → secondaries).
Each entry's vertex must be a declared cluster's merged name.
Equivalences name members in the manifests' own vocabulary (a member may
also be spelled by its canonical name when canonical_maps establishes
it); a cluster's merged name is into, else the canonical name its
members map to, else the one spelling they share.
Source code in graflo/architecture/evolution/ops.py
2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 | |
Attributes¶
allow_dangling_entries = PydanticField(default=False, description='Accept canonical map entries that name nothing on the side they are scoped to, dropping and logging each one instead of refusing with the list. Set it on a map itself to say the map is broader than this merge; set it here to say so for both maps at once.')
class-attribute
instance-attribute
¶
allow_merges = PydanticField(default=False, description='Accept a vertex or relation equivalence that collapses more than one class/relation on a side. A merge is a stated intent — it fuses entities and can create self-relations — so it must be acknowledged here rather than inferred from the equivalence list.')
class-attribute
instance-attribute
¶
allow_observation_fusion = PydanticField(default=False, validation_alias=AliasChoices('allow_observation_fusion', 'allow_row_fusion'), description='Accept a merge whose sources are produced in one accumulator slot (the same pipeline level and the same ``role``, or both bare). Forwarded to the per-side ``CanonicalizeOp``. ``allow_row_fusion`` is accepted as a legacy alias.')
class-attribute
instance-attribute
¶
allow_self_relations = PydanticField(default=False, description='Accept a merge whose sources are connected by an edge that becomes a self-relation once both endpoints land on the same merged vertex. Forwarded to the per-side ``MergeVerticesOp``.')
class-attribute
instance-attribute
¶
canonical_maps = PydanticField(default_factory=dict, description="Canonical vocabulary per side. ``left`` / ``right`` apply to that manifest's own names; ``both`` applies to either side and to merged names. Merge applies each side's map together with its equivalences in one step, and refuses when the two disagree on where a name goes.")
class-attribute
instance-attribute
¶
identity_alignments = PydanticField(default_factory=list, description='Optional identity alignments applied after the schema/resource union, one per merged class.')
class-attribute
instance-attribute
¶
name = PydanticField(default=None, description="Label for the merged manifest and its schema. Unset, the two sides' names are folded into ``left+right``.")
class-attribute
instance-attribute
¶
name_conflict = PydanticField(default='error', description='How to handle name collisions no equivalence covers, on the right side (vertices, relations, resources, connectors). Vertex and relation names collide both exactly and when they key alike under ``canonical_key`` -- ``OrderLine`` and ``order_line`` are one concept spelled two ways, and merging them into two unrelated types splits the data silently. ``error`` refuses and names the equivalences to declare; ``prefix_right`` keeps them apart under ``r_`` names; ``union_right`` unions by name -- every exact or near collision becomes a synthesized 1-1 equivalence into the left spelling, so identity and property reconciliation apply exactly as to a declared one. ``union_right`` applies to vertices and relations only (resources and connectors are addresses, not concepts, so it behaves as ``error`` for them). ``fuse_right`` is accepted as a legacy spelling of ``union_right``; `fuse` is otherwise reserved for records becoming one node, not for type names.')
class-attribute
instance-attribute
¶
op = 'merge_manifests'
class-attribute
instance-attribute
¶
relation_equivalences = PydanticField(default_factory=list, validation_alias=AliasChoices('relation_equivalences', 'relations'), description='Optional relation equivalences across the two input manifests. ``relations`` is accepted as a legacy alias.')
class-attribute
instance-attribute
¶
resource_renames = PydanticField(default_factory=dict, description='Rename map applied to *right* resource names before union.')
class-attribute
instance-attribute
¶
target_namespace = PydanticField(default=None, description="Database / graph / space the merged schema deploys into. Supersedes both sides' ``db_profile.target_namespace`` (so it also resolves a disagreement between them) and is validated against the merged ``db_flavor``. Unset, the namespace is derived from the schema name when deployed.")
class-attribute
instance-attribute
¶
vertex_equivalences = PydanticField(default_factory=list, validation_alias=AliasChoices('vertex_equivalences', 'vertices'), description='Explicit vertex equivalences across the two input manifests. ``vertices`` is accepted as a legacy alias.')
class-attribute
instance-attribute
¶
MergeVerticesOp
¶
Bases: ConfigBaseModel
Merge source vertices into a single logical name (schema, edges, ingestion).
Source code in graflo/architecture/evolution/ops.py
Attributes¶
allow_observation_fusion = PydanticField(default=False, validation_alias=AliasChoices('allow_observation_fusion', 'allow_row_fusion'), description='Accept resource pipelines whose sources land in one accumulator slot — the same level and the same ``role``, or both bare. Those steps then write to one slot, fusing into one node what a single source document emitted as two vertex observations. Same-level steps with distinct ``role``s never share a slot and need no acknowledgement. Rejected unless set. ``allow_row_fusion`` is accepted as a legacy alias.')
class-attribute
instance-attribute
¶
allow_self_relations = PydanticField(default=False, description='Accept edges whose endpoints both land on ``into``. A self-relation makes both endpoints share one accumulator slot, so assembly merges observations that were previously separate nodes. Rejected unless set.')
class-attribute
instance-attribute
¶
into = PydanticField(..., description='Resulting vertex type name. If it already exists, source vertices are merged into it. If it does not exist, a new vertex is built from all sources.')
class-attribute
instance-attribute
¶
op = 'merge_vertices'
class-attribute
instance-attribute
¶
sources = PydanticField(..., description='Vertex type names to merge away. Must not include ``into``. Each name must exist in the schema before the merge.', min_length=1)
class-attribute
instance-attribute
¶
NaturalIdentityTarget
¶
Bases: ConfigBaseModel
Target a natural key: the named properties identify the vertex directly.
Source code in graflo/architecture/evolution/ops.py
ProjectManifestOp
¶
Bases: ConfigBaseModel
Project a manifest to a vertex/edge subgraph with consistent cascade.
Keeps only the requested logical vertices and edges (and optionally resources).
All schema, db_profile, ingestion, and bindings references to removed
entities are pruned. Inverse edges are not kept by default; list them in
keep_edges, or set keep_inverse_edges to keep the declared mirror of
every edge that is kept.
With connectivity="induced_prune" (v1 default), when keep_vertices is
set, vertex types from that list with no incident surviving edge are dropped.
depth turns keep_vertices from a literal list into seeds for a
neighbourhood walk. One rule covers every combination: let E be
keep_edges when given and every declared edge otherwise; the survivors are
the vertex types within depth hops of a seed along E under
direction, and then E restricted to surviving endpoints. So the result
is the induced subgraph on the hop ball — an edge between two neighbours
survives even though no walk needed it — and keep_edges bounds the walk
rather than being overridden by it. Pruning is unchanged: a seed left with no
surviving edge is still dropped, whatever the depth.
Edge.by (the third vertex type on an EdgeType.INDIRECT edge) is not part
of schema adjacency, so a walk never pulls it in — the same blind spot the flat
selection already has.
Source code in graflo/architecture/evolution/ops.py
1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 | |
Attributes¶
connectivity = PydanticField(default='induced_prune', description='How to interpret ``keep_vertices`` relative to surviving edges.')
class-attribute
instance-attribute
¶
depth = PydanticField(default=0, ge=0, description='Hops to expand ``keep_vertices`` by before the induced slice. ``0`` (default) keeps the literal list.')
class-attribute
instance-attribute
¶
direction = PydanticField(default=EdgeDirection.ANY, description='Orientation followed when expanding by ``depth``. Edges declared ``directed: false`` are followed both ways regardless. Ignored when ``depth`` is 0.')
class-attribute
instance-attribute
¶
keep_edges = PydanticField(default=None, description='Edge triples ``(source, target, relation)`` to retain.')
class-attribute
instance-attribute
¶
keep_inverse_edges = PydanticField(default=False, description='With ``keep_edges``: also keep the declared mirror ``(T, S, inv)`` of each kept ``(S, T, r)``, so a materialized pair survives as a pair. Without ``keep_edges`` every edge between surviving vertices is kept already.')
class-attribute
instance-attribute
¶
keep_resources = PydanticField(default=None, description='Optional ingestion resource names to retain after graph slice.')
class-attribute
instance-attribute
¶
keep_vertices = PydanticField(default=None, description='Vertex type names to retain (after induced connectivity pruning).')
class-attribute
instance-attribute
¶
op = 'project_manifest'
class-attribute
instance-attribute
¶
strict = PydanticField(default=True, description='When True, unknown vertex/edge selectors raise ``ValueError``.')
class-attribute
instance-attribute
¶
PropertyEquivalence
¶
Bases: ConfigBaseModel
Align a property from the left and/or right member(s) onto a canonical name.
At least one of left / right must be set. A bare string applies to
every member declared on that side of the owning
:class:VertexEquivalence; a {member: field} dict maps per member,
for when members are not aligned under the same source field name.
Exact-name matches do not need a :class:PropertyEquivalence: after
boundary rename, merge_vertex_models unions fields by spelling, so a
property present under the same name on every member fuses for free.
Declare an equivalence only to rename, to pick a different into, or to
flag identity=True.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
identity = PydanticField(default=False, description='When True and ``VertexEquivalence.identity`` is unset, include ``into`` in the derived identity list after merge.')
class-attribute
instance-attribute
¶
into = PydanticField(..., description='Canonical property name on the merged vertex.')
class-attribute
instance-attribute
¶
left = PydanticField(default=None, description='Field name on the left member(s): a bare string applies to every left member of the owning equivalence, a ``{member: field}`` dict maps per member.')
class-attribute
instance-attribute
¶
right = PydanticField(default=None, description='Same shape as ``left``, for the right member(s).')
class-attribute
instance-attribute
¶
RelationEquivalence
¶
Bases: ConfigBaseModel
Collapse one or more left relations and one or more right relations onto one name.
Shares the left / right n-ary shape of :class:VertexEquivalence:
a bare name is a 1-1 equivalence, a list is a merge and requires
MergeManifestsOp.allow_merges=True.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
into = PydanticField(default=None, description='Merged relation name. Omitted, the name comes from the canonical map that maps a member, or from the one spelling every member shares.')
class-attribute
instance-attribute
¶
left = PydanticField(..., description='One or more left relation names.')
class-attribute
instance-attribute
¶
left_members
property
¶
right = PydanticField(..., description='One or more right relation names.')
class-attribute
instance-attribute
¶
right_members
property
¶
Methods:¶
RemoveEdgeIndexesOp
¶
Bases: ConfigBaseModel
Withdraw authored indexes from edge physical specs, addressed by field list.
Source code in graflo/architecture/evolution/ops.py
RemoveEdgePropertiesOp
¶
Bases: ConfigBaseModel
Remove edge properties for each relation across schema/profile/ingestion.
Source code in graflo/architecture/evolution/ops.py
RemoveEdgesOp
¶
Bases: ConfigBaseModel
Remove logical edges from schema, profile, and ingestion selectors.
Two addressing forms, combinable in one op. relations removes a relation
on every endpoint pair it occurs on. edges removes exactly the named
triples, which is the only way to remove one pair of several sharing a
relation, or an edge with no relation set at all.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
edges = PydanticField(default_factory=list, description='Edge triples ``(source, target, relation)`` to remove.')
class-attribute
instance-attribute
¶
op = 'remove_edges'
class-attribute
instance-attribute
¶
relations = PydanticField(default_factory=list, description='Relation names to remove from edge definitions and references.')
class-attribute
instance-attribute
¶
RemoveResourcesOp
¶
Bases: ConfigBaseModel
Remove ingestion resources and the bindings that wired them.
Source code in graflo/architecture/evolution/ops.py
RemoveSecondaryIdentitiesOp
¶
Bases: ConfigBaseModel
Withdraw alternate lookup keys, dropping their derived indexes.
Rejected when a surviving edge step still selects the removed field-set — that step would have no way to resolve its endpoint.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
op = 'remove_secondary_identities'
class-attribute
instance-attribute
¶
removals = PydanticField(..., description='Per-vertex secondary identities to withdraw, addressed by name or by field list: ``{vertex_name: [name | [field, ...], ...]}``.', min_length=1)
class-attribute
instance-attribute
¶
RemoveVertexIndexesOp
¶
Bases: ConfigBaseModel
Withdraw authored vertex indexes, addressed by field list.
Indexes derived from secondary_identities are not removable here — they would
be re-registered by the next finish_init. Use
:class:RemoveSecondaryIdentitiesOp for those.
Source code in graflo/architecture/evolution/ops.py
RemoveVertexPropertiesOp
¶
Bases: ConfigBaseModel
Remove vertex properties and propagate pruning to ingestion/db profile references.
Source code in graflo/architecture/evolution/ops.py
RemoveVerticesOp
¶
Bases: ConfigBaseModel
Remove logical vertices and cascade: edges, ingestion resources, bindings.
Source code in graflo/architecture/evolution/ops.py
RenameEdgePropertiesOp
¶
Bases: ConfigBaseModel
Rename edge properties for each relation across schema/profile/ingestion.
Source code in graflo/architecture/evolution/ops.py
RenameRelationsOp
¶
Bases: ConfigBaseModel
Rename logical edge relation names across schema and ingestion.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
op = 'rename_relations'
class-attribute
instance-attribute
¶
renames = PydanticField(..., validation_alias=AliasChoices('renames', 'relations'), description='Relation rename map: ``{old_relation: new_relation}``. Must be injective. ``relations`` is accepted as a legacy alias.', min_length=1)
class-attribute
instance-attribute
¶
RenameResourcesOp
¶
Bases: ConfigBaseModel
Rename ingestion resource names and bindings references.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
op = 'rename_resources'
class-attribute
instance-attribute
¶
renames = PydanticField(..., validation_alias=AliasChoices('renames', 'resources'), description='Ingestion resource rename map: ``{old_resource: new_resource}``. Must be injective. ``resources`` is accepted as a legacy alias.', min_length=1)
class-attribute
instance-attribute
¶
RenameVertexPropertiesOp
¶
Bases: ConfigBaseModel
Rename vertex properties (and identity references) and propagate to ingestion.
renames maps each vertex name to a per-vertex {old_field: new_field} map.
Schema-side: rewrites Field.name, vertex.identity, and any DB profile
structures that reference field names (vertex_indexes, edge_specs.indexes).
Ingestion-side: rewrites VertexActor.from so the doc still uses the OLD field
name (injecting {new_field: old_field} when missing), rewrites
TransformActor.rename values that target a renamed vertex field, and updates
Resource.extra_weights / edge.vertex_weights (:class:~graflo.architecture.graph_types.Weight
fields, map, and filter keys that address vertex observation columns).
Source code in graflo/architecture/evolution/ops.py
RenameVerticesOp
¶
Bases: ConfigBaseModel
Rename logical vertex names across schema, ingestion, and bindings.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
op = 'rename_vertices'
class-attribute
instance-attribute
¶
renames = PydanticField(..., validation_alias=AliasChoices('renames', 'vertices'), description='Vertex rename map: ``{old_vertex: new_vertex}``. Must be injective. ``vertices`` is accepted as a legacy alias.', min_length=1)
class-attribute
instance-attribute
¶
ReplaceEdgeIdentitiesOp
¶
Bases: ConfigBaseModel
Replace the uniqueness keys of logical edges.
The edge-side counterpart of :class:ReplaceIdentityOp. There is no retire policy:
edge identities have no lookup plane to demote into. Non-endpoint tokens are merged
into edge properties by Edge.finish_init, as with authored identities.
Source code in graflo/architecture/evolution/ops.py
ReplaceIdentityOp
¶
Bases: ConfigBaseModel
Replace the identity policy of one or more vertices.
Covers both a change of identity fields and a change of identity mode
(natural / hash / assigned / blank), because the cascade is the
same in either case: the field-set that upserts changes, and everything that
referenced the old one must be repointed or retired.
Not covered: blank vertices cannot retire by demotion (they cannot declare
secondary identities at all), and a no-op replacement does not bump the version.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
op = 'replace_identity'
class-attribute
instance-attribute
¶
replacements = PydanticField(..., validation_alias=AliasChoices('replacements', 'vertices'), description='Per-vertex identity replacement: ``{vertex_name: replacement}``. ``vertices`` is accepted as a legacy alias.', min_length=1)
class-attribute
instance-attribute
¶
RetargetEdgesOp
¶
Bases: ConfigBaseModel
Change which vertex types an edge connects, preserving everything else.
Remove-plus-add would lose the edge's properties, uniqueness keys, directed
flag, and its db_profile physical spec. Retargeting rewrites the EdgeId
everywhere it is keyed instead: edge config, physical specs, and pipeline edge steps.
Source code in graflo/architecture/evolution/ops.py
RetractEdgeInversesOp
¶
Bases: ConfigBaseModel
Withdraw declared inverses, addressed by relation name.
A paired relation retracts its whole pair (either side names it); a
symmetric relation retracts its own declaration. Refused while a native
inverse still realizes a pair: that would leave the database maintaining a
type the schema no longer names. Explicit inverse edges are ordinary edges
and survive a retraction; so does directed: false.
Source code in graflo/architecture/evolution/ops.py
SanitizeOp
¶
Bases: ConfigBaseModel
Apply DB-flavor-specific name/field sanitization to a manifest.
Merges (in order):
- Storage-name sanitization on
DatabaseProfile(vertex storage names + edge relation names) against the flavor's reserved-words set. - Vertex field rename for fields whose names are reserved words.
- For TigerGraph, normalize identity fields across edges that share a relation (TigerGraph requires consistent source/target indexes per relation).
Source code in graflo/architecture/evolution/ops.py
Attributes¶
db_flavor = PydanticField(..., description='Target database flavor whose reserved words/constraints drive the sanitization.')
class-attribute
instance-attribute
¶
op = 'sanitize'
class-attribute
instance-attribute
¶
reserved_words = PydanticField(default=None, description="Optional override for the flavor's reserved words. When unset, ``graflo.db.util.load_reserved_words(db_flavor)`` is used.")
class-attribute
instance-attribute
¶
SetBindingsOp
¶
Bases: ConfigBaseModel
Replace the whole bindings block.
The bindings block had no op at all, so diff_manifests could only
report it as inexpressible and a change set that touched it was not
replayable -- which is why a merge that unions two bindings registries
could not be recorded as a commit.
Wholesale rather than granular (add/remove/rename a connector) because that is what the diff needs to say: the block became this. The cost is coarseness in a three-way merge -- the whole block is one slot, so two independent bindings edits conflict where granular ops would merge. Granular connector ops can be added later without changing this one's meaning.
Source code in graflo/architecture/evolution/ops.py
SetDbProfileOp
¶
Bases: ConfigBaseModel
Replace the whole db_profile of the schema block.
vertex_indexes and each edge spec's indexes already have four
authoring ops; nothing else on the profile had any, so db_flavor,
target_namespace, vertex_storage_names, default_property_values
and the non-index parts of edge_specs were inexpressible -- and they are
part of the content hash, so a change set that moved one of them could not
replay.
This op carries the whole profile, indexes included, and therefore subsumes the index ops when it is emitted; the differ emits it instead of them rather than alongside, so the two can never fight over ordering. When only indexes differ, the index ops are still what gets emitted -- they say more about intent and merge at a finer slot.
Source code in graflo/architecture/evolution/ops.py
SetEdgeDirectedOp
¶
Bases: ConfigBaseModel
Set the directed flag on logical edges.
Small, but load-bearing for replay: directed decides what
:class:AddInverseEdgesOp is allowed to duplicate, so an un-authorable flag makes
inverse-edge change sets non-replayable.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
directed = PydanticField(..., description='Value applied to every selected edge.')
class-attribute
instance-attribute
¶
edges = PydanticField(..., description='Edge triples whose ``directed`` flag changes.', min_length=1)
class-attribute
instance-attribute
¶
op = 'set_edge_directed'
class-attribute
instance-attribute
¶
SetEdgeSemanticsOp
¶
Bases: ConfigBaseModel
Ground edge relations in an external vocabulary.
The vertex op's counterpart. Relations carry as much meaning as types --
wasDerivedFrom and dependsOn are not interchangeable -- and a
conformance profile that asks whether types are grounded has to be able to
ask it of edges too.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
edges = PydanticField(..., description='Edge triples whose grounding changes.', min_length=1)
class-attribute
instance-attribute
¶
op = 'set_edge_semantics'
class-attribute
instance-attribute
¶
semantics = PydanticField(default=None, description='Grounding applied to every selected edge; ``None`` clears it. Not ``FieldSemantics``: a unit on an edge is meaningless, and the model split is what makes ``unit:`` here a validation error.')
class-attribute
instance-attribute
¶
SetFieldSemanticsOp
¶
Bases: ConfigBaseModel
Ground vertex and edge properties, including their unit of measure.
Takes :class:~graflo.architecture.schema.semantics.FieldSemantics rather
than :class:~graflo.architecture.schema.semantics.Semantics, which is the
entire reason this is a third op rather than a mode of the vertex one: only
a property may carry unit, and the two models are kept apart so that
unit: on a type is a validation error rather than a silent no-op.
A target names either a vertex property (vertex + field) or an
edge property (source / target / relation + field).
Source code in graflo/architecture/evolution/ops.py
SetInverseEmissionOp
¶
Bases: ConfigBaseModel
Set or clear emit_inverse on edge steps, addressed by position.
The ingestion half of a materialized inverse, as a primitive: which steps
mirror the edges they write into the declared inverse. A step is addressed
by resource and :class:EdgeStepRef (at / step / link), so the
op says exactly which steps change and nothing is inferred at replay time.
Enabling is refused where the flag could never write anything -- a step naming exactly one edge whose relation has no declared pair, is symmetric, or has no declared inverse edge. A step whose relation comes from the data is accepted; it mirrors per document what has a materialized inverse.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
enabled = PydanticField(default=True, description='``False`` clears the flag.')
class-attribute
instance-attribute
¶
op = 'set_inverse_emission'
class-attribute
instance-attribute
¶
steps = PydanticField(..., description='Edge steps whose flag changes: ``{resource: [step ref, ...]}``.', min_length=1)
class-attribute
instance-attribute
¶
SetNativeInversesOp
¶
Bases: ConfigBaseModel
Have the database maintain declared inverses (TigerGraph WITH REVERSE_EDGE).
Physical, not logical: adds relations to, or removes them from,
db_profile.native_inverses. Keyed by relation, as TigerGraph is: the
reverse type belongs to the edge type, which spans every (S, T) pair of
the relation. The reverse type is named by the declared pair, so the pair
must be declared (:class:DeclareEdgeInversesOp). Refused for symmetric
relations, where explicit inverse edges exist, on undirected edges, for a
relation stored under several physical names, and on non-TigerGraph
profiles.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
enabled = PydanticField(default=True, description='``False`` withdraws the native inverse.')
class-attribute
instance-attribute
¶
op = 'set_native_inverses'
class-attribute
instance-attribute
¶
relations = PydanticField(..., description='Paired relations whose declared inverse the database maintains.', min_length=1)
class-attribute
instance-attribute
¶
SetVertexDescriptionsOp
¶
Bases: ConfigBaseModel
Set or clear the human-readable description of existing vertex types.
A description was authorable only when a type was first written, so a change to one -- a merge folding two types together and joining what each side said about it, or a correction to what an inferred type means -- had no operation and could only be diffed as inexpressible. Like grounding, a description is never consulted at execution time: this op cannot change how anything ingests or stores.
Source code in graflo/architecture/evolution/ops.py
SetVertexSemanticsOp
¶
Bases: ConfigBaseModel
Ground vertex types in an external vocabulary.
Semantics were authorable only when a type was first written: no operation
could attach an iri to a type that already existed, so a manifest that
arrived ungrounded — an inferred one, or anything predating the block — could
never be grounded through the op system, only rewritten by hand.
Grounding is purely additive and never consulted at execution time, so this op cannot change how anything ingests or stores. What it changes is whether a reader who did not author the schema can tell what a type denotes.
Source code in graflo/architecture/evolution/ops.py
SharedDerivation
¶
Bases: ConfigBaseModel
One derivation shared by several members, varying only in parameters.
The compact spelling of the member-keyed form for the common case: the
call is the same for every member and only a parameter changes — a marker
prefix per class — or nothing does. members is a list of member
classes, or a dict from member to the parameters that differ; each
member's derivation is spec with those parameters laid over
spec.params. Anything else that differs between members — the input
columns, the function — is a different derivation: spell it with the
explicit {member: spec} dict.
Expands to that dict; the lowering never sees this model.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
members = PydanticField(..., description='Member classes sharing ``spec``: a list when nothing varies, or ``{member: {param: value}}`` naming what does.')
class-attribute
instance-attribute
¶
spec = PydanticField(..., description='The derivation every member shares.')
class-attribute
instance-attribute
¶
Methods:¶
expand()
¶
The explicit {member: spec} dict this stands for.
Source code in graflo/architecture/evolution/ops.py
SideIdentity
¶
Bases: ConfigBaseModel
Per-side/per-member shorthand for a cluster's merged identity funnel.
Each entry is one funnel branch: a single canonical attribute, or an
ordered composite (list[str]). left / right supply the default
branch chain for every member declared on that side; members overrides
it for specific member classes, keyed by the member's own or canonical
name. Every chain is merged into one global branch order — see
:func:~graflo.architecture.evolution.merge.side_identity_to_funnel —
so declaring the same relative order on every member is required; two
members disagreeing on the order of two branches raises.
Source code in graflo/architecture/evolution/ops.py
Attributes¶
left = PydanticField(default=None, description='Default ordered branch chain for every left member.')
class-attribute
instance-attribute
¶
members = PydanticField(default_factory=dict, description='Per-member branch chain, overriding the side default.')
class-attribute
instance-attribute
¶
right = PydanticField(default=None, description='Default ordered branch chain for every right member.')
class-attribute
instance-attribute
¶
VertexEquivalence
¶
Bases: ConfigBaseModel
Collapse one or more left classes and one or more right classes into one.
GraFlo applies this map deterministically; it does not infer semantic
matches. left / right accept a bare class name (a 1-1 equivalence)
or a list (an n-ary cluster): {Company, Shop} ~ {Org, Branch} ->
Company. Declaring more than one member on a side is a merge and
requires MergeManifestsOp.allow_merges=True.
Properties with the same spelling on every member after alignment fuse by
exact name without an entry in properties — list only renames and
identity-flagged fields.
Source code in graflo/architecture/evolution/ops.py
2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 | |
Attributes¶
identity = PydanticField(default=None, description='Optional explicit merged identity, in canonical attribute names (after alignment): a natural key, an explicit funnel, or a `SideIdentity` shorthand lowered to one funnel. When unset, identity is carried through only if every member agrees after alignment (plus any `PropertyEquivalence.identity` flags); disagreement with nothing declared raises `MergeIdentityError`.')
class-attribute
instance-attribute
¶
into = PydanticField(default=None, description="Merged vertex type name (may equal a member's name, or be a new name). Omitted, the name comes from the canonical map that maps a member, or from the one spelling every member shares.")
class-attribute
instance-attribute
¶
left = PydanticField(..., description='One or more left-manifest vertex type names.')
class-attribute
instance-attribute
¶
left_members
property
¶
properties = PydanticField(default_factory=list, description='Property alignment map applied before the vertex merge.')
class-attribute
instance-attribute
¶
retire = PydanticField(default='demote', description="What becomes of each member's pre-merge identity fields when `identity` is declared. `demote` keeps them as lookup-only secondary identities on `into`; `keep` drops them. Unused when `identity` is unset.")
class-attribute
instance-attribute
¶
right = PydanticField(..., description='One or more right-manifest vertex type names.')
class-attribute
instance-attribute
¶
right_members
property
¶
Methods:¶
members(side)
¶
property_maps(side)
¶
{member: {old_field: into_field}} for side, bare strings expanded.
Source code in graflo/architecture/evolution/ops.py
Functions:¶
ops_reaching_ingestion(ops)
¶
Names of ops whose effect extends into ingestion_model, in order.
Source code in graflo/architecture/evolution/ops.py
validate_merge_sources(sources, into, *, kind)
¶
Reject a merge whose sources repeat or include the target.
The docstrings promise both; enforcing them at parse time means a serialized change set fails where it is read rather than where it is replayed, after the ops before it have already been applied.
Source code in graflo/architecture/evolution/ops.py
validate_rename_map_is_injective(renames, *, kind, merge_hint)
¶
Reject a rename map that would collapse two names onto one.
A rename is a relabelling: it must not change how many types exist. Two sources
sharing a target is a merge, and the merge ops exist precisely because merging
needs decisions a rename cannot express — which properties survive, how identity
combines, what happens to edges that become self-loops. Left unchecked the
collapse is silent: the name-keyed lookup maps in VertexConfig / EdgeConfig
keep the last definition and the earlier one is shadowed but still serialized.
Source code in graflo/architecture/evolution/ops.py
validate_vocabulary_is_idempotent(mapping, *, kind)
¶
Reject a chain or a swap: a canonical vocabulary has fixed points.
A name that is both a source that moves and a target of another entry
({X: Z, Z: Q}, {A: B, B: A}) makes the map non-idempotent — applying
it twice is not applying it once — so it cannot be read as a vocabulary,
where a canonical name is by definition one nothing maps away from. Such a
map is a relabel, which :class:CanonicalizeOp expresses (simultaneous
application over the original schema).
Source code in graflo/architecture/evolution/ops.py
validate_vocabulary_map(vertices, relations, properties, *, allow_merges, kind, merge_hint)
¶
Reject an unacknowledged collapse in a vocabulary map.
A vocabulary map is a function on names, so its groups are its fibers:
every source of one target, including a self entry t: t that
declares an existing t a member of its own group. A group of more than
one name is a merge — it fuses entities and can create self-relations — so
it must be acknowledged rather than inferred from the map. Per-class
attribute maps are plain renames and must be injective outright.
Source code in graflo/architecture/evolution/ops.py
vocabulary_groups(mapping)
¶
{target: [sources]} — the fibers of a vocabulary map, self entries included.