gristlabs_grist-core/sandbox/grist/attribute_recorder.py

60 lines
1.8 KiB
Python
Raw Normal View History

from six.moves import reprlib
import records
class AttributeRecorder(object):
"""
Wrapper around a Record that records attribute accesses.
Used to generate a prompt for the AI with basic 'debugging' info.
"""
def __init__(self, inner, name, attributes):
assert isinstance(inner, records.Record)
self._inner = inner
self._name = name
self._attributes = attributes
def __getattr__(self, name):
"""
Record attribute access.
If the result is a Record or RecordSet, wrap that with AttributeRecorder
to also record nested attribute values.
"""
result = getattr(self._inner, name)
full_name = "{}.{}".format(self._name, name)
if isinstance(result, records.Record):
result = AttributeRecorder(result, full_name, self._attributes)
elif isinstance(result, records.RecordSet):
# Use a tuple to imply immutability so that the AI doesn't try appending.
# Don't try recording attributes of all contained records, just record the first access.
# Pretend that the attribute is always accessed from the first record for simplicity.
result = tuple(AttributeRecorder(r, full_name + "[0]", self._attributes) for r in result)
self._attributes.setdefault(full_name, safe_repr(result))
return result
def __repr__(self):
# The usual Record repr looks like Table1[2] which may surprise the AI.
return "{}(id={})".format(self._inner._table.table_id, self._inner._row_id)
arepr = reprlib.Repr()
arepr.maxlevel = 3
arepr.maxtuple = 3
arepr.maxlist = 3
arepr.maxarray = 3
arepr.maxdict = 4
arepr.maxset = 3
arepr.maxfrozenset = 3
arepr.maxdeque = 3
arepr.maxstring = 40
arepr.maxlong = 20
arepr.maxother = 60
def safe_repr(x):
try:
return arepr.repr(x)
except Exception:
# Copied from Repr.repr_instance in Python 3.
return '<%s instance at %#x>' % (x.__class__.__name__, id(x))