Storing anvil.server.context.client in data table

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 :man_shrugging:

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