Python
Registries
Registries in the unified Python binding.The Python registries reference continues the Python overview.
Classes
These names are Python classes, not calls to the Datalog engine. Calling a class creates an instance; only the registration method below changes the domain registry.
| Class | Kind | Role |
|---|---|---|
Predicate | Immutable value class | Immutable declaration of one predicate; construction does not register it. |
Engine | Resource-owning class | Owns Python wrappers and their native children; registers and loads domains. |
Limits | Immutable value class | Immutable snapshot of native build capacities. |
Constructors
| Constructor | Result | When to use it |
|---|---|---|
Engine() | Engine | Create the Python owner; no domain is registered yet. |
Predicate(name, arity, flags) | Predicate | Build a declaration; registration validates it later. |
Methods
| Method | Purpose |
|---|---|
Engine.register_domain() | Register the complete domain vocabulary before loading policies. |
Engine.close() | Close the Python owner |
Convenience constructors
| Class method | Purpose |
|---|---|
Predicate.edb() | Create a runtime-input declaration with PRED_EDB. |
Predicate.edb_query() | Create a runtime-input declaration with query permission. |
Predicate.idb() | Create a derived-fact declaration with PRED_IDB. |
Predicate.idb_query() | Create a derived-fact declaration with query permission. |
Predicate.policy_fact() | Create a policy-source fact declaration. |
Predicate.policy_fact_query() | Create a policy-source fact declaration with query permission. |
Properties and attributes
Read properties without parentheses. Their links lead to the class card defined on this page.
| Property / protocol | Type | Meaning |
|---|---|---|
engine.limits | Limits | Immutable snapshot of build capacities. |
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.
Predicate reference
@dataclass(frozen=True)
class Predicate:
name: str
arity: int
flags: intfrom maelys_datalog import Predicate, PRED_EDB
predicate = Predicate("owns", 2, PRED_EDB)Engine reference
Engine() -> Engine- Engine.close()Close the Python owner
- Engine.load_inline_ruleset()Load a policy from text
- Engine.load_manifest()Load verified policy files
- Engine.register_domain()Register the complete domain vocabulary before loading policies.
from maelys_datalog import Engine
with Engine() as engine:
print(engine.limits.max_edb_facts)Creating an Engine
Engine() -> Engine takes no arguments. It creates the Python owner; it does not register a domain or load a policy. Use with Engine() as engine to close its resources automatically.
Engine attributes
Read these attributes without parentheses; they are not callable methods.
| Attribute / protocol | Type | Meaning |
|---|---|---|
engine.limits | Limits | Immutable snapshot of build capacities. |
Limits reference
@dataclass(frozen=True)
class Limits:
max_symbols: int
string_pool_bytes: int
max_predicates: int
max_rules: int
max_arity: int
max_body_literals: int
max_depth: int
max_edb_facts: int
max_idb_facts: int
max_facts_per_pred: int
max_string_bytes: int
input_edb_text_bytes: intprint(engine.limits.max_edb_facts)
print(engine.limits.input_edb_text_bytes)Predicate convenience constructors
The six class methods below each construct a new immutable declaration. They do not register the domain.
Runtime-input predicate
Predicate.edb(
name: str,
arity: int
) -> PredicatePredicateReturns a new immutable Predicate with PRED_EDB; it does not register the domain.
Predicate.edb("owns", 2)Queryable runtime-input predicate
Predicate.edb_query(
name: str,
arity: int
) -> PredicatePredicateReturns a new immutable Predicate with PRED_EDB | PRED_QUERY; it does not query or register anything.
Predicate.edb_query("owns", 2)Derived predicate
Predicate.idb(
name: str,
arity: int
) -> PredicatePredicateReturns a new immutable Predicate with PRED_IDB; it does not evaluate rules.
Predicate.idb("allow", 2)Queryable derived predicate
Predicate.idb_query(
name: str,
arity: int
) -> PredicatePredicateReturns a new immutable Predicate with PRED_IDB | PRED_QUERY; it does not run a query.
Predicate.idb_query("allow", 2)Policy-source fact predicate
Predicate.policy_fact(
name: str,
arity: int
) -> PredicatePredicateReturns a new immutable Predicate with PRED_POLICY_FACT; it does not add a fact.
Predicate.policy_fact("trusted", 1)Queryable policy-source fact predicate
Predicate.policy_fact_query(
name: str,
arity: int
) -> PredicatePredicateReturns a new immutable Predicate with PRED_POLICY_FACT | PRED_QUERY; it does not add or query a fact.
Predicate.policy_fact_query("trusted", 1)Register a domain
Register the complete vocabulary before loading a policy. This instance method validates the declaration and sends it to the native process-wide registry.
Engine.register_domain(
name: str,
predicates: Sequence[Predicate],
*,
atoms: Sequence[str] = ()
) -> NoneNoneReturns None after a successful registration; it returns no domain handle. Invalid Python arguments raise TypeError or ValueError. A native registration failure raises MaelysDatalogError, so do not load a policy for that domain.
from maelys_datalog import Predicate
engine.register_domain("documents", [
Predicate.edb("owns", 2),
Predicate.idb_query("allow", 2),
])Close the Python owner
Release its loaded rulesets and their native children. This does not unregister the process-wide domain vocabulary.
Engine.close() -> NoneNoneReturns None after cleanup. Repeated closure is harmless; using closed children raises RuntimeError.
engine.close()Predicate kind flags
The general API is Predicate(name, arity, flags). It creates a declaration;
engine.register_domain() validates and registers that declaration later.
Here, arity is the number of arguments and flags describes the predicate's
role. The usage examples show this explicit form alongside the convenience constructors.
| Flag | Role |
|---|---|
PRED_EDB | Runtime input facts supplied in the EDB for one evaluation. |
PRED_IDB | Facts derived by policy rules. |
PRED_QUERY | Permission to observe a predicate through the query API; not a fact origin by itself. |
PRED_POLICY_FACT | Base facts written in trusted policy source. |
These are integer constants, not callable builders. Combine compatible roles with |,
for example PRED_IDB | PRED_QUERY. Add PRED_QUERY to a fact origin only
when that predicate should be observable.
The constructor's keyword is flags; the historical binding calls it
kind_flags. Predicate names and arities must be declared before loading rules.
These constants are read from the CFFI extension's public C constants, not hard-coded independently in Python. Their values remain EDB=1, IDB=2, QUERY=4 and POLICY_FACT=8. A query flag grants observation permission; it does not turn an input fact into a derived fact.
Usage and examples
Start with the domain vocabulary, choose predicate declarations, then create an Engine and register the domain. The final sections distinguish policy constants from runtime values and explain build limits.
The reference above gives the exact declarations; the sections below explain their use.
Domain definition
A domain is the named vocabulary against which a policy is validated. Declaring it does not add request facts or evaluate the policy.
engine.register_domain(name, predicates, atoms=()) declares two different
parts of the vocabulary:
predicatesdeclares the relations a policy may use: their names, number of arguments and roles. For example,classification/2means a relation namedclassificationwith two arguments.atomslists the text constants allowed in policy source, for both inline and default manifest loading. For example,atoms=["confidential"]allows a rule to contain the literal string"confidential". The defaultatoms=()declares no such constants.
Here, the API parameter atoms means symbolic string constants, not predicate
names, variables, or complete facts. You do not list every runtime value here.
Declare predicates
Predicate("owns", 2, PRED_EDB) creates a Python object describing a relation named owns. It does not add an ownership fact or register a domain. Its name, arity and flags cannot be changed after creation. See the Predicate card for these fields.
Arity means the number of values in a fact. For example, owns("alice", "roadmap.pdf") contains two values:
"alice"identifies the user;"roadmap.pdf"identifies the document.
That is why owns is declared with arity 2, also written owns/2. A fact with only a user, or with a user and two documents, would not match this declaration.
Choose the relation's role with the predicate flags. Creating the Predicate only prepares its description; engine.register_domain() checks the declarations together and registers the domain.
Optional convenience constructors
You can supply the flags yourself with Predicate(name, arity, flags), or use a shortcut that supplies them for you. For example, Predicate.edb("owns", 2) creates the same declaration as Predicate("owns", 2, PRED_EDB).
These shortcuts are Python class methods, not macros. They create declarations; you must still pass those declarations to engine.register_domain() to register and validate the domain.
In the example below, explicit and shortcuts both describe the same three relations:
owns/2: facts provided by the application;can_read/2: facts derived by the rules;allow/2: derived facts that the application may query.
Use either list when registering the domain. assert explicit == shortcuts shows that their declarations have the same names, arities and flags.
from maelys_datalog import Predicate, PRED_EDB, PRED_IDB, PRED_QUERY
# General API: supply every flag explicitly.
explicit = [
Predicate("owns", 2, PRED_EDB),
Predicate("can_read", 2, PRED_IDB),
Predicate("allow", 2, PRED_IDB | PRED_QUERY),
]
# Convenience API: the constructors supply those same flags.
shortcuts = [
Predicate.edb("owns", 2),
Predicate.idb("can_read", 2),
Predicate.idb_query("allow", 2),
]
assert explicit == shortcuts| Explicit flags | Python shortcut |
|---|---|
Predicate(name, arity, PRED_EDB) | Predicate.edb(name, arity) |
Predicate(name, arity, PRED_EDB | PRED_QUERY) | Predicate.edb_query(name, arity) |
Predicate(name, arity, PRED_IDB) | Predicate.idb(name, arity) |
Predicate(name, arity, PRED_IDB | PRED_QUERY) | Predicate.idb_query(name, arity) |
Predicate(name, arity, PRED_POLICY_FACT) | Predicate.policy_fact(name, arity) |
Predicate(name, arity, PRED_POLICY_FACT | PRED_QUERY) | Predicate.policy_fact_query(name, arity) |
The _query shortcuts add permission to query the relation; they do not change where its facts come from. For example, Predicate.policy_fact_query("trusted", 1) declares queryable facts written in the policy source, just like Predicate("trusted", 1, PRED_POLICY_FACT | PRED_QUERY).
Each shortcut takes a relation name and an arity, and returns a Predicate. See their individual method cards for the signatures and examples.
Create an Engine
from maelys_datalog import Engine
with Engine() as engine:
print(engine.limits.max_edb_facts)Engine() creates the object used to register domains and load policies. It takes no arguments. The example reads engine.limits.max_edb_facts, the maximum number of input facts supported by the loaded engine.
Use with Engine() as engine: so its loaded policies, sessions and results are closed when the block ends, including if an exception occurs. Creating the Engine does not register a domain, load a policy or evaluate facts. Each policy and session you create remains a separate object.
Importing maelys_datalog loads the binding and its native library; Engine() records the creating thread, checks the API version and reads the build limits. Build the binding and native library from the same engine version and profile. See Installation for the library paths.
Register the domain
Call engine.register_domain("documents", predicates) to register a domain named documents using your list of Predicate objects. Add atoms=[...] when the policy contains quoted string constants. This method checks the declarations; it does not load a policy or add input facts. Its method card lists the arguments and errors.
All Engine objects in the same Python process share the registered domains. For example, if engine1 registers a domain, engine2 can use that same domain name. Creating another Engine does not create an empty, separate catalogue of domains.
The example below first registers a domain with two relations. Registering that same name with the same declarations succeeds again. Changing allow from two arguments to three is rejected with MaelysDatalogError: an existing domain name cannot be assigned a different vocabulary, including a different atoms list.
The complete document-access example shows registration before loading the policy.
from maelys_datalog import (
Engine, Predicate, MaelysDatalogError, PRED_EDB, PRED_IDB, PRED_QUERY,
)
predicates = [
Predicate("owns", 2, PRED_EDB),
Predicate("allow", 2, PRED_IDB | PRED_QUERY),
]
with Engine() as engine1, Engine() as engine2:
engine1.register_domain("registry_next_docs", predicates)
engine2.register_domain("registry_next_docs", predicates)
print("identical registration: OK")
try:
engine2.register_domain("registry_next_docs", [
Predicate("owns", 2, PRED_EDB),
Predicate("allow", 3, PRED_IDB | PRED_QUERY), # Different arity.
])
except MaelysDatalogError:
print("different definition: rejected")
else:
raise AssertionError("A domain name must not change its vocabulary.")identical registration: OK
different definition: rejectedIn a notebook, re-running the same registration is safe. If you change the domain's declarations, restart the Python interpreter or notebook kernel before registering the new definition.
The binding serializes domain registration and policy loading. Use each Engine and its objects on the thread where they were created. See Threading model for the concurrency rules.
Policy literals and runtime values
For example, with blocked/1 declared as EDB, edb.add_fact("blocked", ["mallory"]) supplies runtime data: "mallory" needs no entry in atoms. The rule can check not(blocked(User)) without naming that user. In contrast, writing blocked("mallory"). in the policy requires both a POLICY_FACT declaration for blocked/1 and atoms=["mallory"]. A string explicitly written as a predicate argument in a rule also needs declaration, even if that predicate is EDB. Registering the string itself never inserts a fact. See the tutorial comparison.
The example below asks whether a document is classified as confidential.
Before loading the rule, it declares the relations classification/2 and
restricted/1, then authorizes the policy constant "confidential" with
atoms=["confidential"].
Doc is a variable, so it does not go in atoms. "roadmap.pdf" is supplied
later as an input fact, so it does not need a declaration either. It is
"confidential"'s presence inside the rule that requires its declaration,
not its later use in an input fact.
from maelys_datalog import Engine, Predicate, PRED_EDB, PRED_IDB, PRED_QUERY
with Engine() as engine:
engine.register_domain("policy_atoms_docs", [
Predicate("classification", 2, PRED_EDB),
Predicate("restricted", 1, PRED_IDB | PRED_QUERY),
], atoms=["confidential"])
ruleset = engine.load_inline_ruleset(
"policy_atoms_docs", "classification.main",
'restricted(Doc) :- classification(Doc, "confidential").',
)
edb = ruleset.edb()
edb.add_fact("classification", ["roadmap.pdf", "confidential"])
result = ruleset.solve(edb)
print(result.contains_fact("restricted", ["roadmap.pdf"]))True| Element in this example | Where it is declared or supplied |
|---|---|
classification/2, restricted/1 | Predicate(...): the relations and their roles |
"confidential" in the rule | atoms=["confidential"]: an allowed policy constant |
Doc in the rule | A variable; no atoms entry |
"roadmap.pdf" in the input fact | edb.add_fact(...): runtime data, not a policy constant |
Removing atoms=["confidential"] makes inline policy loading fail, before
any solve. It does not turn the rule into a silently non-matching rule.
Conversely, adding another document name to the EDB does not require changing
atoms. If you hard-code that name inside a rule, it becomes a policy constant
and must be declared too.
Two boundaries matter: standard filter patterns are not atoms and do not
need this whitelist; manifest loading can explicitly admit bounded
policy-local constants with allow_undeclared_policy_atoms=True, without
changing the global domain. See loading a verified manifest.
Build limits
engine.limits
# Limits(max_symbols=..., string_pool_bytes=..., max_predicates=...,
# max_rules=..., max_arity=..., max_body_literals=...,
# max_depth=..., max_edb_facts=..., max_idb_facts=...,
# max_facts_per_pred=..., max_string_bytes=...)engine.limits gives the capacities of the loaded engine, read when you create the Engine. These are maximum sizes, not counters or remaining space. For example, if max_edb_facts is 64, adding ten facts does not change it to 54.
The Limits card describes each field: maximum numbers of predicates, rules and facts, number of arguments, rule-body length, derivation depth, and string-storage budgets. These values cannot be changed through engine.limits.
The total input-count limit applies before deduplication and is checked at addition, as is each string's UTF-8 byte length. Per-predicate and shared symbol pool limits can still reject a smaller batch at solve: fitting the input buffer is not a promise that a policy can evaluate it.
Once the vocabulary is registered, continue with Loading & manifests to obtain a policy set.