0
# Core Rule Operations
1
2
The fundamental rule evaluation system that provides rule creation, matching, filtering, and expression evaluation capabilities. These operations form the foundation of the rule engine's functionality.
3
4
## Capabilities
5
6
### Rule Creation
7
8
Create rule objects from text expressions with optional context for type safety and custom symbol resolution.
9
10
```python { .api }
11
class Rule:
12
def __init__(self, rule_text: str, context: Context = None):
13
"""
14
Create a new rule from a text expression.
15
16
Args:
17
rule_text (str): The rule expression text to parse
18
context (Context, optional): Context for symbol resolution and type checking
19
20
Raises:
21
RuleSyntaxError: If the rule text contains syntax errors
22
EvaluationError: If type checking fails during parsing
23
"""
24
```
25
26
**Usage Example:**
27
28
```python
29
import rule_engine
30
31
# Basic rule creation
32
rule = rule_engine.Rule('age > 18 and status == "active"')
33
34
# Rule with typed context
35
context = rule_engine.Context(
36
type_resolver=rule_engine.type_resolver_from_dict({
37
'age': rule_engine.DataType.FLOAT,
38
'status': rule_engine.DataType.STRING
39
})
40
)
41
typed_rule = rule_engine.Rule('age > 18', context=context)
42
```
43
44
### Boolean Matching
45
46
Evaluate rules against data to determine if they match the specified conditions.
47
48
```python { .api }
49
def matches(self, thing, **kwargs) -> bool:
50
"""
51
Test if the rule matches the provided object.
52
53
Args:
54
thing: The object to evaluate against the rule
55
**kwargs: Additional keyword arguments passed to the evaluation context
56
57
Returns:
58
bool: True if the rule matches, False otherwise
59
60
Raises:
61
EvaluationError: If evaluation fails due to type errors or other issues
62
SymbolResolutionError: If symbols cannot be resolved
63
AttributeResolutionError: If attributes cannot be accessed
64
"""
65
```
66
67
**Usage Example:**
68
69
```python
70
rule = rule_engine.Rule('name == "John" and age >= 21')
71
72
person1 = {'name': 'John', 'age': 25}
73
person2 = {'name': 'Jane', 'age': 19}
74
75
print(rule.matches(person1)) # True
76
print(rule.matches(person2)) # False
77
78
# Using object attributes instead of dictionary
79
class Person:
80
def __init__(self, name, age):
81
self.name = name
82
self.age = age
83
84
john = Person('John', 25)
85
print(rule.matches(john)) # True
86
```
87
88
### Collection Filtering
89
90
Filter collections of objects using rule expressions, returning only matching items.
91
92
```python { .api }
93
def filter(self, things, **kwargs):
94
"""
95
Filter an iterable of objects, yielding only those that match the rule.
96
97
Args:
98
things: Iterable of objects to filter
99
**kwargs: Additional keyword arguments passed to the evaluation context
100
101
Yields:
102
Objects from the iterable that match the rule
103
104
Raises:
105
EvaluationError: If evaluation fails for any item
106
SymbolResolutionError: If symbols cannot be resolved
107
AttributeResolutionError: If attributes cannot be accessed
108
"""
109
```
110
111
**Usage Example:**
112
113
```python
114
rule = rule_engine.Rule('age >= 18 and department == "engineering"')
115
116
employees = [
117
{'name': 'Alice', 'age': 25, 'department': 'engineering'},
118
{'name': 'Bob', 'age': 17, 'department': 'engineering'},
119
{'name': 'Charlie', 'age': 30, 'department': 'marketing'},
120
{'name': 'Diana', 'age': 28, 'department': 'engineering'}
121
]
122
123
# Filter returns a generator
124
eligible_employees = list(rule.filter(employees))
125
print(f"Found {len(eligible_employees)} eligible employees")
126
127
# Can be used in loops
128
for employee in rule.filter(employees):
129
print(f"{employee['name']} is eligible")
130
```
131
132
### Expression Evaluation
133
134
Evaluate rule expressions to compute and return values rather than just boolean results.
135
136
```python { .api }
137
def evaluate(self, thing, **kwargs):
138
"""
139
Evaluate the rule expression and return the computed result.
140
141
Args:
142
thing: The object to evaluate against
143
**kwargs: Additional keyword arguments passed to the evaluation context
144
145
Returns:
146
The computed result of the expression evaluation
147
148
Raises:
149
EvaluationError: If evaluation fails due to type errors or other issues
150
SymbolResolutionError: If symbols cannot be resolved
151
AttributeResolutionError: If attributes cannot be accessed
152
"""
153
```
154
155
**Usage Example:**
156
157
```python
158
# Arithmetic expressions
159
calc_rule = rule_engine.Rule('price * quantity * (1 + tax_rate)')
160
order = {'price': 10.50, 'quantity': 3, 'tax_rate': 0.08}
161
total = calc_rule.evaluate(order)
162
print(f"Total: ${total:.2f}")
163
164
# String concatenation
165
name_rule = rule_engine.Rule('first_name + " " + last_name')
166
person = {'first_name': 'John', 'last_name': 'Doe'}
167
full_name = name_rule.evaluate(person)
168
print(full_name) # "John Doe"
169
170
# Complex expressions with built-in functions
171
data_rule = rule_engine.Rule('filter(lambda x: x > 10, numbers)')
172
data = {'numbers': [5, 15, 8, 20, 12]}
173
filtered = data_rule.evaluate(data)
174
print(list(filtered)) # [15, 20, 12]
175
```
176
177
### Rule Validation
178
179
Validate rule syntax without creating a full rule object.
180
181
```python { .api }
182
@classmethod
183
def is_valid(cls, text: str, context: Context = None) -> bool:
184
"""
185
Test whether a rule is syntactically correct.
186
187
Args:
188
text (str): The rule expression text to validate
189
context (Context, optional): Context for type checking validation
190
191
Returns:
192
bool: True if the rule is well-formed and valid
193
"""
194
```
195
196
**Usage Example:**
197
198
```python
199
import rule_engine
200
201
# Validate syntax before creating rule
202
if rule_engine.Rule.is_valid('age > 18 and status == "active"'):
203
rule = rule_engine.Rule('age > 18 and status == "active"')
204
print("Rule is valid")
205
else:
206
print("Invalid rule syntax")
207
208
# Validate with context for type checking
209
context = rule_engine.Context(
210
type_resolver=rule_engine.type_resolver_from_dict({
211
'age': rule_engine.DataType.FLOAT,
212
'status': rule_engine.DataType.STRING
213
})
214
)
215
216
if rule_engine.Rule.is_valid('age + status', context): # Will be False - type error
217
print("Valid")
218
else:
219
print("Invalid - type error")
220
```
221
222
### AST Visualization
223
224
Generate GraphViz diagrams of parsed rule abstract syntax trees for debugging.
225
226
```python { .api }
227
def to_graphviz(self):
228
"""
229
Generate a GraphViz diagram of the rule's AST.
230
231
Returns:
232
graphviz.Digraph: The rule diagram
233
234
Raises:
235
ImportError: If graphviz package is not installed
236
"""
237
```
238
239
**Usage Example:**
240
241
```python
242
import rule_engine
243
244
rule = rule_engine.Rule('age > 18 and (status == "active" or priority >= 5)')
245
digraph = rule.to_graphviz()
246
247
# Save the diagram to a file
248
digraph.render('rule_ast', format='png', cleanup=True)
249
250
# Or view the source
251
print(digraph.source)
252
```
253
254
### Rule Properties
255
256
Access rule metadata and configuration information.
257
258
```python { .api }
259
@property
260
def text(self) -> str:
261
"""The original rule text string."""
262
263
@property
264
def context(self) -> Context:
265
"""The context object associated with this rule."""
266
```
267
268
**Usage Example:**
269
270
```python
271
rule = rule_engine.Rule('age > 18')
272
print(f"Rule text: {rule.text}")
273
print(f"Has context: {rule.context is not None}")
274
275
# Accessing context properties
276
context = rule_engine.Context(default_value="unknown")
277
typed_rule = rule_engine.Rule('name', context=context)
278
print(f"Default value: {typed_rule.context.default_value}")
279
```
280
281
## Advanced Features
282
283
### Custom Keyword Arguments
284
285
Rules support custom keyword arguments that can be used in expressions or passed to custom resolvers.
286
287
```python
288
rule = rule_engine.Rule('threshold > min_value')
289
result = rule.matches({'threshold': 15}, min_value=10) # True
290
```
291
292
### Regex Matching
293
294
Use regular expression operators for string pattern matching.
295
296
```python
297
email_rule = rule_engine.Rule('email =~ ".*@company\\.com$"')
298
user = {'email': 'john.doe@company.com'}
299
print(email_rule.matches(user)) # True
300
```
301
302
### Datetime Operations
303
304
Work with datetime objects and operations.
305
306
```python
307
rule = rule_engine.Rule('created_date > parse_datetime("2023-01-01")')
308
record = {'created_date': datetime.datetime(2023, 6, 15)}
309
print(rule.matches(record)) # True
310
```
311
312
## Error Handling
313
314
All core operations can raise various exceptions that should be handled appropriately:
315
316
```python
317
import rule_engine
318
319
try:
320
rule = rule_engine.Rule('invalid syntax ===')
321
except rule_engine.RuleSyntaxError as e:
322
print(f"Syntax error: {e.message}")
323
324
try:
325
rule = rule_engine.Rule('unknown_field == "value"')
326
rule.matches({'known_field': 'value'})
327
except rule_engine.SymbolResolutionError as e:
328
print(f"Symbol error: {e.message}")
329
if e.suggestion:
330
print(f"Did you mean: {e.suggestion}")
331
```