0
# Context and Symbol Resolution
1
2
Context management system for controlling how symbols are resolved, type checking is performed, and default values are handled. The context system enables custom symbol resolution, type safety, and flexible data access patterns.
3
4
## Capabilities
5
6
### Context Creation
7
8
Create context objects that define how symbols are resolved and type checking is performed.
9
10
```python { .api }
11
class Context:
12
def __init__(self, *, regex_flags=0, resolver=None, type_resolver=None,
13
default_timezone='local', default_value=UNDEFINED, decimal_context=None):
14
"""
15
Create a context for rule evaluation.
16
17
Args:
18
regex_flags (int): Flags for regex operations (re module flags)
19
resolver: Function for symbol value resolution
20
type_resolver: Function or mapping for symbol type resolution
21
default_timezone: Default timezone ('local', 'utc', or tzinfo instance)
22
default_value: Default value for unresolved symbols
23
decimal_context: Decimal context for float operations
24
"""
25
```
26
27
**Usage Example:**
28
29
```python
30
import rule_engine
31
32
# Basic context with default value
33
context = rule_engine.Context(default_value="N/A")
34
rule = rule_engine.Rule('missing_field', context=context)
35
result = rule.evaluate({}) # Returns "N/A"
36
37
# Context with custom resolver
38
def custom_resolver(symbol_name, obj):
39
if symbol_name == 'full_name':
40
return f"{obj.get('first', '')} {obj.get('last', '')}"
41
return obj.get(symbol_name)
42
43
context = rule_engine.Context(resolver=custom_resolver)
44
rule = rule_engine.Rule('full_name', context=context)
45
person = {'first': 'John', 'last': 'Doe'}
46
print(rule.evaluate(person)) # "John Doe"
47
```
48
49
### Symbol Resolution
50
51
Resolve symbols to values using context-specific resolution functions.
52
53
```python { .api }
54
def resolve(self, thing, name: str, scope: str = None):
55
"""
56
Resolve a symbol name to its value.
57
58
Args:
59
thing: The object to resolve the symbol against
60
name (str): The symbol name to resolve
61
scope (str, optional): The scope for symbol resolution
62
63
Returns:
64
The resolved value for the symbol
65
66
Raises:
67
SymbolResolutionError: If the symbol cannot be resolved
68
"""
69
```
70
71
### Attribute Resolution
72
73
Resolve attributes from object values within the context.
74
75
```python { .api }
76
def resolve_attribute(self, thing, object_, name: str):
77
"""
78
Resolve an attribute from an object value.
79
80
Args:
81
thing: The root object for resolution
82
object_: The object to access the attribute on
83
name (str): The attribute name to resolve
84
85
Returns:
86
The resolved attribute value
87
88
Raises:
89
AttributeResolutionError: If the attribute cannot be resolved
90
"""
91
```
92
93
### Type Resolution
94
95
Resolve type information for symbols during rule parsing.
96
97
```python { .api }
98
def resolve_type(self, name: str, scope: str = None):
99
"""
100
Resolve type information for a symbol.
101
102
Args:
103
name (str): The symbol name to get type information for
104
scope (str, optional): The scope for type resolution
105
106
Returns:
107
DataType: The data type for the symbol
108
109
Raises:
110
SymbolResolutionError: If type cannot be resolved
111
"""
112
```
113
114
### Assignment Management
115
116
Manage temporary symbol assignments within expression contexts.
117
118
```python { .api }
119
@contextlib.contextmanager
120
def assignments(self, *assignments):
121
"""
122
Add assignments to a thread-specific scope.
123
124
Args:
125
*assignments: Assignment objects to define in the scope
126
127
Yields:
128
Context manager for assignment scope
129
"""
130
```
131
132
### Standalone Attribute Resolution
133
134
Standalone function for resolving symbols as object attributes with error handling and suggestions.
135
136
```python { .api }
137
def resolve_attribute(thing, name: str):
138
"""
139
Resolve a symbol as an attribute of the provided object.
140
141
Args:
142
thing: The object to access the attribute on
143
name (str): The attribute name to resolve
144
145
Returns:
146
The value of the specified attribute
147
148
Raises:
149
SymbolResolutionError: If the attribute doesn't exist
150
151
Warning:
152
This exposes all attributes of the object. Use custom resolvers
153
for security-sensitive applications.
154
"""
155
```
156
157
**Usage Example:**
158
159
```python
160
import rule_engine
161
162
class Person:
163
def __init__(self, name, age):
164
self.name = name
165
self.age = age
166
self._private = "secret"
167
168
# Using attribute resolution
169
context = rule_engine.Context(resolver=rule_engine.resolve_attribute)
170
rule = rule_engine.Rule('name + " is " + str(age)', context=context)
171
172
person = Person("Alice", 30)
173
result = rule.evaluate(person)
174
print(result) # "Alice is 30"
175
176
# Accessing private attributes (be careful!)
177
private_rule = rule_engine.Rule('_private', context=context)
178
print(private_rule.evaluate(person)) # "secret"
179
```
180
181
### Item Resolution
182
183
Resolve symbols as dictionary/mapping keys with error handling and suggestions.
184
185
```python { .api }
186
def resolve_item(thing, name: str):
187
"""
188
Resolve a symbol as a key in a mapping object.
189
190
Args:
191
thing: The mapping object to access
192
name (str): The key name to resolve
193
194
Returns:
195
The value for the specified key
196
197
Raises:
198
SymbolResolutionError: If the key doesn't exist or object isn't a mapping
199
"""
200
```
201
202
**Usage Example:**
203
204
```python
205
import rule_engine
206
207
# Using item resolution for dictionaries
208
context = rule_engine.Context(resolver=rule_engine.resolve_item)
209
rule = rule_engine.Rule('user_id > 1000 and status == "active"', context=context)
210
211
user_data = {
212
'user_id': 1234,
213
'status': 'active',
214
'email': 'user@example.com'
215
}
216
217
print(rule.matches(user_data)) # True
218
219
# Works with any mapping-like object
220
from collections import OrderedDict
221
ordered_data = OrderedDict([('priority', 1), ('task', 'important')])
222
task_rule = rule_engine.Rule('priority == 1', context=context)
223
print(task_rule.matches(ordered_data)) # True
224
```
225
226
### Type Resolution
227
228
Create type resolvers from dictionaries to enable type checking and validation.
229
230
```python { .api }
231
def type_resolver_from_dict(dictionary: dict):
232
"""
233
Create a type resolver function from a dictionary mapping.
234
235
Args:
236
dictionary (dict): Mapping of symbol names to DataType constants
237
238
Returns:
239
A type resolver function for use with Context
240
241
Raises:
242
SymbolResolutionError: If a symbol is not defined in the dictionary
243
"""
244
```
245
246
**Usage Example:**
247
248
```python
249
import rule_engine
250
251
# Define types for symbols
252
type_map = {
253
'name': rule_engine.DataType.STRING,
254
'age': rule_engine.DataType.FLOAT,
255
'active': rule_engine.DataType.BOOLEAN,
256
'tags': rule_engine.DataType.ARRAY,
257
'metadata': rule_engine.DataType.MAPPING
258
}
259
260
type_resolver = rule_engine.type_resolver_from_dict(type_map)
261
context = rule_engine.Context(type_resolver=type_resolver)
262
263
# This will work - types match
264
rule = rule_engine.Rule('name + " is " + str(age)', context=context)
265
266
# This will fail - type mismatch
267
try:
268
bad_rule = rule_engine.Rule('name + age', context=context) # Can't add string + number
269
except rule_engine.EvaluationError as e:
270
print(f"Type error: {e.message}")
271
272
# This will fail - undefined symbol
273
try:
274
unknown_rule = rule_engine.Rule('unknown_field == "value"', context=context)
275
except rule_engine.SymbolResolutionError as e:
276
print(f"Unknown symbol: {e.symbol_name}")
277
```
278
279
## Advanced Context Patterns
280
281
### Combined Resolvers
282
283
Use both type and value resolvers together for comprehensive symbol management.
284
285
```python
286
import rule_engine
287
288
# Custom resolver that handles special cases
289
def smart_resolver(symbol_name, obj):
290
if symbol_name == 'full_name':
291
return f"{obj.get('first_name', '')} {obj.get('last_name', '')}"
292
elif symbol_name == 'is_adult':
293
return obj.get('age', 0) >= 18
294
return rule_engine.resolve_item(obj, symbol_name)
295
296
# Type definitions including computed fields
297
type_map = {
298
'first_name': rule_engine.DataType.STRING,
299
'last_name': rule_engine.DataType.STRING,
300
'age': rule_engine.DataType.FLOAT,
301
'full_name': rule_engine.DataType.STRING, # computed field
302
'is_adult': rule_engine.DataType.BOOLEAN # computed field
303
}
304
305
context = rule_engine.Context(
306
type_resolver=rule_engine.type_resolver_from_dict(type_map),
307
resolver=smart_resolver
308
)
309
310
rule = rule_engine.Rule('is_adult and full_name =~ "John.*"', context=context)
311
person = {'first_name': 'John', 'last_name': 'Doe', 'age': 25}
312
print(rule.matches(person)) # True
313
```
314
315
### Security-Conscious Resolution
316
317
Create secure resolvers that limit access to specific attributes or keys.
318
319
```python
320
import rule_engine
321
322
def secure_resolver(symbol_name, obj):
323
# Whitelist of allowed attributes
324
allowed_fields = {'name', 'email', 'department', 'role'}
325
326
if symbol_name not in allowed_fields:
327
raise rule_engine.SymbolResolutionError(symbol_name)
328
329
return getattr(obj, symbol_name, None)
330
331
context = rule_engine.Context(resolver=secure_resolver)
332
rule = rule_engine.Rule('department == "engineering"', context=context)
333
334
class Employee:
335
def __init__(self):
336
self.name = "John"
337
self.department = "engineering"
338
self.salary = 100000 # This won't be accessible
339
340
employee = Employee()
341
print(rule.matches(employee)) # True
342
343
# This will fail - salary is not in the whitelist
344
try:
345
salary_rule = rule_engine.Rule('salary > 50000', context=context)
346
salary_rule.matches(employee)
347
except rule_engine.SymbolResolutionError as e:
348
print(f"Access denied to: {e.symbol_name}")
349
```
350
351
### Context Properties
352
353
Access context configuration and metadata.
354
355
```python
356
context = rule_engine.Context(
357
type_resolver=rule_engine.type_resolver_from_dict({'name': rule_engine.DataType.STRING}),
358
resolver=rule_engine.resolve_item,
359
default_value="unknown"
360
)
361
362
print(f"Has type resolver: {context.type_resolver is not None}")
363
print(f"Has custom resolver: {context.resolver is not None}")
364
print(f"Default value: {context.default_value}")
365
```
366
367
## Error Handling and Debugging
368
369
Context operations can raise various exceptions that provide detailed error information:
370
371
```python
372
import rule_engine
373
374
try:
375
context = rule_engine.Context()
376
rule = rule_engine.Rule('undefined_symbol', context=context)
377
rule.evaluate({})
378
except rule_engine.SymbolResolutionError as e:
379
print(f"Symbol '{e.symbol_name}' not found")
380
if e.thing is not rule_engine.UNDEFINED:
381
print(f"Available attributes: {dir(e.thing)}")
382
if e.suggestion:
383
print(f"Did you mean: {e.suggestion}")
384
385
# Type resolution errors
386
try:
387
type_resolver = rule_engine.type_resolver_from_dict({'age': rule_engine.DataType.FLOAT})
388
context = rule_engine.Context(type_resolver=type_resolver)
389
rule = rule_engine.Rule('name == "John"', context=context) # 'name' not defined
390
except rule_engine.SymbolResolutionError as e:
391
print(f"Type not defined for symbol: {e.symbol_name}")
392
```