Keon Kim
Keon Kim

Reputation: 760

TypeError: key <built-in function id> is not a string

I am using flask and its flask-restful extension to make a simple restful-api. I am having trouble stuffing data into Question table with User id (reference).

The model.py file is the following:

class User(UserMixin, SurrogatePK, Model):

    __tablename__ = 'users'
    username = Column(db.String(80), unique=True, nullable=False)
    email = Column(db.String(80), unique=True, nullable=False)
    #: The hashed password
    password = Column(db.String(128), nullable=True)
    created_at = Column(db.DateTime, nullable=False, default=dt.datetime.utcnow)

    def __init__(self, username, email, password=None, **kwargs):
        db.Model.__init__(self, username=username, email=email, **kwargs)
        if password:
            self.set_password(password)
        else:
            self.password = None

    def set_password(self, password):
        self.password = bcrypt.generate_password_hash(password)

    def check_password(self, value):
        return bcrypt.check_password_hash(self.password, value)

    def generate_auth_token(self, expiration = 600):
        s = Serializer('secret_key', expires_in = expiration)
        return s.dumps({ 'id': self.id })

    @staticmethod
    def verify_auth_token(token):
        s = Serializer('secret_key')
        try:
            data = s.loads(token)
        except SignatureExpired:
            return None # valid token, but expired
        except BadSignature:
            return None # invalid token
        user = User.query.get(data['id'])
        return user
    def __repr__(self):
        return '<User({username!r})>'.format(username=self.username)

class Question(SurrogatePK, Model):
    __tablename__ = 'questions'
    text = Column(db.String(400), nullable=True)
    created_at = Column(db.DateTime, nullable=True, default=dt.datetime.utcnow)

    user_id = ReferenceCol('users', nullable=True)
    user = db.relationship('User', backref='question_users')

    def __init__(self, text, created_at, user, **kwargs):
        db.Model.__init__(self, text=text, user=user, **kwargs)

    def __repr__(self):
        return '<Question({text})>'.format(text=self.text)

I used flask-httpauth's HTTPBasicAuth to authenticate user and used following decorator to store user info to global variable g:

@auth.verify_password
def verify_password(username_or_token, password):
    # first try to authenticate by token
    user = User.verify_auth_token(username_or_token)
    if not user:
        # try to authenticate with username/password
        user = User.query.filter_by(username = username_or_token).first()
        if not user or not user.check_password(password):
            return False
    g.user = user
    return True

lastly, my view file. The view file looks like below:

questionlist_fields = {
    'text':fields.String,
    'uri':fields.Url
}

class QuestionListAPI(Resource):
    decorators = [auth.login_required]
    def __init__(self):
        self.reqparse = reqparse.RequestParser()
        self.reqparse.add_argument('text', type=str, required=True,
            help='No Question Title Provided', location='json')
        super(QuestionListAPI, self).__init__()
    def post(self):
        args = self.reqparse.parse_args()

        #----------the code causing error-----------------#
        question = Question.create(text=args.text,
                                    created_at=datetime.utcnow(),
                                    user=g.user)
        #-------------------------------------------------#

        return {id:marshal(question, questionlist_fields)}
api.add_resource(QuestionListAPI, '/api/questions', endpoint='questions')

The error log is the following:

Traceback (most recent call last):
  File "C:\Users\NI\Envs\nektime\lib\site-packages\flask\app.py", line 1836, in __call__
    return self.wsgi_app(environ, start_response)
  File "C:\Users\NI\Envs\nektime\lib\site-packages\flask\app.py", line 1820, in wsgi_app
    response = self.make_response(self.handle_exception(e))
  File "C:\Users\NI\Envs\nektime\lib\site-packages\flask_restful\__init__.py", line 265, in error_router
    return original_handler(e)
  File "C:\Users\NI\Envs\nektime\lib\site-packages\flask\app.py", line 1403, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Users\NI\Envs\nektime\lib\site-packages\flask\_compat.py", line 32, in reraise
    raise value.with_traceback(tb)
  File "C:\Users\NI\Envs\nektime\lib\site-packages\flask\app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\Users\NI\Envs\nektime\lib\site-packages\flask\app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:\Users\NI\Envs\nektime\lib\site-packages\flask_restful\__init__.py", line 265, in error_router
    return original_handler(e)
  File "C:\Users\NI\Envs\nektime\lib\site-packages\flask\app.py", line 1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Users\NI\Envs\nektime\lib\site-packages\flask\_compat.py", line 32, in reraise
    raise value.with_traceback(tb)
  File "C:\Users\NI\Envs\nektime\lib\site-packages\flask\app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\Users\NI\Envs\nektime\lib\site-packages\flask_debugtoolbar\__init__.py", line 124, in dispatch_request
    return view_func(**req.view_args)
  File "C:\Users\NI\Envs\nektime\lib\site-packages\flask_restful\__init__.py", line 450, in wrapper
    return self.make_response(data, code, headers=headers)
  File "C:\Users\NI\Envs\nektime\lib\site-packages\flask_restful\__init__.py", line 474, in make_response
    resp = self.representations[mediatype](data, *args, **kwargs)
  File "C:\Users\NI\Envs\nektime\lib\site-packages\flask_restful\representations\json.py", line 24, in output_json
    dumped = dumps(data, **local_settings)
  File "C:\Python34\Lib\json\__init__.py", line 237, in dumps
    **kw).encode(obj)
  File "C:\Python34\Lib\json\encoder.py", line 194, in encode
    chunks = list(chunks)
  File "C:\Python34\Lib\json\encoder.py", line 422, in _iterencode
    yield from _iterencode_dict(o, _current_indent_level)
  File "C:\Python34\Lib\json\encoder.py", line 368, in _iterencode_dict
    raise TypeError("key " + repr(key) + " is not a string")
TypeError: key <built-in function id> is not a string

Sorry for long lines of code. I just don't know what is causing the problem.

Upvotes: 2

Views: 12972

Answers (1)

abarnert
abarnert

Reputation: 365767

Your problem is in this line:

return {id:marshal(question, questionlist_fields)}

I think you wanted this:

return {'id': marshal(question, questionlist_fields)}

In some languages, notably JavaScript, all dict keys are strings, and the syntax allows you to leave the quotes off.

In Python, dict keys can be anything you want.* If you want a string, you need the quotes. If you just pass id, you're just saying you want the key to be whatever's in the variable id. Since you don't have a local variable named id, what you get is the builtin.

That's still perfectly valid, even if it's not what you want, and not very useful.

But when you return a dict from a Flask post method on a JSON API, it gets encoded to JSON. And JSON only allows strings for dict keys, not functions. Hence the error.


* Well, not quite. They can be anything hashable, which doesn't include mutable types like list, and some other types that don't want to be used as keys for some other reason.

Upvotes: 8

Related Questions