I would like to propose a change to the Row.get()
to make its behavior more consistent with a python dict
.
If we stop considering that every row is fully populated at creation with None
and start taking advantage of the underlying null
value for empty values, I think we can make the get()
method more consistent and more useful.
Let me give some examples:
account_row = app_tables.my_accounts.add_row(id='me')
account_dict = dict(id='me')
# Alignment with expectation
print(account_row.get('id', "no id set"))
>> 'me'
print(account_dict.get('id', "no id set"))
>> 'me'
# Key in table but value is not set
# This behavior does not seem useful
print(account_row.get('last_update', 0))
>> None
print(account_dict.get('last_update', 0))
>> 0
# Key not in table
# This seems... dangerous
print(account_row.get('non_existent_key', True))
>> True
print(account_dict.get('non_existent_key', True))
>> True
I think that the row
object should feel like a dict
that has defined key scope.
My proposal would be to change this to:
# Proposed behavior
# Key in table but not set
print(account_row.get('last_update', 0))
>> 0
print(account_dict.get('last_update', 0))
>> 0
# The dict consistent behavior for a missing key
print(account_row.get('non_existent_key', True))
>> True
print(account_dict.get('non_existent_key', True))
>> True
# Maybe better behavior for missing key:
# auto create missing columns: False
print(account_row.get('non_existent_key', True))
>> NoSuchColumnError
# auto create missing columns: True
print(account_row.get('non_existent_key', True))
>> True
print(account_dict.get('non_existent_key', True))
>> True
The or
method works, it has some pitfalls with bool(table_value)
. I realize that I’m trying to trigger those pitfalls below and you may be rolling your eyes with a duh. But they are not always obvious and the proposed get()
behavior would resolve these.
# bool(False) == bool(None)
account['update'] = False
print(account['updated'] or True)
>> True
print(account.get('updated', True)
>> False
# bool(0) == bool(None)
account['last_update'] = 0
print(account['last_update'] or now())
>> 123456
print(account.get('last_update', now))
>> 0
# bool(None) == bool({}) empty dict, list, set
account['messages'] = []
print(account['messages'] or 'never messaged')
>> 'never messaged'
print(account.get('messages', 'never messaged'))
>> []
Creating the functionality on our side means that None
is a reserved value in tables. Here is what I’ve done in the past.
def default_get(row: Row, key: str, default: Any=None) -> Any:
value = row[key]
if value is None:
return default
else:
return value
Here is what it could look like:
def get(self, key: str, default: Any=None) -> Any:
try:
value = self[key]
if value == NULL_VALUE:
return default
else:
return value
except NoSuchColumn as e:
if _auto_create_columns:
return default
else:
raise e