graflo.architecture.schema.inverse_realization¶
How declared inverses are realized, and what is wrong with a realization.
A declared pair {a, b} (edge_config.inverses) is a statement about
relation names only, and it is already useful as it stands: b is a name for
reading a from its target, and a read resolves it to a reverse traversal of
the a edge. Nothing needs storing for that.
Storing the reverse reading is a separate, optional step -- a realization -- and a pair has at most one:
native
The database maintains the reverse type itself
(db_profile.native_inverses; TigerGraph WITH REVERSE_EDGE).
materialized
(T, S, b) is an ordinary declared edge, fed at ingestion.
This module reports the state of every declared pair -- declared when
nothing realizes it, native or materialized when something does, and the
two diagnostic states partial and conflicting -- and what it found as
typed :class:InverseFinding records. Nothing here raises and nothing mutates:
the same checks back the load-time refusals (which raise), the string
advisories, and the audit, so the three cannot drift apart.
Everything works on a schema that has not been through finish_init, so
a manifest that no longer loads can still be inspected.
Attributes¶
FindingSeverity = Literal['repairable', 'conflict', 'note']
module-attribute
¶
repairable: one side under-reports what the other states, and propagating
it cannot change meaning. conflict: the two sides contradict each other, and
only the author can say which is right. note: nothing is wrong.
NativeInverseCode = Literal['not_tigergraph', 'no_edge', 'symmetric', 'undeclared', 'both_sides', 'collides_with_edges', 'collides_with_vertex', 'split_physical_names']
module-attribute
¶
PairState = Literal['declared', 'native', 'materialized', 'partial', 'conflicting']
module-attribute
¶
declared: the pair is declared and nothing realizes it, which is the
default and needs no remedy. native / materialized: how it is realized.
partial: mirrored for some endpoint pairs only. conflicting: touched by
a conflict finding.
__all__ = ['FindingSeverity', 'InverseFinding', 'NativeInverseCode', 'NativeInverseViolation', 'PairRealization', 'PairState', 'ResolvedRelation', 'StepAddress', 'edge_inverse_findings', 'inverse_emission_refusal', 'materialized_inverse_id', 'mixed_directed_relations', 'native_inverse_violations', 'pair_realizations', 'resolve_relation', 'schema_inverse_findings']
module-attribute
¶
Classes¶
InverseFinding
¶
Bases: ConfigBaseModel
One thing worth knowing about how declared inverses are realized.
Source code in graflo/architecture/schema/inverse_realization.py
Attributes¶
detail = PydanticField(default_factory=dict)
class-attribute
instance-attribute
¶
edges = PydanticField(default_factory=list)
class-attribute
instance-attribute
¶
key
property
¶
Identity of the finding, for comparing two reports of one manifest.
kind = PydanticField(..., description='Stable identifier of the finding.')
class-attribute
instance-attribute
¶
message
instance-attribute
¶
relations = PydanticField(default_factory=list)
class-attribute
instance-attribute
¶
severity
instance-attribute
¶
steps = PydanticField(default_factory=list, description='Ingestion steps involved; empty for a schema-only finding.')
class-attribute
instance-attribute
¶
NativeInverseViolation
¶
Bases: ConfigBaseModel
One rule a native inverse breaks, for one relation.
Source code in graflo/architecture/schema/inverse_realization.py
PairRealization
¶
Bases: ConfigBaseModel
How one declared pair is realized in a schema.
Source code in graflo/architecture/schema/inverse_realization.py
Attributes¶
inverse
instance-attribute
¶
mirrored = PydanticField(default=0, description='Directed edges of the pair whose mirror is declared.')
class-attribute
instance-attribute
¶
native_side = PydanticField(default=None, description='The relation listed in ``db_profile.native_inverses``, if any.')
class-attribute
instance-attribute
¶
relation
instance-attribute
¶
state
instance-attribute
¶
stored_sides = PydanticField(default_factory=list, description='Relations of the pair that name at least one declared edge.')
class-attribute
instance-attribute
¶
total = PydanticField(default=0, description='Directed edges naming either relation of the pair.')
class-attribute
instance-attribute
¶
ResolvedRelation
¶
Bases: ConfigBaseModel
One declared edge that answers a read of a relation name.
reversed means the name is the declared inverse of the edge's relation
and nothing is stored under it, so the read follows the edge from its
target: the caller flips the direction it was asked for.
Source code in graflo/architecture/schema/inverse_realization.py
StepAddress
¶
Bases: ConfigBaseModel
Where an edge step sits: a resource, a path into its pipeline, a position.
at descends through nested pipelines by step index, step is the
index within the pipeline reached, and link selects one entry of a
step's links list.
Source code in graflo/architecture/schema/inverse_realization.py
Functions:¶
edge_inverse_findings(edge_config)
¶
Findings that need only the logical edges and the inverse table.
Covers what the load-time checks refuse (so an unloadable config can still be described) and what they let through because realizing a pair is optional: drift between an edge and its mirror, a relation mirrored for some endpoint pairs only, and a pair read from the same side.
Source code in graflo/architecture/schema/inverse_realization.py
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 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 | |
inverse_emission_refusal(edge_config, edge_id)
¶
Why an edge step writing exactly edge_id cannot set emit_inverse, or None.
Applies to a step whose endpoints and relation are all fixed, so it names one edge and everything can be decided from the schema: the relation needs a declared pair (a symmetric relation has no inverse edge -- its edges are undirected), and the inverse must be materialized.
Source code in graflo/architecture/schema/inverse_realization.py
materialized_inverse_id(edge_config, edge_id)
¶
The declared edge that mirroring edge_id writes into, or None.
(s, t, a) mirrors into (t, s, b) when a is paired with b and
that edge -- or a relation-less template between the same types -- is
declared. That second condition is what materialized means: a pair that is
only declared, or that the database maintains, has nothing to write into.
Source code in graflo/architecture/schema/inverse_realization.py
mixed_directed_relations(edge_config)
¶
Relations whose edges disagree on directed, with every edge of each.
Source code in graflo/architecture/schema/inverse_realization.py
native_inverse_violations(profile, edge_config, vertex_names, *, candidates=None)
¶
Every rule broken by a native inverse, without changing the profile.
A native inverse is the database realizing a declared pair for a whole relation, so it needs the declaration, must not coexist with explicit inverse edges, and exists only where the database can maintain one: on a single TigerGraph edge type whose reverse name is free.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
profile
|
DatabaseProfile
|
The physical profile; read only. |
required |
edge_config
|
EdgeConfig
|
Declared edges and inverse table. |
required |
vertex_names
|
set[str]
|
Logical vertex type names, which share a namespace with edge types on TigerGraph. |
required |
candidates
|
Iterable[str] | None
|
Relations to check as if they were listed in
|
None
|
Source code in graflo/architecture/schema/inverse_realization.py
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 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 | |
pair_realizations(schema, *, findings=None)
¶
How each declared pair is realized.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
schema
|
Schema
|
The schema to classify. |
required |
findings
|
Iterable[InverseFinding] | None
|
Findings already computed for this schema; a pair touched by a
|
None
|
Source code in graflo/architecture/schema/inverse_realization.py
resolve_relation(schema, name)
¶
The declared edges that answer a read of relation name.
A name that labels declared edges resolves to them as they are. A name that labels none, but is the declared inverse of a relation that does, resolves to those edges read backwards -- which is what makes a declared pair useful without storing anything. A name known neither way resolves to nothing.
Source code in graflo/architecture/schema/inverse_realization.py
schema_inverse_findings(schema)
¶
Every finding that needs the schema but not the ingestion model.