2

I'm trying to build a REST API application in Python 3.4, using Flask, SQLAlchemy and Marshmallow.

In my model I have a User class with a one to many relationship with a MailAddress class.

If I run a GET request, I manage to read data from the DB, and the data is correctly returned as a JSON string.

Instead, if I run a POST request with the JSON serialization of my User object with some MailAddresses, I get this error:

    File "X:\test\...\site-packages\sqlalchemy\orm\collections.py", line 785, in bulk_replace 
      constants = existing_idset.intersection(values or ())
    File "X:\test\...\site-packages\sqlalchemy\util\_collections.py", line 612, in intersection
      result._members.update(self._working_set(members).intersection(other))
    TypeError: unhashable type: 'dict'

I've tried adding a __hash__ function to my model classes (as suggested at sqlalchemy: TypeError: unhashable type creating instance, sqlalchemy) but that didn't help.

Here is a complete code example that shows this problem:

from flask import Flask, request
from flask_marshmallow import Marshmallow
from flask_sqlalchemy import SQLAlchemy
from marshmallow import fields
from sqlalchemy import Table, Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship

class Config(object):
    SQLALCHEMY_DATABASE_URI = '<CONNECTION STRING HERE>'
    SQLALCHEMY_TRACK_MODIFICATIONS = False

app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
ma = Marshmallow(app)

# Model
class MailAddress(db.Model):
    __tablename__ = 'mail_addresses'
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('users.id'))
    mail_type = Column(String(200), nullable=False)
    mail = Column(String(200), nullable=False)
    def __init__(self, mail, mail_type):
        self.mail = mail
        self.mail_type = mail_type

class MailAddressSchema(ma.ModelSchema):
    class Meta:
        model = MailAddress

class User(db.Model):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(200), nullable=False)
    mail_addresses = relationship('MailAddress', backref='user')
    def __init__(self, name, mail_addresses):
        self.name = name
        self.mail_addresses = mail_addresses
    def __hash__(self):
        return hash(self.name)

class UserSchema(ma.ModelSchema):
    mail_addresses = fields.Nested(MailAddressSchema, many = True, only=('mail', 'mail_type'))
    class Meta:
        model = User

# Routes
user_schema = UserSchema()

@app.route('/api/v0/user', methods=['GET'])
def user_get():
    users = db.session.query(User).all()
    return user_schema.jsonify(users, many = True), 200

@app.route('/api/v0/user', methods=['POST'])
def user_create():
    new_instance = user_schema.make_instance(request.json)
    db.session.add(new_instance)
    db.session.commit()
    return user_schema.jsonify(new_instance), 201

# Main
if __name__ == '__main__':
    app.run('localhost', 5555)

Is there something I'm missing?

Community
  • 1
  • 1
Paolo Tedesco
  • 55,237
  • 33
  • 144
  • 193

1 Answers1

1

Use load instead make_instance

@app.route('/api/v0/user', methods=['POST'])
def user_create():
    new_instance, errors = user_schema.load(request.json)
    db.session.add(new_instance)
    db.session.commit()
    return user_schema.jsonify(new_instance), 201
Jair Perrut
  • 1,360
  • 13
  • 24
  • note in version 3 the load method returns the deserialized data rather than a (data, errors) duple. See the [documentation](https://marshmallow.readthedocs.io/en/3.0/api_reference.html#marshmallow.Schema.load) – Andrew Allen May 14 '19 at 11:58