Python

Loading & manifests

Load policies from inline source or verified manifests with the Python binding.

Load a policy from source text or verified manifest files. Both methods return a Ruleset; its metadata and lifetime are documented in Rulesets.

Methods

MethodPurpose
Engine.load_inline_ruleset()Load a policy from text
Engine.load_manifest()Load verified policy files

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.

Signatures and class declarations below come from the published binding inventory. 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.

Load a policy from text

Use this path when the application already holds the Datalog source in memory. Register the domain first.

Python · method
Engine.load_inline_ruleset
Engine.load_inline_ruleset(
domain: str,
policy_id: str,
source: str
) -> Ruleset
Loads one policy directly from Datalog source text against a registered domain, without reading a manifest.
Arguments
domainstr
Name of a domain that has already been registered.
policy_idstr
Identifier for the single policy loaded from this source string.
sourcestr
Datalog program text, not a filename; encoded as UTF-8 by the binding.
Return value
Ruleset

Returns a new Ruleset containing one policy, selected with policy_index=0. Invalid source or domain vocabulary raises MaelysDatalogError; loading does not solve runtime facts.

Example
ruleset = engine.load_inline_ruleset(
    "documents", "access",
    "allow(User, Doc) :- owns(User, Doc).",
)

Load verified policy files

Use a manifest to package policy identities, source digests and public queries. Permissions are independent, explicit keyword arguments.

Python · method
Engine.load_manifest
Engine.load_manifest(
path: str | os.PathLike[str],
*,
allow_test_only: bool = False,
allow_undeclared_policy_atoms: bool = False
) -> Ruleset
Loads a policy set from a manifest file, applying its source-hash checks, domain validation and public query permissions.
Arguments
pathstr | os.PathLike[str]
Path to a JSON manifest; its referenced policy files are verified by the native loader.
allow_test_onlybool
False by default. True explicitly permits enabled manifest policies marked test_only; they execute normally.
allow_undeclared_policy_atomsbool
False by default. True admits undeclared source strings into this loaded policy only; it does not change the registered domain.
Return value
Ruleset

Returns a new Ruleset containing the admitted policies. File, integrity or policy-validation failures raise MaelysDatalogError; no partially loaded set is returned.

Example
ruleset = engine.load_manifest("policies/manifest.json")
print(ruleset.policy_count)

Usage and examples

Choose a loading path, load inline source or verified manifest files, then explore filters written in the policy. Both loaders require the vocabulary described in Registries.

The reference above gives the exact declarations; the sections below explain their use.

Loading path

There are two ways to get a policy loaded: the Manifest path (JSON manifest, SHA-256 verification, Public Query Whitelist) and the Inline path (raw .dl text, no manifest, no manifest query whitelist). The original reference distinguishes file/buffer manifest loading and static/dynamic inline loading. Keep that distinction, but do not confuse the low-level C functions with the public entry points used by Python:

PathVariantPython / opaque C API
Manifestfileengine.load_manifest(path) → maelys_datalog_policy_load_manifest; verifies manifest and policy files.
ManifestbufferNot exposed by this facade/binding. load_manifest takes a filesystem path, not JSON text.
Inlinestatic C tableNo combined static-table Python entry point. Register the vocabulary from Python values, then load source.
Inlinedynamicengine.register_domain(...) then engine.load_inline_ruleset(...) → maelys_datalog_domain_register + maelys_datalog_policy_load_inline.

Python therefore supports Manifest, file and Inline, dynamic. The original Python binding supports only the latter. See the current C manifest reference for loading paths. The historical 0.9.1 registry discussion gives a wider native model; this table states the subset actually exposed here.

Domain Registry
vocabulary authority
Manifest · file
  1. Verified files
    load_manifest(path)
Inline · dynamic
  1. Source text
    load_inline_ruleset(...)

ruleset = engine.load_inline_ruleset(domain, policy_id, source) calls maelys_datalog_policy_load_inline through CFFI. engine.register_domain(...) calls maelys_datalog_domain_register — the registration step that must already have declared the vocabulary. Together, the Python calls preserve the same order as C: register, then load.

Why not the static variant too? A combined static loader solves a C-specific problem: its predicate table already exists as a compile-time C array. In Python, register_domain(name, predicates, atoms=...) accepts plain Python values directly. There is no compile-time C table to skip past; registration and loading remain explicit, separate operations.

What changes for manifests? SHA-256 verification and a Public Query Whitelist are real additions over the original binding. load_manifest(path) uses the native loader, not a Python reimplementation of its checks. It can return a policy set containing several policies; subsequent evaluation selects one by policy_index. The manifest example shows packaging, hashing and loading the exact source bytes.

The manifest example covers verified files; the inline example below uses source text held in memory. Language filters below are written in the policy source, not exposed as Python callback methods.

Ruleset loading

CODE
ruleset = engine.load_inline_ruleset(
    "document_access", "doc.main",
    "allow(User, Document) :- owns(User, Document).\n",
)

Here source is the .dl program text, not a filename. Loading parses and validates it against the registered domain and returns a Ruleset; it does not yet evaluate runtime inputs. The wrapper encodes the Python string as UTF-8 and passes its byte length to the C facade, so there is no manual lengthBytesUTF8 bookkeeping. Parse and validation failures raise MaelysDatalogError with the native diagnostic, not an empty ruleset.

The source accepts the full supported language, including bounded positive or disjunction, stratified not(...), wildcards and standard filters. Those are language features in the source, not different Python loading methods. Each predicate must already exist in the domain, and string literals must meet the vocabulary rules above.

engine.load_inline_ruleset(domain, policy_id, source) compiles one policy against an already registered domain. Standard filters and language constructs are expressed directly in policy source, not through special Python methods. Invalid source or vocabulary raises MaelysDatalogError.

Loading a verified manifest

Use engine.load_manifest(path) for a native manifest. It verifies source SHA-256 digests and query declarations in the native loader, atomically. ruleset.policy_count reports the loaded count; policy_index selects a zero-based policy from that set. The default is index zero.

allow_undeclared_policy_atoms=True explicitly admits policy-local string constants without mutating the global domain. It does not make predicates dynamic, affect runtime facts, or change inline loading. Referenced atoms remain bounded: 256 distinct entries, each at most 63 UTF-8 bytes, subject to native pool limits. Filter patterns are separate (256-byte maximum). allow_test_only=True is an independent permission to load enabled manifest entries marked "mode": "test_only". Without it, the loader returns FORBIDDEN, surfaced as MaelysDatalogError. It is not a dry run: an admitted policy is evaluated normally. It does not relax atom declarations or integrity checks, and the engine does not detect whether the caller is running in production.

Both options default to False, can be combined, and apply only to load_manifest(). load_inline_ruleset() guarantees declared policy constants and exposes no permission to bypass that check. The C facade's corresponding permission mask rejects unknown bits with INVALID_ARGUMENT; since 0.7.1, MAELYS_DATALOG_PUBLIC_ALLOW_NONE names its permanent zero value. Python exposes named boolean arguments instead of an arbitrary mask, so the ordinary call with both defaults false is already the Python equivalent. See public loading permissions.

This complete example creates a temporary manifest and source, computes the digest from the exact source bytes, then loads and evaluates them. In production, use the manifest and sources produced by your policy packaging workflow.

CODE
import hashlib
import json
from pathlib import Path
from tempfile import TemporaryDirectory
from maelys_datalog import Engine, Predicate, PRED_EDB, PRED_IDB, PRED_QUERY

with TemporaryDirectory() as directory, Engine() as engine:
    root = Path(directory)
    engine.register_domain("manifest_docs", [
        Predicate("user", 1, PRED_EDB),
        Predicate("allow", 1, PRED_IDB | PRED_QUERY),
    ])
    source = b'allow("alice") :- user("alice").\n'
    (root / "access.dl").write_bytes(source)
    manifest = {
        "policy_set_id": "documents", "policy_set_version": "1",
        "manifest_version": "1", "default_profile": "enforce",
        "created_for": "example", "strict_loading": True,
        "fail_closed": True, "capabilities": [],
        "policies": [{
            "policy_id": "access", "domain": "manifest_docs",
            "file": "access.dl", "sha256": hashlib.sha256(source).hexdigest(),
            "mode": "enforce", "enabled": True, "description": "Document access",
            "queries": [{"name": "allow", "arity": 1}],
        }],
    }
    path = root / "manifest.json"
    path.write_text(json.dumps(manifest), encoding="utf-8")
    ruleset = engine.load_manifest(path, allow_undeclared_policy_atoms=True)
    print("policies:", ruleset.policy_count)
    edb = ruleset.edb()
    edb.add_fact("user", ["alice"])
    with ruleset.solve(edb, policy_index=0) as result:
        print(result.contains_fact("allow", ["alice"]))
CODE
policies: 1
True

Here "alice" is admitted only into this loaded policy. A subsequent inline load using it still needs the closed domain whitelist. Altering access.dl without updating its manifest digest makes loading fail, not execute different rules silently.

Standard filters in policy source

Filters are not Python callbacks. The standard starts_with, ends_with and contains operations are written in the Datalog body and evaluated by the engine. They are non-binding: a positive atom must first bind the value they inspect.

CODE
from maelys_datalog import Engine, Predicate, PRED_EDB, PRED_IDB, PRED_QUERY

with Engine() as engine:
    engine.register_domain("filters_next_docs", [
        Predicate("file", 1, PRED_EDB),
        Predicate("allow", 1, PRED_IDB | PRED_QUERY),
    ])
    ruleset = engine.load_inline_ruleset(
        "filters_next_docs", "files.main",
        'allow(Path) :- file(Path), starts_with(Path, "docs/"), '
        'ends_with(Path, ".md"), contains(Path, "api").',
    )
    edb = ruleset.edb()
    edb.add_facts([
        ("file", ["docs/api.md"]),
        ("file", ["docs/guide.md"]),
        ("file", ["src/api.md"]),
    ])
    with ruleset.solve(edb) as result:
        print(result.enumerate_predicate_facts("allow", 1))
CODE
[('docs/api.md',)]

Here the patterns "docs/", ".md" and "api" are filter parameters, not policy atoms: no atoms=[...] list is needed for them. Moving a filter before any positive binding, or omitting that binding, does not make it generate values; an unsafe rule is rejected. Writing a custom native filter implementation is a different task, covered by the extension SDK rather than a Python callback registration API.