Milad Milad - 1 month ago 11
Python Question

Default value for ndb.KeyProperty

I have these two models:

#################
### Usergroup ###
#################

class Usergroup(ndb.Model):
group_name = ndb.StringProperty(indexed = False, required = True)
is_admin_group = ndb.BooleanProperty(indexed = False, required = False, default = False)

############
### User ###
############

class User(ndb.Model):
fb_id = ndb.StringProperty(indexed = True, required = True)
fb_access_token = ndb.TextProperty(indexed = False, required = True)
email = ndb.StringProperty(indexed = True, required = True)
first_name = ndb.StringProperty(indexed = False, required = True)
last_name = ndb.StringProperty(indexed = False, required = True)
gender = ndb.StringProperty(indexed = False)
group_key = ndb.KeyProperty(indexed = False, required = False, kind = Usergroup, default = ndb.Key(Usergroup, 'member'))
join_date = ndb.DateTimeProperty(indexed = True, auto_now_add = True)
last_login = ndb.DateTimeProperty(indexed = True, auto_now_add = True, auto_now = False)

@app.route('/user/login', methods=['POST'])
def user_login():
me = exchange_token_me(request.form['accessToken'])
if me is not False:
user = user_find_or_register(me)
if user is not None:
register_session(user)
return 'success'
return 'error'

def user_find_or_register(user):
qry = User.query(User.fb_id == user['id'])
existing_user = qry.get()

if existing_user is not None:
existing_user.fb_access_token = user['access_token']
existing_user.fb_id = user['id']
existing_user.email = user['email']
existing_user.last_login = datetime.datetime.now()
existing_user.put()
return existing_user

new_user = User()
new_user.fb_id = user['id']
new_user.fb_access_token = user['access_token']
new_user.email = user['email']
new_user.first_name = user['first_name']
new_user.last_name = user['last_name']
new_user.gender = user['gender']
new_user.last_login = datetime.datetime.now()
#new_user.group_key = ndb.key(Usergroup, 'member')

key = new_user.put()
saved_user = key.get()
#key.delete() # DEBUG

if saved_user is not None:
return saved_user

def register_session(user):
session['fb_id'] = user.fb_id
session['first_name'] = user.first_name
session['last_name'] = user.last_name
session['group_key'] = user.group_key
session['loggedin'] = True


The Usergroup model has a small unique string as entity key. There is already a Usergroup whose key is 'member'

Whenever we create/save a user, it should use the key to the 'member' usergroup, but we get this error instead:

TypeError: Key('Usergroup', 'member') is not JSON serializable

Traceback:

ERROR 2016-10-18 14:32:40,572 app.py:1587] Exception on /user/login [POST]
Traceback (most recent call last):
File "/var/www/mywebsite/public_html/lib/flask/app.py", line 1988, in wsgi_app
response = self.full_dispatch_request()
File "/var/www/mywebsite/public_html/lib/flask/app.py", line 1643, in full_dispatch_request
response = self.process_response(response)
File "/var/www/mywebsite/public_html/lib/flask/app.py", line 1864, in process_response
self.save_session(ctx.session, response)
File "/var/www/mywebsite/public_html/lib/flask/app.py", line 926, in save_session
return self.session_interface.save_session(self, session, response)
File "/var/www/mywebsite/public_html/lib/flask/sessions.py", line 359, in save_session
val = self.get_signing_serializer(app).dumps(dict(session))
File "/var/www/mywebsite/public_html/lib/itsdangerous.py", line 565, in dumps
payload = want_bytes(self.dump_payload(obj))
File "/var/www/mywebsite/public_html/lib/itsdangerous.py", line 847, in dump_payload
json = super(URLSafeSerializerMixin, self).dump_payload(obj)
File "/var/www/mywebsite/public_html/lib/itsdangerous.py", line 550, in dump_payload
return want_bytes(self.serializer.dumps(obj))
File "/var/www/mywebsite/public_html/lib/flask/sessions.py", line 85, in dumps
return json.dumps(_tag(value), separators=(',', ':'))
File "/var/www/mywebsite/public_html/lib/flask/json.py", line 126, in dumps
rv = _json.dumps(obj, **kwargs)
File "/usr/lib/python2.7/json/__init__.py", line 251, in dumps
sort_keys=sort_keys, **kw).encode(obj)
File "/usr/lib/python2.7/json/encoder.py", line 209, in encode
chunks = list(chunks)
File "/usr/lib/python2.7/json/encoder.py", line 434, in _iterencode
for chunk in _iterencode_dict(o, _current_indent_level):
File "/usr/lib/python2.7/json/encoder.py", line 408, in _iterencode_dict
for chunk in chunks:
File "/usr/lib/python2.7/json/encoder.py", line 442, in _iterencode
o = _default(o)
File "/var/www/mywebsite/public_html/lib/flask/json.py", line 83, in default
return _json.JSONEncoder.default(self, o)
File "/usr/lib/python2.7/json/encoder.py", line 184, in default
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: Key('Usergroup', 'member') is not JSON serializable
ERROR 2016-10-18 14:32:40,593 main.py:178] An error occurred during a request.
Traceback (most recent call last):
File "/var/www/mywebsite/public_html/lib/flask/app.py", line 1988, in wsgi_app
response = self.full_dispatch_request()
File "/var/www/mywebsite/public_html/lib/flask/app.py", line 1643, in full_dispatch_request
response = self.process_response(response)
File "/var/www/mywebsite/public_html/lib/flask/app.py", line 1864, in process_response
self.save_session(ctx.session, response)
File "/var/www/mywebsite/public_html/lib/flask/app.py", line 926, in save_session
return self.session_interface.save_session(self, session, response)
File "/var/www/mywebsite/public_html/lib/flask/sessions.py", line 359, in save_session
val = self.get_signing_serializer(app).dumps(dict(session))
File "/var/www/mywebsite/public_html/lib/itsdangerous.py", line 565, in dumps
payload = want_bytes(self.dump_payload(obj))
File "/var/www/mywebsite/public_html/lib/itsdangerous.py", line 847, in dump_payload
json = super(URLSafeSerializerMixin, self).dump_payload(obj)
File "/var/www/mywebsite/public_html/lib/itsdangerous.py", line 550, in dump_payload
return want_bytes(self.serializer.dumps(obj))
File "/var/www/mywebsite/public_html/lib/flask/sessions.py", line 85, in dumps
return json.dumps(_tag(value), separators=(',', ':'))
File "/var/www/mywebsite/public_html/lib/flask/json.py", line 126, in dumps
rv = _json.dumps(obj, **kwargs)
File "/usr/lib/python2.7/json/__init__.py", line 251, in dumps
sort_keys=sort_keys, **kw).encode(obj)
File "/usr/lib/python2.7/json/encoder.py", line 209, in encode
chunks = list(chunks)
File "/usr/lib/python2.7/json/encoder.py", line 434, in _iterencode
for chunk in _iterencode_dict(o, _current_indent_level):
File "/usr/lib/python2.7/json/encoder.py", line 408, in _iterencode_dict
for chunk in chunks:
File "/usr/lib/python2.7/json/encoder.py", line 442, in _iterencode
o = _default(o)
File "/var/www/mywebsite/public_html/lib/flask/json.py", line 83, in default
return _json.JSONEncoder.default(self, o)
File "/usr/lib/python2.7/json/encoder.py", line 184, in default
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: Key('Usergroup', 'member') is not JSON serializable
INFO 2016-10-18 14:32:40,610 module.py:788] default: "POST /user/login HTTP/1.1" 500 27

Answer

FWIW, a quick test with your code as models.py shows this to be working just fine, at least on the development server:

    from models import User

    user = User(email='email', username='username')
    user.put()

produced:

enter image description here

This worked without even having a Usergroup entity - a key can exist without a matching entity. Of course, trying to follow the link to the Usergroup in the datastore viewer fails:

Traceback (most recent call last):
  File "/home/usr_local/google_appengine_1.9.40/lib/webapp2-2.5.1/webapp2.py", line 1536, in __call__
    rv = self.handle_exception(request, response, e)
  File "/home/usr_local/google_appengine_1.9.40/lib/webapp2-2.5.1/webapp2.py", line 1530, in __call__
    rv = self.router.dispatch(request, response)
  File "/home/usr_local/google_appengine_1.9.40/lib/webapp2-2.5.1/webapp2.py", line 1278, in default_dispatcher
    return route.handler_adapter(request, response)
  File "/home/usr_local/google_appengine_1.9.40/lib/webapp2-2.5.1/webapp2.py", line 1102, in __call__
    return handler.dispatch()
  File "/home/usr_local/google_appengine/google/appengine/tools/devappserver2/admin/admin_request_handler.py", line 96, in dispatch
    super(AdminRequestHandler, self).dispatch()
  File "/home/usr_local/google_appengine_1.9.40/lib/webapp2-2.5.1/webapp2.py", line 572, in dispatch
    return self.handle_exception(e, self.app.debug)
  File "/home/usr_local/google_appengine_1.9.40/lib/webapp2-2.5.1/webapp2.py", line 570, in dispatch
    return method(*args, **kwargs)
  File "/home/usr_local/google_appengine/google/appengine/tools/devappserver2/admin/datastore_viewer.py", line 741, in get
    entities = [datastore.Get(entity_key)]
  File "/home/usr_local/google_appengine/google/appengine/api/datastore.py", line 671, in Get
    return GetAsync(keys, **kwargs).get_result()
  File "/home/usr_local/google_appengine/google/appengine/api/apiproxy_stub_map.py", line 613, in get_result
    return self.__get_result_hook(self)
  File "/home/usr_local/google_appengine/google/appengine/datastore/datastore_rpc.py", line 1717, in __get_hook
    entities = extra_hook(entities)
  File "/home/usr_local/google_appengine/google/appengine/api/datastore.py", line 640, in local_extra_hook
    raise datastore_errors.EntityNotFoundError()
EntityNotFoundError

So you might want to show your actual code creating the entity and the full traceback, something else may be going on.

Your traceback indicates that the problem is not with creating the entity, but with saving your session:

File "/var/www/mywebsite/public_html/lib/flask/app.py", line 926, in save_session
    return self.session_interface.save_session(self, session, response)

It appears you included in the session content the key object (possibly by including the entire User entity?), which is what causes the failure. For that purpose keys need to be serialized and you can use key.urlsafe() for that. See this answer for an example: http://stackoverflow.com/a/34835074/4495081

If indeed you included the entire User entity in the session you can just include its urlsafe key instead.

Comments