Python
Runtime EDB
Edb in the unified Python binding.The Python runtime EDB reference continues the Python overview.
Classes
| Class | Kind | Role |
|---|---|---|
Edb | Resource-owning class | Owns the mutable input facts for one complete request. |
Methods
| Method | Purpose |
|---|---|
Ruleset.edb() | Create an input buffer |
Edb.add_fact() | Append one fact |
Edb.add_facts() | Append an atomic batch |
Edb.clear() | Discard an unsolved batch |
Edb.reset() | Start a new request in the same buffer |
Edb.close() | Release input storage |
Properties and attributes
Use Python's length protocol to inspect the buffer. It is not an additional native count method; the Edb card below explains its ownership.
| Property / protocol | Type | Meaning |
|---|---|---|
len(edb) | int | Number of stored entries before deduplication; Python length protocol, not an Edb.count() method. |
Detailed API reference
Every class and method indexed at the start of this page has its detailed card here. A class’s method list also links to related operations documented on other topic pages; those links do not duplicate their cards.
Value-class declarations and public creation signatures below come from the published binding inventory. Resource-owning classes list their methods as individual links with a short explanation. Each link opens the complete signature, arguments, return behavior and example; the creation signatures are a reference, not executable class source. The examples are fragments: engine, ruleset, edb, session and result refer to the live objects explained on this page and in the complete examples. Import the named classes before using them.
Edb reference
Ruleset.edb(
*,
fact_capacity: int | None = None,
text_capacity: int | None = None,
) -> Edb- Edb.add_fact()Append one fact
- Edb.add_facts()Append an atomic batch
- Edb.clear()Discard an unsolved batch
- Edb.close()Release input storage
- Edb.reset()Start a new request in the same buffer
edb = ruleset.edb()
edb.add_fact("owns", ["alice", "roadmap.pdf"])
print(len(edb))Creating an Edb
Obtain this object through the following method. Do not call its internal handle-taking constructor.
| Operation | Purpose |
|---|---|
Ruleset.edb() | Create an input buffer |
Edb attributes and protocols
Use Python’s len(edb) protocol to count stored entries. It is not an Edb.count() method.
| Attribute / protocol | Type | Meaning |
|---|---|---|
len(edb) | int | Number of stored entries before deduplication; Python length protocol, not an Edb.count() method. |
Create an input buffer
Keep request facts separate from the compiled policy. Optional capacities reserve bounded input storage; they do not change solver limits.
Ruleset.edb(
*,
fact_capacity: int | None = None,
text_capacity: int | None = None
) -> EdbEdbReturns a new, empty Edb associated with this Ruleset. Invalid capacities raise TypeError or ValueError; native creation failures raise MaelysDatalogError.
edb = ruleset.edb()
edb.add_fact("owns", ["alice", "roadmap.pdf"])Append one fact
This copies one fact into the input buffer. It does not run rules or validate the complete policy vocabulary.
Edb.add_fact(
predicate: str,
terms: Sequence[object]
) -> NoneNoneReturns None after a successful copy. Python shape errors and native storage errors raise exceptions; earlier successful additions remain. Exact predicate vocabulary is checked at solve.
edb.add_fact("owns", ["alice", "roadmap.pdf"])Append an atomic batch
Prefer a batch when several facts form one logical addition: either all are appended or none are.
Edb.add_facts(
facts: Iterable[tuple[str, Sequence[object]]]
) -> NoneNoneReturns None after appending the whole batch. Python validation, generator or native storage failure appends none of this batch. Earlier additions remain; generator side effects are not rolled back.
edb.add_facts([
("user", ["alice"]),
("owns", ["alice", "roadmap.pdf"]),
("blocked", ["mallory"]),
])Discard an unsolved batch
Clear input after correcting an invalid unsolved request. This reuses the same native storage.
Edb.clear() -> NoneNoneReturns None after emptying an unsolved Edb. A successfully solved Edb is frozen: clear() then raises RuntimeError; use reset() to start a new request.
edb.clear()
edb.add_fact("owns", ["alice", "corrected.pdf"])Start a new request in the same buffer
Reset explicitly ends the mutation freeze after successful solving and removes all input facts. It does not release an existing result.
Edb.reset() -> NoneNoneReturns None with an empty, mutable Edb. The session still rejects a second solve while its previous result remains open. Native failures raise MaelysDatalogError.
with session.solve(edb) as result:
print(result.contains_fact("allow", ["alice", "roadmap.pdf"]))
edb.reset()
edb.add_fact("owns", ["bob", "notes.txt"])
with session.solve(edb) as result:
print(result.contains_fact("allow", ["bob", "notes.txt"]))Release input storage
Once solving has produced a retained result, the input buffer is no longer required to query that result.
Edb.close() -> NoneNoneReturns None after releasing native input storage. Already returned results remain valid. Repeated closure is harmless; using the closed Edb raises RuntimeError.
edb.close()Usage and examples
Build a request with plain Python values, understand batch insertion and validation, then reuse its storage. Input values and raw output identifiers have different roles.
The reference above gives the exact declarations; the sections below explain their use.
EDB: adding facts
ruleset.edb() creates an independent opaque C input buffer, owned by Python.
edb.add_fact(predicate, terms) immediately copies one predicate name and its
terms into native storage. edb.add_facts(iterable) temporarily stages pairs,
then appends them atomically in C. Neither leaves a persistent Python fact list.
len(edb) counts entries before deduplication; edb.clear() empties an unsolved
buffer. Ruleset and Engine closure free any remaining EDB handles.
EDB facts take Python values directly: strings, signed 64-bit integers and
booleans, mixed freely within a term sequence. The old binding exported
InputTerm, ResolvedTerm, Fact, RawFact and a writable Term helper.
Do not copy those imports into Python: inputs here are plain Python
values, resolved outputs are tuples of those values, and raw output terms
use the separate, result-owned ResultTerm class.
| Python value | Copied at addition as | Notes |
|---|---|---|
| str | MAELYS_DATALOG_VALUE_SYMBOL | UTF-8 text supplied to the native batch; no ruleset.intern_symbol call. |
| int | MAELYS_DATALOG_VALUE_INTEGER | Full signed int64 range; values outside that range raise OverflowError. |
| bool | MAELYS_DATALOG_VALUE_BOOLEAN | Encoded before the int case: True is a boolean, not integer 1. |
| float / legacy Term / ResultTerm | Not accepted as input | Use plain str, int or bool. ResultTerm is an output view, not an input token. |
The example needs its own vocabulary: declaring owns does not implicitly
declare quota_exceeded or has_flag.
from maelys_datalog import Engine, Predicate, PRED_EDB, PRED_IDB, PRED_QUERY
with Engine() as engine:
engine.register_domain("typed_next_docs", [
Predicate("owns", 2, PRED_EDB),
Predicate("quota_exceeded", 2, PRED_EDB),
Predicate("has_flag", 2, PRED_EDB),
Predicate("allow", 2, PRED_IDB | PRED_QUERY),
])
ruleset = engine.load_inline_ruleset(
"typed_next_docs", "typed.main",
"allow(User, Document) :- owns(User, Document).",
)
edb = ruleset.edb()
edb.add_fact("owns", ["alice", "roadmap.pdf"]) # str + str
edb.add_fact("quota_exceeded", ["alice", 1024]) # str + int
edb.add_fact("has_flag", ["alice", True]) # str + bool
with ruleset.solve(edb) as result:
print(result.enumerate_predicate_facts("allow", 2))[('alice', 'roadmap.pdf')]Unit and batch: the same choice in C and Python
The following snippets assume a live input EDB and a domain declaring
user/1, owns/2 and blocked/1. C also uses the status rc,
the diagnostic object and a cleanup path. Choose the unit or batch example;
do not execute both to build the same request.
rc = MAELYS_DATALOG_ADD_FACT(
edb, &diagnostic, "owns", "alice", "roadmap.pdf");
if (rc != MAELYS_DATALOG_STATUS_OK) goto cleanup;rc = MAELYS_DATALOG_ADD_FACTS(
edb, &diagnostic,
MAELYS_DATALOG_FACT("user", "alice"),
MAELYS_DATALOG_FACT("owns", "alice", "roadmap.pdf"),
MAELYS_DATALOG_FACT("blocked", "mallory")
);
if (rc != MAELYS_DATALOG_STATUS_OK) goto cleanup;A batch either appends all its facts or none. Existing facts are preserved on failure. By contrast, three independent unit calls can leave two facts stored if the third call fails. The guarantee concerns input insertion, not solving: undeclared predicates and policy constraints can still fail later.
Python uses ordinary methods, not C macros. C11's FACT and ADD_FACTS
conveniences do not change the ABI or the native batch API. See
C input facts, with and without macros
for conversions, lifetimes and the explicit typed-array form for dynamic input.
Buffered additions, one native batch
- Add factsadd_fact / add_facts
- Native input EDBowned copies; bounded storage
- solve(edb)evaluate the entire native buffer
- Resultquery without re-solving
Both addition methods copy values into the opaque native EDB. add_facts()
accepts an iterable of (predicate, terms) pairs, including a generator. Its
temporary Python staging area is bounded by the native entry limit; no Python
fact list remains after return. Later changes to the caller's term lists cannot
alter the owned native copies.
Each add_facts() call is atomic for Python-side and native storage errors:
a malformed pair, invalid term, failing generator, exhausted entry capacity,
overlong string or allocation failure appends none of that batch. Earlier
successful additions remain. Side effects performed by a caller's generator
are not rolled back. Python checks types, signed 64-bit bounds and embedded
NULs; no floats or pre-interned Term objects are accepted.
Policy-specific checks — predicate existence, declared arity and kind,
per-predicate and shared symbol-pool capacities — occur at solve().
A rejected solve produces no partial result. Use edb.clear() to discard an
invalid unsolved input, then rebuild and retry. This is full-batch evaluation,
not incremental inference.
| When | What is checked |
|---|---|
| Addition | Python representation; native copy, entry count, arity ceiling and per-string byte bound. |
| Solve | Selected policy vocabulary, exact arity, predicate kind, symbol pool and per-predicate capacities. |
| Successful solve | Python freezes the EDB for mutation; the C buffer API itself remains reusable. |
The C facade exposes maelys_datalog_input_edb_t and
maelys_datalog_session_solve_edb() through <maelys/datalog.h> alone.
These calls are included in 0.4.0: build the CFFI extension and native library
from that same release.
Native errors at solve time
This standalone example deliberately buffers a fact with the wrong arity:
from maelys_datalog import (
Engine, Predicate, MaelysDatalogError, PRED_EDB, PRED_IDB, PRED_QUERY,
)
with Engine() as engine:
engine.register_domain("invalid_batch_docs", [
Predicate("owns", 2, PRED_EDB),
Predicate("allow", 2, PRED_IDB | PRED_QUERY),
])
ruleset = engine.load_inline_ruleset(
"invalid_batch_docs", "batch.main",
"allow(User, Doc) :- owns(User, Doc).",
)
edb = ruleset.edb()
edb.add_fact("owns", ["alice", "doc.pdf"])
edb.add_fact("owns", ["bob", "doc.pdf", "extra"]) # Buffered, not yet rejected.
try:
ruleset.solve(edb)
except MaelysDatalogError as error:
print(error.message)
else:
raise AssertionError("The invalid batch must not succeed.")Invalid fact at index 1: owns expects 2 arguments, received 3.Indices are zero-based across the complete EDB buffer, not just the
last addition. Use error.code, error.message and error.hint for
diagnosis; message wording is not a stable machine-readable grammar.
A Python TypeError or ValueError is distinct from MaelysDatalogError,
which can arise during native addition or solving. Append diagnostics index
the submitted batch; solve diagnostics index the complete EDB.
Reuse an input buffer
After a successful solve, clear() is rejected because that EDB is frozen. Use reset() to empty and reuse its native storage for a new complete request; this does not release a live result or make a Session accept a second result until the first is closed.
Inserting by pre-interned symbol ID
The original binding lets a caller intern a string once and reuse its ruleset-local integer handle. Python deliberately does not carry that input optimization across the opaque boundary. Submit the string itself, even if it appears in several buffered facts; the native batch prepares its symbols. A Python integer always means an integer term, never an implicit symbol identifier.
Raw output still exists, but its IDs belong to one retained result. They are not portable between results or reusable in later input batches. This is a scope change, not the loss of the ability to supply symbols. See resolved versus raw enumeration.