FlexiRule Docs
Condition System
FlexiRule employs a sophisticated, high-performance condition system that allows users to build complex logic visually while executing it with the ...
Condition System Architecture#
FlexiRule employs a sophisticated, high-performance condition system that allows users to build complex logic visually while executing it with the speed and safety of native Python.
Ubiquitous Usage#
The Condition Builder is not limited to a single “Condition” node; it is a pervasive component used throughout FlexiRule to drive logic-based execution at multiple levels:
- Rule Trigger Eligibility: Every Rule has a global condition defined at the DocType level. This is evaluated by the
RuleCoordinatorto decide if a rule should skip or start execution. - Node-Level Branching: The
Conditionaction node uses it to split the execution path into True/False branches. - Row-Level Assignment (
Run If): InAssignmentactions, each mutation can have its own “Run If” condition, allowing for granular, state-dependent updates within a single batch. - Collection Filtering: Used within
LoopandCollectionnodes to filter datasets or define exit criteria. - Switch Case Logic: The
Switchaction uses multiple condition blocks to determine which path to follow among many options.
The Three-Tier Architecture#
The system is divided into three distinct layers, ensuring a clean separation between user interface, logic representation, and execution.
1. The Visual Builder (Frontend)#
The Condition Builder is a recursive Vue 3 interface that allows users to construct logic trees.
- Input: User interactions (drag-and-drop, field selection, operator picking).
- Output: A standardized JSON AST (Abstract Syntax Tree).
- Key Files:
ConditionBuilder.vue,ConditionNode.vue,SimpleCondition.vue. - Detailed Guide: Condition Builder Frontend
2. The Condition Compiler (Backend - Save-Time)#
To avoid the overhead of parsing JSON at runtime, FlexiRule uses a “Save-Time Compilation” strategy. When a Rule is saved, the ConditionCompiler processes the JSON AST.
- Input: JSON AST.
- Output: An optimized Python Expression String and a list of Watched Fields.
- Optimization: The compiler translates visual groups into native Python
and/orchains and collections into efficient generator expressions (e.g.,any(row.status == 'Open' for row in doc.items)). - Key Files:
compiler.py.
3. The Evaluator (Backend - Runtime)#
At runtime, the execution engine evaluates the pre-compiled Python string within a sandboxed environment.
- Input: Compiled Python string + Execution Context (
doc,vars, etc.). - Execution: Uses
frappe.safe_evalfor maximum performance with guaranteed security. - Security: Restricted to
SafeFrappeAPI, preventing any write operations or unauthorized access during evaluation. - Key Files:
runtime_eval.py,engine.py.
The Condition Evaluator (ConditionEvaluator)#
While the compiler is the modern preferred path, FlexiRule maintains a ConditionEvaluator class that can interpret the JSON AST directly. This is primarily used for:
- Legacy Compatibility: Rules created before the pre-compiler was introduced.
- Immediate Feedback: Running “Live Tests” or “Simulations” in the builder without requiring a database save.
Evaluation Lifecycle#
- Context Resolution: The evaluator resolves values from the
docorrowobjects based on the node’srefpath. - Recursive Traversal: The evaluator walks the JSON tree, applying the logical operators (
AND/OR) and quantifiers (ANY/ALL). - Coercion: Values are automatically coerced (e.g.,
1for True,0for False) to ensure consistency with Frappe’s field values.
The Condition Compiler (ConditionCompiler)#
The ConditionCompiler is responsible for transforming the structured JSON representation of conditions into a valid, safe, and optimized Python expression.
1. JSON Schema (AST)#
The compiler expects three types of nodes:
- Condition Node: A leaf node representing a single comparison.
{ "left": { "ref": "doc.status" }, "op": "==", "right": { "value": "Open" } } - Group Node: A logical container for multiple nodes.
{ "op": "and", "conditions": [...] } - Collection Node: Logic applied across a child table or list.
{ "op": "any", "collection": "doc.items", "alias": "item", "where": { ... } }
2. Operator Translation#
The compiler maps visual operators to Python equivalents and helper functions:
| Visual Operator | Python/Helper Expression |
|---|---|
==, !=, >, < | Native Python operators |
is_set | (lhs is not None and lhs != '') |
is_empty | is_empty_value(lhs) |
contains | (rhs in str(lhs) if lhs else False) |
length_gt | (length_of(lhs) > rhs) |
in | check_link_match(lhs, rhs, 'in') (for Link fields) |
3. Collection Logic (any/all/none)#
One of the most powerful features of the compiler is how it handles child tables using Python generator expressions:
- Any:
any(condition for alias in (collection or [])) - All:
all(condition for alias in (collection or [])) - None:
not any(condition for alias in (collection or []))
Example: “Check if any item has a quantity greater than 10” becomes:
any(item.get('qty') > 10 for item in (doc.get('items') or []))
4. Scope Resolution#
The compiler intelligently resolves variable scopes:
doc.status->doc.get('status')row.price->row.get('price')- Deep paths like
doc.owner.emailare wrapped in aresolve()helper:resolve(doc, 'owner.email').
Performance: Why Pre-Compilation?#
Traditional no-code engines often interpret JSON logic trees at runtime. This involves recursive function calls and dictionary lookups for every single comparison, which is slow.
FlexiRule’s approach provides several advantages:
- Fast Evaluation: Compiled Python strings run at near-native speeds.
- Short-Circuiting: We leverage Python’s native
and/orshort-circuiting. If the first part of anANDis false, the rest isn’t even evaluated. - Static Analysis (Watched Fields): By compiling at save-time, we can use regex to extract which fields the condition depends on. This allows the
RuleCoordinatorto skip rule execution entirely if none of the “Watched Fields” have changed, drastically reducing CPU load. - Dry-Run Validation: We can validate the syntax and safety of the generated Python code before the rule is even activated.
Was this page helpful?