boolia


Nameboolia JSON
Version 0.1.2 PyPI version JSON
download
home_pageNone
SummaryTiny, safe boolean-expression language with dotted paths, functions, and rule books.
upload_time2025-10-13 03:07:07
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT
keywords boolean dsl expression jinja-like logic rules
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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`)
- **Tags**: bare identifiers evaluate `True` if present in a `tags: set[str]`
- **Functions**: user-registered, safe callables (`starts_with`, `matches`, ...)
- **RuleBook**: name your rules and evaluate them later
- **RuleGroup**: compose rules with `all`/`any` semantics and nested groups
- **Missing policy**: choose to **raise** or substitute **None/False/custom default**

```py
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

```bash
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:

```bash
ruff check .
mypy .
```

## Quick start

```py
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
```

### Functions

```py
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
```

### Custom operators

```py
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

```py
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`.

### Missing policy

```py
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

```bash
pip install -e .[dev]
pytest -q
ruff check .
mypy .
```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "boolia",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "Jo\u00e3o Freires <joaofreires@tutanota.com>",
    "keywords": "boolean, dsl, expression, jinja-like, logic, rules",
    "author": null,
    "author_email": "Jo\u00e3o Freires <joaofreires@tutanota.com>",
    "download_url": "https://files.pythonhosted.org/packages/97/2a/9566fb93c6fd7236068df99335cdfc467289eb3724f650b8d929efb2a655/boolia-0.1.2.tar.gz",
    "platform": null,
    "description": "# boolia\n\nA tiny, safe **boolean expression** engine: like Jinja for logic.\n\n- **Grammar**: `and`, `or`, `not`, parentheses, comparisons (`== != > >= <= <`), `in`\n- **Values**: numbers, strings, booleans, `null/None`, identifiers, dotted paths (`user.age`, `house.light.on`)\n- **Tags**: bare identifiers evaluate `True` if present in a `tags: set[str]`\n- **Functions**: user-registered, safe callables (`starts_with`, `matches`, ...)\n- **RuleBook**: name your rules and evaluate them later\n- **RuleGroup**: compose rules with `all`/`any` semantics and nested groups\n- **Missing policy**: choose to **raise** or substitute **None/False/custom default**\n\n```py\nfrom boolia import evaluate, RuleBook, DEFAULT_FUNCTIONS\n\nexpr = \"(car and elephant) or house.light.on\"\nprint(evaluate(expr, context={\"house\": {\"light\": {\"on\": True}}}, tags={\"car\"}))  # True\n```\n\n## Install\n\n```bash\npip install boolia\n```\n\n## Tooling\n\nThe project ships with Ruff for linting and MyPy for type checking. After installing the\ndevelopment extras you can run the primary checks with:\n\n```bash\nruff check .\nmypy .\n```\n\n## Quick start\n\n```py\nfrom boolia import evaluate, DEFAULT_FUNCTIONS\n\nctx  = {\"user\": {\"age\": 21, \"roles\": [\"admin\", \"ops\"]}}\ntags = {\"beta\"}\nexpr = \"user.age >= 18 and 'admin' in user.roles\"\nprint(evaluate(expr, context=ctx, tags=tags))  # True\n```\n\n### Functions\n\n```py\nfrom boolia import evaluate, DEFAULT_FUNCTIONS\n\nDEFAULT_FUNCTIONS.register(\"starts_with\", lambda s, p: str(s).startswith(str(p)))\n\nexpr = \"starts_with(user.name, 'Sn')\"\nprint(evaluate(expr, context={\"user\": {\"name\": \"Snoopy\"}}))  # True\n```\n\n### Custom operators\n\n```py\nfrom boolia import evaluate, DEFAULT_OPERATORS\n\ncustom_ops = DEFAULT_OPERATORS.copy()\ncustom_ops.register(\n    \"XOR\", # The operator identifier\n    precedence=20, # Higher precedence than AND/OR\n    evaluator=lambda left, right: bool(left) ^ bool(right), # XOR logic\n    keywords=(\"xor\",), # Use \"xor\" in expressions\n)\n\nprint(evaluate(\"true xor false\", operators=custom_ops))  # True\nprint(evaluate(\"true xor true\", operators=custom_ops))   # False\n```\n\nOperators can be declared with `keywords=(\"xor\",)` for word-style syntax or `symbols=(\"^\",)`\nfor symbolic tokens. Use `compile_rule(expr, operators=custom_ops)` to persist custom\noperators inside compiled rules. When evaluating rules or rule groups you can still pass a\ndifferent registry with `operators=` if you need to override their behavior.\n\n### RuleBook\n\n```py\nfrom boolia import RuleBook, RuleGroup\n\nrules = RuleBook()\nrules.add(\"adult\", \"user.age >= 18\")\nrules.add(\"brazilian\", \"starts_with(user.country, 'Br')\")\nrules.add(\"vip\", \"contains(user.roles, 'vip')\")\nrules.add_group(\n    \"eligible\",\n    mode=\"all\",\n    members=[\n        \"adult\",\n        RuleGroup(mode=\"any\", members=[\"brazilian\", \"vip\"]),\n    ],\n)\n\nok = rules.evaluate(\n    \"eligible\",\n    context={\"user\": {\"age\": 22, \"country\": \"Brazil\", \"roles\": [\"member\"]}},\n)\nprint(ok)  # True\n\nprint(rules.evaluate(\"eligible\", context={\"user\": {\"age\": 22, \"country\": \"Chile\", \"roles\": [\"vip\"]}}))  # True\nprint(rules.evaluate(\"eligible\", context={\"user\": {\"age\": 17, \"country\": \"Chile\", \"roles\": [\"member\"]}}))  # False\n```\n\n`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`.\n\n### Missing policy\n\n```py\nfrom boolia import evaluate, MissingVariableError\n\ntry:\n    evaluate(\"user.age >= 18 and house.light.on\", context={\"user\": {\"age\": 20}}, on_missing=\"raise\")\nexcept MissingVariableError as e:\n    print(e)  # Missing variable/path: house.light.on\n\nprint(evaluate(\"score >= 10\", context={}, on_missing=\"default\", default_value=0))  # False\nprint(evaluate(\"flag and beta\", context={}, tags={\"beta\"}, on_missing=\"none\"))     # False (flag is None)\n```\n\n### Notes\n\n- Use `on_missing=\"none\"` if you want **tags to override** missing bare identifiers.\n- For stricter semantics on dotted paths, keep `on_missing=\"raise\"` and allow tags only for bare names.\n\n## Local development\n\n```bash\npip install -e .[dev]\npytest -q\nruff check .\nmypy .\n```\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Tiny, safe boolean-expression language with dotted paths, functions, and rule books.",
    "version": "0.1.2",
    "project_urls": {
        "Changelog": "https://github.com/joaofreires/boolia/releases",
        "Documentation": "https://github.com/joaofreires/boolia#readme",
        "Homepage": "https://github.com/joaofreires/boolia",
        "Issues": "https://github.com/joaofreires/boolia/issues",
        "Repository": "https://github.com/joaofreires/boolia"
    },
    "split_keywords": [
        "boolean",
        " dsl",
        " expression",
        " jinja-like",
        " logic",
        " rules"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "a254edbf3d46fe47da6f0cc9e323e2b9c91aa36a59fca035eb0cb18e7851a1a3",
                "md5": "0f44ff0a18e1245bc162a781a482ae06",
                "sha256": "5e20ec9fdcc12bf548310caf59833ae61ea5ef2be8164d6e0ed8c9594ef1f74a"
            },
            "downloads": -1,
            "filename": "boolia-0.1.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "0f44ff0a18e1245bc162a781a482ae06",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 11637,
            "upload_time": "2025-10-13T03:07:06",
            "upload_time_iso_8601": "2025-10-13T03:07:06.724254Z",
            "url": "https://files.pythonhosted.org/packages/a2/54/edbf3d46fe47da6f0cc9e323e2b9c91aa36a59fca035eb0cb18e7851a1a3/boolia-0.1.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "972a9566fb93c6fd7236068df99335cdfc467289eb3724f650b8d929efb2a655",
                "md5": "d7169dccdc56bc761dadb170390f83af",
                "sha256": "79a92e2e884bd772274c45ce9f28f72cc71422282dc8bec87c79fd7aa960eda3"
            },
            "downloads": -1,
            "filename": "boolia-0.1.2.tar.gz",
            "has_sig": false,
            "md5_digest": "d7169dccdc56bc761dadb170390f83af",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 30305,
            "upload_time": "2025-10-13T03:07:07",
            "upload_time_iso_8601": "2025-10-13T03:07:07.816298Z",
            "url": "https://files.pythonhosted.org/packages/97/2a/9566fb93c6fd7236068df99335cdfc467289eb3724f650b8d929efb2a655/boolia-0.1.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-13 03:07:07",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "joaofreires",
    "github_project": "boolia",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "boolia"
}
        
Elapsed time: 2.56174s