Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQLAlchemy - update an object with a transient object

Tags:

I'm trying to use flask-rest-api as a framework for a simple example webservice with SQLAlchemy as the ORM. Everything works fine, except when dealing with updates.

Here's the code in question:

    @blp.arguments(DogSchema)
    @blp.response(DogSchema)
    def put(self, data, dog_id):
        """Update existing dog"""
        try:
            dog = Dog.query.get(dog_id)
        except Exception as e:
            abort(404, message="Item not found - %s" % e)
        
        # update the dog here
        return dog

When this executes, the data variable is a transient Dog model object. flask-rest-api takes care of unserializing the data in the JSON request, looking at your Marshmallow schema and creating a sqlalchemy Model object of the correct type.

Here's my model and schemas for completeness sake:

class Dog(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String, nullable=False)


@api.definition("Dog")
class DogSchema(ma.ModelSchema):
    class Meta:
        model = Dog
        strict = True
        ordered = True
    
    id = field_for(Dog, 'id', dump_only=True)

What I'd like to do is just take the transient object and update the object in the session with all the attributes.

I've been able to do this two different ways, neither of which I like and I'm hoping there is a better way of doing this.

1) via merge

    @blp.arguments(DogSchema)
    @blp.response(DogSchema)
    def put(self, data, dog_id):
        """Update existing dog"""
        try:
            dog = Dog.query.get(dog_id)
        except Exception as e:
            abort(404, message="Item not found - %s" % e)

        data.id = dog_id
        db.session.merge(data)
        db.session.commit()
        return dog

This works, but merge seems to have other side effects - it pulls from the db if the record isn't in the session, and if it doesn't exist in the db, adds it.

2) Via marshmallow serialization

    @blp.arguments(DogSchema)
    @blp.response(DogSchema)
    def put(self, data, dog_id):
        """Update existing dog"""
        try:
            dog = Dog.query.get(dog_id)
        except Exception as e:
            abort(404, message="Item not found - %s" % e)

        serializer = DogSchema()
        serializer.load(serializer.dump(data).data, instance=dog, session=db.session, partial=True)
        db.session.commit()
        return dog

This uses Marshmallow to serialize the data variable back to JSON, then load it back in, which seems rather inefficient.

I'd really love there to be something as simple as

    @blp.arguments(DogSchema)
    @blp.response(DogSchema)
    def put(self, data, dog_id):
        """Update existing dog"""
        try:
            dog = Dog.query.get(dog_id)
        except Exception as e:
            abort(404, message="Item not found - %s" % e)

        dog.update(data)
        db.session.commit()
        return dog

but that doesn't seem to be around, so hoping someone can help me out.

Thanks!

like image 530
sjmh Avatar asked Feb 04 '19 22:02

sjmh


People also ask

How do I update data in SQLAlchemy?

Update table elements in SQLAlchemy. Get the books to table from the Metadata object initialized while connecting to the database. Pass the update query to the execute() function and get all the results using fetchall() function. Use a for loop to iterate through the results.

What is _sa_instance_state in SQLAlchemy?

_sa_instance_state is a non-database-persisted value used by SQLAlchemy internally (it refers to the InstanceState for the instance.

How do I update ORM?

Python Flask and SQLAlchemy ORM To modify data of a certain attribute of any object, we have to assign new value to it and commit the changes to make the change persistent. SELECT customers.id AS customers_id, customers.name AS customers_name, customers. address AS customers_address, customers.


1 Answers

You can specify update method for you Dog class:

class Dog(db.Model):
    ...
    def update(self, data):
        for k, v in data.items():
            setattr(self, k, v)
        return self

And then use it as you wanted:

    try:
        dog = Dog.query.get(dog_id)
    except Exception as e:
        abort(404, message="Item not found - %s" % e)

    dog.update(data)
    db.session.commit()
like image 174
петр костюкевич Avatar answered Oct 14 '22 09:10

петр костюкевич