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

"First example" doc questions and best practices #95

Open ssirowy opened 6 years ago

ssirowy commented 6 years ago

Not sure where to post a question regarding best practices of this library, so I thought I'd include an issue.

In the First example docs, the SQL alchemy models specify a Person, the computers and the relationships between them. So far, so good.

At the end the example, there are two APIS for getting lists of computers. One is the whole list, the other is a given person's computers:

api.route(ComputerList, 'computer_list', '/computers', '/persons/<int:id>/computers')

The /computers route seems to work as I'd expect, but the latter one does not. It appears you are overriding query in the example, which the docs don't really go into detail about, and it doesn't seem to make use the before_get call.

Without thinking about how the library works, it seems like it'd be great if I had a hook somewhere to return the value of:

self.session.query(Person).filter_by(id=view_kwargs['id']).one().computers

and just return the computers object off of my SQLAlchemy model since that was already set up.

Several questions then:

  1. Is what is in the example considered best practice for pulling down a resources (e.g persons) items (e.g computers)?
  2. What if the person had 2 different sets of computers (e.g old and new). Is the best practice then in the ComputerList object to branch based off the view_kwargs to decide how to filter the list?
  3. How do I make use of existing SQLAlchemy connections?
  4. What is the main use case for using before_get_object (and other variants)? In a typical setup, what is it intended to manipulate before the rest of the method runs?
akira-dev commented 5 years ago
  1. I don't understand the first question => you want to pull a resource or several resources ?
  2. Yes you're right I usually use view_kwargs to switch between cases
  3. https://flask-rest-jsonapi.readthedocs.io/en/latest/data_layer.html with the data_layer config you can specify the session or create a session from a connection
  4. You can only manipulate view_kwargs, this hook is setup up for homogeneity
zeusttu commented 5 years ago

It is possible to define two view classes, one without the query method and one with the query method. The class without the query method would then serve /computers, and the class with the query (which may inherit from the one without the query) would serve /persons/:id/computers. The query method then also does not have to check whether the id is passed, because it always is. This would look like:

class ComputerList(ResourceList):
    schema = ComputerSchema
    data_layer = {'session': db.session, 'model': Computer}

class PersonComputerList(ComputerList)
    def query(self, view_kwargs):
        try:
            self.session.query(Person).filter_by(id=view_kwargs['id']).one()
        except NoResultFound:
            raise ObjectNotFound({'parameter': 'id'}, "Person: {} not found".format(view_kwargs['id']))
        else:
            return self.session.query(Computer).join(Person).filter(Person.id == view_kwargs['id'])

    def before_create_object(self, data, view_kwargs):
        person = self.session.query(Person).filter_by(id=view_kwargs['id']).one()
        data['person_id'] = person.id

    view_kwargs = True
    data_layer = {'session': db.session,
                  'model': Computer,
                  'methods': {'query': query,
                              'before_create_object': before_create_object}}

...

api.route(ComputerList, 'computer_list', '/computers')
api.route(PersonComputerList, 'person_computer_list', '/persons/<int:id>/computers')

I think this provides a nicer separation of concerns, and keeps things more flexible e.g. in the case described in question 2.

Note the view_kwargs = True class attribute on PersonComputerList which is necessary to prevent Werkzeug router errors (although I wasted some time reading over it, it is in fact documented).