Log some user activity and store the contents of anvil.server.context.client. I’m primarily interested in the IP address, but there are situations where that field won’t exist, so I figured I’d save all the client data and analyze it later.
What I’ve tried and what’s not working:
Converting to dict did not work, e.g. dict(anvil.server.context.client). The object is not iterable.
Using the client directly gives me a not-serializable error.
Using vars did not work, e.g. vars(anvil.server.context.client). That bumps the not-serializable error to anvil.server.context.client.location.
Using json did not work, e.g. json.dumps(anvil.server.context.client). The object is not JSON serializable.
Is there some technique I’m missing for storing anvil.server.context.client in a data table column?
Here’s an app to play with if you have ideas: Anvil | Login
It looks like there isn’t any serializable methods coded for the object and it just inherits from the builtins object class. Also the <ClientInfo: blah blah > that comes out when you print it is just what has been hard coded for its repr.
serializable_context = { k: (v if not hasattr(v, '__dict__') else v.__dict__)
for k,v in anvil.server.context.client.__dict__.items() }
Unless someone comes up with a better approach, I’ll likely expand this to handle the exceptions on the contained objects, so everything that can be serialized is.
Edit: probably way overkill, but here’s what I came up with to handle potential nested objects in future versions of the client info:
def dump_to_dict(obj):
result = {}
for k,v in obj.__dict__.items():
if not hasattr(v, '__dict__'):
result[k] = v
continue
result[k] = dump_to_dict(v)
return result
Calling that with dump_to_dict(anvil.server.context.client) works at the moment. I’m not sure what error situations might still come up with it, though.
I’ve just been picking away at this throughout the day as I’m working and here is what I have so far:
def serialize_nested(obj):
# Used to recursively extend easily serializable types that
# Would normally raise a SerializationError exception
if hasattr(obj, '__iter__' ) or hasattr(obj, '__next__'):
if isinstance(obj, (str, anvil._server.LiveObjectProxy) ):
return obj
if isinstance(obj, dict):
for k in obj:
obj[k] = serialize_nested(obj[k])
elif hasattr(obj, '__dict__'):
obj = dict(obj.__dict__)
for k in obj:
obj[k] = serialize_nested(obj[k])
else:
obj = list(obj)
for i, o in enumerate(obj):
obj[i] = serialize_nested(o)
elif hasattr(obj, '__dict__'):
obj = dict(obj.__dict__)
for k in obj:
obj[k] = serialize_nested(obj[k])
if isinstance(obj, # serializable types that are not portable classes
( str, int, float,
list, bool, dict, datetime.date,
datetime.datetime, anvil.Media,
anvil._server.LiveObjectProxy, )
) or obj is None :
return obj
if hasattr(obj, '__str__') or hasattr(obj, '__repr__'):
return str(obj)
raise anvil.server.SerializationError(f'Cannot serialize {type(obj)}')
It should be extendable, but if you need more power than this you are probably already using portable classes which should already be serializable.