Errors and error codes#
The public exception hierarchy raised by the factgraph SDK, the attributes carried on each error, and the exported error-code string constants.
Most public error classes and all code constants are re-exported from the top-level factgraph.sdk package (see reference/factgraph.md). The base class SDKError is not re-exported from factgraph.sdk; import it from factgraph.sdk.errors. RuleValidationError and DetachedRowError live outside the SDK package; DetachedRowError is re-exported from factgraph.sdk, while RuleValidationError is not.
The .code and .path convention#
Every SDK error derives from a common base, SDKError, whose constructor accepts an optional machine-readable code and an optional path:
class SDKError(Exception):
def __init__(self, message: str, *, code: str | None = None, path: str | None = None) -> None:
super().__init__(message)
self.code = code
self.path = pathcodeis a stable string identifier (for example"ENTITY_ALREADY_EXISTS","SCHEMA_CONFLICT"). It isNonewhen not set.pathis a dotted location string (for example"$.test"), orNone.
Both attributes are present on every error instance because every error class inherits this base. Catch broadly on SDKError and branch on .code:
from factgraph.sdk.errors import SDKError
try:
fg.schema.register(User)
except SDKError as exc:
if exc.code == "SCHEMA_CONFLICT":
...Exception hierarchy#
Bases are defined in factgraph._sdk_errors; rule, DSL, and evaluation errors are defined under factgraph.application.protocol and factgraph.sdk.dsl.
Exception
└── SDKError (factgraph.sdk.errors.SDKError)
├── SDKSchemaError
├── SDKDSLError
│ ├── DSLToApplicationRuleError
│ ├── RuleExprError
│ │ └── ExplicitBoolError
│ └── (SDKDSLError raised directly)
└── SDKStoreError
├── SDKValueError
├── EntityNotFoundError
├── EntityAlreadyExistsError
├── SchemaConflictError
├── SchemaNotFoundError
├── SchemaNonAdditiveError
├── FrozenSnapshotError
├── CardinalityError
└── EditorClosedError
ValueError
└── RuleValidationError (factgraph.application.protocol.rule.RuleValidationError)
RuntimeError
└── DetachedRowError (factgraph.application.protocol.evaluate_result.DetachedRowError)RuleValidationError derives from the Python built-in ValueError, and DetachedRowError derives from RuntimeError. Neither is part of the SDKError tree.
Store and schema errors#
These derive from SDKError and are raised by the read/write/schema surface. SDKStoreError and SDKSchemaError are themselves public and are also used directly as generic fallbacks.
SDKError#
class SDKError(Exception):
def __init__(self, message: str, *, code: str | None = None, path: str | None = None) -> NoneRoot of the SDK exception hierarchy. Carries .code and .path. Importable from factgraph.sdk.errors.
SDKSchemaError#
class SDKSchemaError(SDKError):
passRaised when schema input is structurally invalid before any store mutation, for example an empty class list, a class that is not an Entity subclass, or get()/edit() called with non-identity or missing identity fields.
raise SDKSchemaError("classes must be non-empty list[Entity|Relationship]")SDKStoreError#
class SDKStoreError(SDKError):
passThe general store-runtime error and the fallback for application-layer errors that have no more specific SDK type. Raised directly for invalid call arguments to read/write namespaces and for unmapped application error codes; in the latter case the application code is preserved on .code.
raise SDKStoreError(err.message, code=err.code)SDKValueError#
class SDKValueError(SDKStoreError):
passRaised when a field value fails validation. Carries the application code "FIELD_VALUE_VALIDATION_FAILED" and a dotted path to the offending value.
raise SDKValueError(err.message, code="FIELD_VALUE_VALIDATION_FAILED", path=path)EntityNotFoundError#
class EntityNotFoundError(SDKStoreError):
def __init__(
self,
message: str,
entity_type: str | None = None,
identity_kwargs: dict | None = None,
code: str | None = None,
path: str | None = None,
) -> NoneRaised when an entity addressed by identity does not exist, for example when opening an editor on a missing entity (fg.entities.edit(...)), or when the application layer reports code "ENTITY_NOT_FOUND".
| Attribute | Type | Description |
|---|---|---|
entity_type | str | None | Name of the entity class. |
identity_kwargs | dict | Copy of the identity bundle used to address the entity. Defaults to {}. |
code | str | None | "ENTITY_NOT_FOUND" when remapped from an application error. |
with pytest.raises(EntityNotFoundError):
fg.entities.edit(User, user_id="missing")EntityAlreadyExistsError#
class EntityAlreadyExistsError(SDKStoreError):
def __init__(
self,
message: str,
entity_type: str | None = None,
identity_kwargs: dict | None = None,
e_ref: str | None = None,
code: str | None = None,
path: str | None = None,
) -> NoneRaised by fg.entities.create(...) when an entity with the supplied identity bundle is already visible in the ledger, including when that entity was materialized lazily by a prior fg.fields.set. The application code is "ENTITY_ALREADY_EXISTS".
| Attribute | Type | Description |
|---|---|---|
entity_type | str | None | Name of the entity class. |
identity_kwargs | dict | Copy of the supplied identity bundle. Defaults to {}. |
e_ref | str | None | Encoded reference of the entity that already exists. |
code | str | None | "ENTITY_ALREADY_EXISTS". |
fg.entities.create(User, user_id="alice", tenant_id="acme")
with pytest.raises(EntityAlreadyExistsError) as exc_info:
fg.entities.create(User, user_id="alice", tenant_id="acme")
err = exc_info.value
assert err.code == "ENTITY_ALREADY_EXISTS"
assert err.entity_type == "User"
assert err.identity_kwargs == {"user_id": "alice", "tenant_id": "acme"}CardinalityError#
class CardinalityError(SDKStoreError):
def __init__(
self,
message: str,
field_name: str | None = None,
actual_cardinality: str | None = None,
operation: str | None = None,
code: str | None = None,
path: str | None = None,
) -> NoneRaised when a write operation does not match a field’s declared cardinality: calling .set (or fg.fields.set) on a multi field, or .add (or fg.fields.add) on a single field. The application code is "FIELD_CARDINALITY_MISMATCH".
| Attribute | Type | Description |
|---|---|---|
field_name | str | None | Name of the field. |
actual_cardinality | str | None | The field’s declared cardinality (for example "single", "multi"). |
operation | str | None | The attempted operation ("set" or "add"). |
code | str | None | "FIELD_CARDINALITY_MISMATCH". |
with self.assertRaises(CardinalityError) as ctx:
sdk.fields.set(User.tag, alice, "admin")
self.assertEqual(ctx.exception.code, "FIELD_CARDINALITY_MISMATCH")
self.assertEqual(ctx.exception.operation, "set")
self.assertEqual(ctx.exception.actual_cardinality, "multi")FrozenSnapshotError#
class FrozenSnapshotError(SDKStoreError):
passRaised on any attempt to mutate a read-only snapshot view, for example writing through an EntitySnapshot or an AssertionView.
raise FrozenSnapshotError("EntitySnapshot is read-only")SchemaConflictError#
class SchemaConflictError(SDKStoreError):
passRaised by fg.schema.register(...) (and by fg.schema.add(...) when it resolves to a register) when the entity type is already registered, or when a register would not be additive. The code is "SCHEMA_CONFLICT".
with pytest.raises(SchemaConflictError) as exc_info:
fg.schema.register(User) # already registered
assert exc_info.value.code == "SCHEMA_CONFLICT"SchemaNotFoundError#
class SchemaNotFoundError(SDKStoreError):
passRaised by fg.schema.extend(...) when the target entity type has not been registered. The code is "SCHEMA_NOT_FOUND".
with pytest.raises(SchemaNotFoundError) as exc_info:
fg.schema.extend(Unregistered)
assert exc_info.value.code == "SCHEMA_NOT_FOUND"SchemaNonAdditiveError#
class SchemaNonAdditiveError(SDKStoreError):
passRaised by fg.schema.extend(...) (and by fg.schema.add(...) when it resolves to an extend) when the requested change to an existing entity type is not purely additive. The code is "SCHEMA_NON_ADDITIVE".
with pytest.raises(SchemaNonAdditiveError) as exc_info:
fg.schema.extend(UserWithChangedIdentity)
assert exc_info.value.code == "SCHEMA_NON_ADDITIVE"EditorClosedError#
class EditorClosedError(SDKStoreError):
passRaised when an operation is attempted on an entity editor that has already been closed.
raise EditorClosedError("editor is closed")Rule and DSL errors#
Authoring-time errors raised while building rules, lowering the SDK DSL, and validating rule expressions. RuleExprError, ExplicitBoolError, DSLToApplicationRuleError, and SDKDSLError are re-exported from factgraph.sdk.
SDKDSLError#
class SDKDSLError(SDKError):
passBase for DSL and rule-authoring errors. A subclass of SDKError, so it carries .code and .path. Raised directly for some DSL lowering failures, carrying a code and path describing the failure location.
self.assertTrue(issubclass(SDKDSLError, SDKError))
raise SDKDSLError("dsl problem", code="X", path="$.test")DSLToApplicationRuleError#
class DSLToApplicationRuleError(SDKDSLError):
passRaised by build_application_rule(...) when SDK DSL input cannot be lowered into an application Rule, for example when a when body is empty, contains an OR branch or Case (the application Rule bridge accepts AND-only when bodies), or when ports are malformed or reference a LogicVar that does not appear in when.
with self.assertRaises(DSLToApplicationRuleError):
build_application_rule(...) # OR branch in when bodyRuleExprError#
class RuleExprError(SDKDSLError):
passRaised when RuleExpr authoring input violates the expression contract. Sources include: RuleExpr.all/RuleExpr.any called with no operands; RuleExpr.join called without constraints or with constraints that do not connect distinct occurrences; join_by_ports called without port names; inspecting or evaluating an expression whose head is not closed (unbound ports); requesting a port that the expression does not declare; and other shape violations during lowering and inspection.
with self.assertRaisesRegex(RuleExprError, "require at least one operand"):
RuleExpr.all()ExplicitBoolError#
class ExplicitBoolError(RuleExprError):
passRaised when a Rule or RuleExpr value is used in a Python boolean context (for example with and, or, not, or an if). Rule.__bool__ and RuleExpr.__bool__ raise this instead of returning a truth value, to force the use of the & and | combinators.
raise ExplicitBoolError(
"RuleExpr values do not support Python truthiness; use & or | instead of and/or"
)RuleValidationError#
class RuleValidationError(ValueError):
passRaised when an application protocol Rule violates its shape invariants, for example an empty when tuple, empty or malformed ports, a port Var that does not appear in where, an invalid repr template, or a projection request with duplicate or non-string port names. Derives from the built-in ValueError, not from SDKError. Defined in factgraph.application.protocol.rule.
raise RuleValidationError("when must be non-empty tuple[Atom, ...]")Evaluation errors#
DetachedRowError#
class DetachedRowError(RuntimeError):
passRaised when a live-only operation is requested on an EvaluateRow that is detached from (or stale with respect to) its EvaluateResult. Derives from the built-in RuntimeError, not from SDKError. Re-exported from factgraph.sdk. Defined in factgraph.application.protocol.evaluate_result. See reference/evaluation.md for the evaluation result types.
with self.assertRaisesRegex(DetachedRowError, "detached"):
row.some_live_operation()Error-code constants#
String constants exported from factgraph.sdk for use when matching on .code. They are defined in factgraph.sdk.error_codes.
| Constant | Value |
|---|---|
INVALID_ROW_FORMAT | "INVALID_ROW_FORMAT" |
QUERY_MISSING_REF | "QUERY_MISSING_REF" |
QUERY_TYPE_MISMATCH | "QUERY_TYPE_MISMATCH" |
QUERY_ALIAS_CONFLICT | "QUERY_ALIAS_CONFLICT" |
QUERY_UNBOUND_VAR | "QUERY_UNBOUND_VAR" |
QUERY_INVALID_ROW_FORMAT | "QUERY_INVALID_ROW_FORMAT" |
QUERY_NOT_IMPLEMENTED | "QUERY_NOT_IMPLEMENTED" |
These constants are the exported code values. Other code strings appear on errors as literals set at the raise site, including "ENTITY_ALREADY_EXISTS", "ENTITY_NOT_FOUND", "FIELD_CARDINALITY_MISMATCH", "FIELD_VALUE_VALIDATION_FAILED", "SCHEMA_CONFLICT", "SCHEMA_NOT_FOUND", and "SCHEMA_NON_ADDITIVE".
The QUERY_* constants name codes carried by the legacy query path, which is not covered by this documentation set.