{"id":48119270,"url":"https://github.com/joaofreires/boolia","last_synced_at":"2026-04-04T16:18:11.059Z","repository":{"id":318804345,"uuid":"1075065660","full_name":"joaofreires/boolia","owner":"joaofreires","description":"boolia is a tiny, safe boolean-expression engine for Python. It parses human-readable rules with dotted identifiers, comparisons, custom functions, and logical operators, then evaluates them against plain dict data.","archived":false,"fork":false,"pushed_at":"2025-10-15T04:23:32.000Z","size":72,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-27T18:44:49.655Z","etag":null,"topics":["boolean-logic","dsl","expression-evaluator","expression-language","python","rulebook","rules-engine","rules-processor","safe-evaluation"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/joaofreires.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-13T01:33:26.000Z","updated_at":"2025-10-15T04:22:55.000Z","dependencies_parsed_at":"2025-10-16T02:24:44.289Z","dependency_job_id":"cac3c3df-b206-4912-8685-11f205a05235","html_url":"https://github.com/joaofreires/boolia","commit_stats":null,"previous_names":["joaofreires/boolia"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/joaofreires/boolia","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joaofreires%2Fboolia","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joaofreires%2Fboolia/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joaofreires%2Fboolia/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joaofreires%2Fboolia/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/joaofreires","download_url":"https://codeload.github.com/joaofreires/boolia/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/joaofreires%2Fboolia/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31405700,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["boolean-logic","dsl","expression-evaluator","expression-language","python","rulebook","rules-engine","rules-processor","safe-evaluation"],"created_at":"2026-04-04T16:18:09.208Z","updated_at":"2026-04-04T16:18:11.050Z","avatar_url":"https://github.com/joaofreires.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# boolia\n\nA tiny, safe **boolean expression** engine: like Jinja for logic.\n\n- **Grammar**: `and`, `or`, `not`, parentheses, comparisons (`== != \u003e \u003e= \u003c= \u003c`), `in`\n- **Values**: numbers, strings, booleans, `null/None`, identifiers, dotted paths (`user.age`, `house.light.on`, `cart.owner.country`, `cart.owner.get_country`)\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- **Serialization**: export/import rule books as JSON or (optionally) YAML\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 \u003e= 18 and 'admin' in user.roles\"\nprint(evaluate(expr, context=ctx, tags=tags))  # True\n```\n\n### Context traversal\n\nWhen 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.\n\n```py\nfrom boolia import evaluate\n\n\nclass Account:\n    country = \"Australia\"\n    province = \"NSW\"\n\n    def get_country(self):\n        return self.country\n\n\nclass User:\n    def get_account(self):\n        return Account()\n\n\nctx = {\"user\": User()}\nprint(evaluate(\"user.get_account.get_country == 'Australia' and user.get_account.province == 'NSW'\", context=ctx))  # True\n```\n\nIf 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.\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### Bulk evaluation\n\n```py\nfrom boolia import evaluate_all, evaluate_any\n\nrules = [\"1\", \"true\", \"x\", \"y == 1\"]\ncontext = {\"x\": True, \"y\": 1}\n\nevaluate_all(rules, context=context)  # True\nevaluate_any([\"false\", \"x\"], context=context)  # 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 \u003e= 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#### RuleBook serialization\n\n```py\nfrom boolia import RuleBook\n\nrules = RuleBook()\nrules.add(\"adult\", \"user.age \u003e= 18\")\nrules.add_group(\"gate\", members=[\"adult\"])\n\npayload = rules.to_dict()\nclone = RuleBook.from_dict(payload)\nassert clone.evaluate(\"gate\", context={\"user\": {\"age\": 21}})\n\njson_blob = rules.to_json(indent=2)\nloaded = RuleBook.from_json(json_blob)\n\n# Optional YAML helpers (requires: pip install boolia[yaml])\nyaml_blob = rules.to_yaml()\nRuleBook.from_yaml(yaml_blob)\n```\n\n- `RuleBook.to_dict` / `RuleBook.from_dict` are the canonical API and perform schema validation by default.\n- `to_json` / `from_json` are always available via the standard library.\n- `to_yaml` / `from_yaml` lazily import PyYAML; missing dependencies raise a clear `RulebookSerializationError`.\n- Pass custom JSON encoders/decoders (e.g. `orjson.dumps`) via the `encoder=` / `decoder=` keyword arguments.\n\nPayloads 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.\n\n### Missing policy\n\n```py\nfrom boolia import evaluate, MissingVariableError\n\ntry:\n    evaluate(\"user.age \u003e= 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 \u003e= 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","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoaofreires%2Fboolia","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoaofreires%2Fboolia","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoaofreires%2Fboolia/lists"}