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.

ClassKindRole
PredicateImmutable value classImmutable declaration of one predicate; construction does not register it.
EngineResource-owning classOwns Python wrappers and their native children; registers and loads domains.
LimitsImmutable value classImmutable snapshot of native build capacities.

Constructors

ConstructorResultWhen to use it
Engine()EngineCreate the Python owner; no domain is registered yet.
Predicate(name, arity, flags)PredicateBuild a declaration; registration validates it later.

Methods

MethodPurpose
Engine.register_domain()Register the complete domain vocabulary before loading policies.
Engine.close()Close the Python owner

Convenience constructors

Class methodPurpose
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 / protocolTypeMeaning
engine.limitsLimitsImmutable 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

Python · class
Predicate
@dataclass(frozen=True)
class Predicate:
    name: str
    arity: int
    flags: int
Immutable declaration of one predicate; the class methods below provide convenient constructors for common flag combinations.
Fields
namestr
Relation name used in policy rules, facts and queries.
arityint
Number of terms in each fact or rule atom.
flagsint
Origin and optional query-permission bitmask, explained under Predicate kind flags.
Example
from maelys_datalog import Predicate, PRED_EDB

predicate = Predicate("owns", 2, PRED_EDB)

Engine reference

Python · class
Engine
Creation
Engine() -> Engine
Python owner created with Engine(); it reads engine.limits and tracks loaded Ruleset objects. Closing it closes native children but does not unregister domains. Use it on the creating thread.
Methods
Example
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 / protocolTypeMeaning
engine.limitsLimitsImmutable snapshot of build capacities.

Limits reference

Python · class
Limits
@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: int
Immutable capacity snapshot obtained from engine.limits. Its fields describe the loaded native profile, not available remaining space. Applications normally read this object instead of constructing it.
Fields
max_symbolsint
Maximum distinct symbols in the engine symbol table.
string_pool_bytesint
Byte capacity of the engine symbol-text pool.
max_predicatesint
Maximum declared predicates.
max_rulesint
Maximum compiled rules.
max_arityint
Maximum number of terms in a predicate.
max_body_literalsint
Maximum body literals per rule.
max_depthint
Maximum configured derivation depth.
max_edb_factsint
Maximum input entries; counted before deduplication.
max_idb_factsint
Maximum derived facts.
max_facts_per_predint
Maximum facts retained for one predicate.
max_string_bytesint
Maximum UTF-8 bytes in one input string.
input_edb_text_bytesint
Default and maximum byte capacity for owned input EDB text.
Example
print(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

Python · class method
Predicate.edb
Predicate.edb(
name: str,
arity: int
) -> Predicate
Constructs a declaration for a predicate whose facts come from runtime request input. Creating this value does not register it with the engine.
Arguments
namestr
Predicate name used by policy rules and facts.
arityint
Number of terms in each fact of this predicate.
Return value
Predicate

Returns a new immutable Predicate with PRED_EDB; it does not register the domain.

Example
Predicate.edb("owns", 2)

Queryable runtime-input predicate

Python · class method
Predicate.edb_query
Predicate.edb_query(
name: str,
arity: int
) -> Predicate
Constructs a runtime-input predicate declaration with query permission, so callers may test its facts in a result.
Arguments
namestr
Predicate name used by policy rules and facts.
arityint
Number of terms in each fact of this predicate.
Return value
Predicate

Returns a new immutable Predicate with PRED_EDB | PRED_QUERY; it does not query or register anything.

Example
Predicate.edb_query("owns", 2)

Derived predicate

Python · class method
Predicate.idb
Predicate.idb(
name: str,
arity: int
) -> Predicate
Constructs a declaration for a predicate whose facts are derived by policy rules, without enabling public queries.
Arguments
namestr
Predicate name used by policy rules and facts.
arityint
Number of terms in each fact of this predicate.
Return value
Predicate

Returns a new immutable Predicate with PRED_IDB; it does not evaluate rules.

Example
Predicate.idb("allow", 2)

Queryable derived predicate

Python · class method
Predicate.idb_query
Predicate.idb_query(
name: str,
arity: int
) -> Predicate
Constructs a derived-predicate declaration with query permission, so callers may inspect the relation after solving.
Arguments
namestr
Predicate name used by policy rules and facts.
arityint
Number of terms in each fact of this predicate.
Return value
Predicate

Returns a new immutable Predicate with PRED_IDB | PRED_QUERY; it does not run a query.

Example
Predicate.idb_query("allow", 2)

Policy-source fact predicate

Python · class method
Predicate.policy_fact
Predicate.policy_fact(
name: str,
arity: int
) -> Predicate
Constructs a declaration for a predicate whose ground facts may be written directly in policy source.
Arguments
namestr
Predicate name used by policy rules and facts.
arityint
Number of terms in each fact of this predicate.
Return value
Predicate

Returns a new immutable Predicate with PRED_POLICY_FACT; it does not add a fact.

Example
Predicate.policy_fact("trusted", 1)

Queryable policy-source fact predicate

Python · class method
Predicate.policy_fact_query
Predicate.policy_fact_query(
name: str,
arity: int
) -> Predicate
Constructs a policy-source fact declaration with query permission, so callers may test those facts in a result.
Arguments
namestr
Predicate name used by policy rules and facts.
arityint
Number of terms in each fact of this predicate.
Return value
Predicate

Returns a new immutable Predicate with PRED_POLICY_FACT | PRED_QUERY; it does not add or query a fact.

Example
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.

Python · method
Engine.register_domain
Engine.register_domain(
name: str,
predicates: Sequence[Predicate],
*,
atoms: Sequence[str] = ()
) -> None
Registers the domain’s predicate vocabulary and optional policy-source atoms before policies are loaded against it. It does not load or evaluate a policy.
Arguments
namestr
Process-wide domain name.
predicatesSequence[Predicate]
Nonempty sequence of Predicate declarations.
atomsSequence[str]
Optional policy-source string constants; runtime fact values do not belong here.
Return value
None

Returns 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.

Example
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.

Python · method
Engine.close
Engine.close() -> None
Closes this Python owner and its native children. It does not unregister domains from the global registry.
Arguments
Return value
None

Returns None after cleanup. Repeated closure is harmless; using closed children raises RuntimeError.

Example
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.

FlagRole
PRED_EDBRuntime input facts supplied in the EDB for one evaluation.
PRED_IDBFacts derived by policy rules.
PRED_QUERYPermission to observe a predicate through the query API; not a fact origin by itself.
PRED_POLICY_FACTBase 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:

  • predicates declares the relations a policy may use: their names, number of arguments and roles. For example, classification/2 means a relation named classification with two arguments.
  • atoms lists 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 default atoms=() 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.

CODE
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 flagsPython 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

CODE
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.

CODE
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.")
CODE
identical registration: OK
different definition: rejected

In 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.

CODE
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"]))
CODE
True
Element in this exampleWhere it is declared or supplied
classification/2, restricted/1Predicate(...): the relations and their roles
"confidential" in the ruleatoms=["confidential"]: an allowed policy constant
Doc in the ruleA variable; no atoms entry
"roadmap.pdf" in the input factedb.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

CODE
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.