boolia
A tiny, safe boolean expression engine: like Jinja for logic.
- Grammar:
and,or,not, parentheses, comparisons (== != > >= <= <),in - Values: numbers, strings, booleans,
null/None, identifiers, dotted paths (user.age,house.light.on,cart.owner.country,cart.owner.get_country) - Tags: bare identifiers evaluate
Trueif present in atags: set[str] - Functions: user-registered, safe callables (
starts_with,matches, ...) - RuleBook: name your rules and evaluate them later
- RuleGroup: compose rules with
all/anysemantics and nested groups - Missing policy: choose to raise or substitute None/False/custom default
- Serialization: export/import rule books as JSON or (optionally) YAML
from boolia import evaluate, RuleBook, DEFAULT_FUNCTIONS
expr = "(car and elephant) or house.light.on"
print(evaluate(expr, context={"house": {"light": {"on": True}}}, tags={"car"})) # True
Install
pip install boolia
Tooling
The project ships with Ruff for linting and MyPy for type checking. After installing the development extras you can run the primary checks with:
ruff check .
mypy .
Quick start
from boolia import evaluate, DEFAULT_FUNCTIONS
ctx = {"user": {"age": 21, "roles": ["admin", "ops"]}}
tags = {"beta"}
expr = "user.age >= 18 and 'admin' in user.roles"
print(evaluate(expr, context=ctx, tags=tags)) # True
Context traversal
When context values are plain objects, boolia walks their public attributes and automatically invokes bound methods that accept no arguments, letting you jump across Python models without adapters.
from boolia import evaluate
class Account:
country = "Australia"
province = "NSW"
def get_country(self):
return self.country
class User:
def get_account(self):
return Account()
ctx = {"user": User()}
print(evaluate("user.get_account.get_country == 'Australia' and user.get_account.province == 'NSW'", context=ctx)) # True
If a bound method requires positional arguments, the resolver treats it as a missing path. That means on_missing="raise" surfaces a MissingVariableError, while the other policies (false, none, or default) return their configured fallback.
Functions
from boolia import evaluate, DEFAULT_FUNCTIONS
DEFAULT_FUNCTIONS.register("starts_with", lambda s, p: str(s).startswith(str(p)))
expr = "starts_with(user.name, 'Sn')"
print(evaluate(expr, context={"user": {"name": "Snoopy"}})) # True
Bulk evaluation
from boolia import evaluate_all, evaluate_any
rules = ["1", "true", "x", "y == 1"]
context = {"x": True, "y": 1}
evaluate_all(rules, context=context) # True
evaluate_any(["false", "x"], context=context) # True
Custom operators
from boolia import evaluate, DEFAULT_OPERATORS
custom_ops = DEFAULT_OPERATORS.copy()
custom_ops.register(
"XOR", # The operator identifier
precedence=20, # Higher precedence than AND/OR
evaluator=lambda left, right: bool(left) ^ bool(right), # XOR logic
keywords=("xor",), # Use "xor" in expressions
)
print(evaluate("true xor false", operators=custom_ops)) # True
print(evaluate("true xor true", operators=custom_ops)) # False
Operators can be declared with keywords=("xor",) for word-style syntax or symbols=("^",)
for symbolic tokens. Use compile_rule(expr, operators=custom_ops) to persist custom
operators inside compiled rules. When evaluating rules or rule groups you can still pass a
different registry with operators= if you need to override their behavior.
RuleBook
from boolia import RuleBook, RuleGroup
rules = RuleBook()
rules.add("adult", "user.age >= 18")
rules.add("brazilian", "starts_with(user.country, 'Br')")
rules.add("vip", "contains(user.roles, 'vip')")
rules.add_group(
"eligible",
mode="all",
members=[
"adult",
RuleGroup(mode="any", members=["brazilian", "vip"]),
],
)
ok = rules.evaluate(
"eligible",
context={"user": {"age": 22, "country": "Brazil", "roles": ["member"]}},
)
print(ok) # True
print(rules.evaluate("eligible", context={"user": {"age": 22, "country": "Chile", "roles": ["vip"]}})) # True
print(rules.evaluate("eligible", context={"user": {"age": 17, "country": "Chile", "roles": ["member"]}})) # False
RuleGroup members can be rule names, already compiled Rule objects, or other RuleGroup instances. Nested groups short-circuit according to their mode (all/any), empty groups are vacuously True/False, and cycles raise a helpful error. Add groups with RuleBook.add_group or register existing ones with RuleBook.register.
RuleBook serialization
from boolia import RuleBook
rules = RuleBook()
rules.add("adult", "user.age >= 18")
rules.add_group("gate", members=["adult"])
payload = rules.to_dict()
clone = RuleBook.from_dict(payload)
assert clone.evaluate("gate", context={"user": {"age": 21}})
json_blob = rules.to_json(indent=2)
loaded = RuleBook.from_json(json_blob)
# Optional YAML helpers (requires: pip install boolia[yaml])
yaml_blob = rules.to_yaml()
RuleBook.from_yaml(yaml_blob)
RuleBook.to_dict/RuleBook.from_dictare the canonical API and perform schema validation by default.to_json/from_jsonare always available via the standard library.to_yaml/from_yamllazily import PyYAML; missing dependencies raise a clearRulebookSerializationError.- Pass custom JSON encoders/decoders (e.g.
orjson.dumps) via theencoder=/decoder=keyword arguments.
Payloads include a schema version to enable future migrations. Inline rules or groups are supported when importing by default; pass allow_inline=False to reject them.
Missing policy
from boolia import evaluate, MissingVariableError
try:
evaluate("user.age >= 18 and house.light.on", context={"user": {"age": 20}}, on_missing="raise")
except MissingVariableError as e:
print(e) # Missing variable/path: house.light.on
print(evaluate("score >= 10", context={}, on_missing="default", default_value=0)) # False
print(evaluate("flag and beta", context={}, tags={"beta"}, on_missing="none")) # False (flag is None)
Notes
- Use
on_missing="none"if you want tags to override missing bare identifiers. - For stricter semantics on dotted paths, keep
on_missing="raise"and allow tags only for bare names.
Local development
pip install -e .[dev]
pytest -q
ruff check .
mypy .
Metadata
Release files for boolia 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| boolia-0.1.3.tar.gz | 36.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| boolia-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 53.3 kB
Release files / boolia-0.1.3.tar.gz
| Download URL | boolia-0.1.3.tar.gz |
|---|---|
| Size | 36.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
757e2eb617ae1abd20bb1919146f086c9c3d6e4d46642cfc0d86d2865cddadea
|
|
BLAKE2b-256 checksum How to use checksums |
a06baf892c2598fef833c0d3e3b80573f23306b94f638749e52266ee4a04e304
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2025.
Transparency logRelease files / boolia-0.1.3-py3-none-any.whl
| Download URL | boolia-0.1.3-py3-none-any.whl |
|---|---|
| Size | 16.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fab15a7c82971263a33f5b40f22240dde9cbc95eb479fe7720f2275e23bb677f
|
|
BLAKE2b-256 checksum How to use checksums |
edd5b2cff4b296ec3ccaa798b36eb35804eeb5ff2158d8acc2cb193ae899339a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2025.
Transparency log