-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Problem
The Rust CEL implementation doesn't properly handle Python dict subclasses when used as variables in CEL Context. When evaluating expressions that access attributes on dict subclass instances, the evaluation returns None instead of the expected values.
Reproduction
from cel import cel
class CustomDict(dict):
"""A dict subclass that loads values on access"""
def __init__(self):
super().__init__()
self['key'] = 'value'
# Test with regular dict - works
regular_context = {'data': {'key': 'value'}}
env1 = cel.Context(variables=regular_context)
result1 = cel.evaluate('data.key', env1)
print(f"Regular dict: {result1}") # Output: "value"
# Test with dict subclass - doesn't work
custom_dict = CustomDict()
custom_context = {'data': custom_dict}
env2 = cel.Context(variables=custom_context)
result2 = cel.evaluate('data.key', env2)
print(f"Dict subclass: {result2}") # Output: None (expected: "value")
Impact
This issue affects code that uses dict subclasses for:
- Lazy loading values (like LazyFileLoadingDict that loads file contents on first access)
- Custom dict behaviors (caching, validation, etc.)
- Any dict wrapper or proxy pattern
Use Case
In netchecks, we use a LazyFileLoadingDict to load configuration files from a directory only when accessed:
class LazyFileLoadingDict(dict):
def __init__(self, directory):
super().__init__()
for filename in os.listdir(directory):
self[filename] = None
def __getitem__(self, key):
if super().__getitem__(key) is None:
# Load file contents on first access
with open(os.path.join(self.directory, key)) as f:
self[key] = f.read()
return super().__getitem__(key)
This worked with the Python celpy library but breaks with the Rust implementation.
Current Workaround
We're currently working around this by materializing the dict subclass to a regular dict before passing to CEL:
lazy_dict = LazyFileLoadingDict(directory)
context = {'data': dict(lazy_dict)} # Convert to regular dict
However, this defeats the purpose of lazy loading and requires changes in calling code.
Expected Behavior
Dict subclasses should be treated the same as regular dicts when accessing their contents via CEL expressions. The Rust CEL implementation should use dict protocol (__getitem__) rather than attribute access when evaluating member access on dict-like objects.
Environment
- python-common-expression-language: 0.5.3
- Python: 3.12
- OS: Linux
Related
This regression was introduced when switching from the Python celpy library to the Rust-based implementation. The Python implementation properly handled dict subclasses.