miLibris / flask-rest-jsonapi

Flask extension to build REST APIs around JSONAPI 1.0 specification.
http://flask-rest-jsonapi.readthedocs.io
MIT License
597 stars 153 forks source link

Possible to get outdated info using GET after PATCH when using after_patch #133

Open graysonhead opened 5 years ago

graysonhead commented 5 years ago

I have a resource called Segments which contains a geoJSON path and some statistical attributes (Only distance is included in this example for simplicity.)

The issue I'm running into is that when I use after_patch to re-calculate statistical info (distance of the selected path in this case), if I make a GET immediately after the PATCH the first GET has the old distance value, not the distance value that gets set in my after_patch function. If I wait long enough, the GET has the correct value, but for longer paths updating the distance/average speed/etc can be long and un-predictable. I would much rather have the request time out than get outdated information back.

I feel that I'm doing this the wrong way, but I wasn't able to figure out any cleaner way to do it, help appreciated!

Model:

class Segment(db.Model):
    __tablename__ = 'segment'
    id = Column(Integer, primary_key=True)
    name = Column(String(120))
    start_index = Column(Integer)
    end_index = Column(Integer)
    trip_id = Column(Integer, ForeignKey('trip.id'))
    trip = relationship('Trip', back_populates='segments')
    distance = Column(Integer)
    path = Column(JSON)

    def __init__(self, name, path_dict):
        self.name = name
        self.path = path_dict
        self.reset_path_index()

    def set_index(self, start, end):
        self.start_index = start
        self.end_index = end
        self.calc_distance()

    def reset_path_index(self):
        self.start_index = 0
        self.end_index = self.path['coordinates'].__len__()
        self.calc_distance()

    def calc_distance(self):
        self.distance = calc_path_distance(self.path['coordinates'][self.start_index:self.end_index])

    def __repr__(self):
        return '<Segment: {}>'.format(self.name)``

Schema:

class SegmentSchema(Schema):
    class Meta:
        type_ = 'segment'
        self_view = 'api.segment_detail'
        self_view_kwargs = {'id': '<id>'}
        self_view_many = 'api.segment_list'

    id = fields.Str(as_string=True, dump_only=True)
    name = fields.Str()
    start_index = fields.Integer()
    end_index = fields.Integer()
    path = fields.Dict()
    trip_id = fields.Int()
    distance = fields.Int()
    trip = Relationship(
        attribute='trip',
        self_view='api.segment_trip',
        self_view_kwargs={'id': '<id>'},
        related_view='api.trip_detail',
        related_view_kwargs={'segment_id': '<id>'},
        schema='TripSchema',
        type='trip'
    )

ResourceDetail:

class SegmentDetail(ResourceDetail):
    schema = SegmentSchema
    decorators = (login_required_api, )
    data_layer = {
        'session': db.session,
        'model': models.Segment
    }

    def after_patch(*args, **kwargs):
        session = args[0]._data_layer.session
        segment = session.query(models.Segment).filter_by(id=args[1]['data']['id']).one()
        segment.calc_distance()
        session.add(segment)
        session.commit()
api.route(SegmentDetail, 'segment_detail', '/segments/<int:id>')
etiennecaldo commented 5 years ago

In my opinion you should:

Moreover, the way you recover the session seems strange to me. Why don't you use the db.session comming from your db object (the one you use to declare your Segment class)?

graysonhead commented 5 years ago

Thanks @etiennecaldo, It now looks like the follwing:

class SegmentDetail(ResourceDetail):

    def after_update_object(self, object):
        object.calc_distance()
        self.session.add(object)
        self.session.commit()

    schema = SegmentSchema
    decorators = (login_required_api, )
    data_layer = {
        'session': db.session,
        'model': models.Segment,
        'methods': {'after_update_object': after_update_object}
    }

Which is much cleaner indeed. However, I seems like I can still get outdated information after I get a 200 from the PATCH request.