What I’m trying to do:
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
Digging in the source code only got me this far:
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
.
I wonder if this should just be turned into a FR?
Ok so this works but for how long before anvil is updated and it breaks is anyones guess
Ymmv, and I would definitely catch any serialization errors if using the following:
serializable_context = { k: (v if k != 'location' else v.__dict__)
for k,v in anvil.server.context.client.__dict__.items() }
Thanks for that! I generalized that a bit to:
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.
1 Like
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.
1 Like