Fetching resources and relationships

For the purposes of concreteness in this section, suppose we have executed the following code on the server:

from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.restless import APIManager

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)

class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True)

class Article(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    author_id = db.Column(db.Integer, db.ForeignKey('person.id'))
    author = db.relationship(Person, backref=db.backref('articles'))

db.create_all()
manager = APIManager(app, flask_sqlalchemy_db=db)
manager.create_api(Person)
manager.create_api(Article)

By default, all columns and relationships will appear in the resource object representation of an instance of your model. See Specifying which fields appear in responses for more information on specifying which values appear in responses.

To fetch a collection of resources, the request

GET /api/person HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

yields the response

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "id": "1",
      "links": {
        "self": "http://example.com/api/person/1"
      },
      "relationships": {
        "articles": {
          "data": [],
          "links": {
            "related": "http://example.com/api/person/1/articles",
            "self": "http://example.com/api/person/1/relationships/articles"
          }
        }
      },
      "type": "person"
    }
  ],
  "links": {
    "first": "http://example.com/api/person?page[number]=1&page[size]=10",
    "last": "http://example.com/api/person?page[number]=1&page[size]=10",
    "next": null,
    "prev": null,
    "self": "http://example.com/api/person"
  },
  "meta": {
    "total": 1
  }
}

To fetch a single resource, the request

GET /api/person/1 HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

yields the response

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": {
    "id": "1",
    "links": {
      "self": "http://example.com/api/person/1"
    },
    "relationships": {
      "articles": {
        "data": [],
        "links": {
          "related": "http://example.com/api/person/1/articles",
          "self": "http://example.com/api/person/1/relationships/articles"
        }
      }
    },
    "type": "person"
  }
}

To fetch a resource from a to-one relationship, the request

GET /api/article/1/author HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

yields the response

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": {
    "id": "1",
    "links": {
      "self": "http://example.com/api/person/1"
    },
    "relationships": {
      "articles": {
        "data": [
          {
            "id": "1",
            "type": "article"
          }
        ],
        "links": {
          "related": "http://example.com/api/person/1/articles",
          "self": "http://example.com/api/person/1/relationships/articles"
        }
      }
    },
    "type": "person"
  }
}

To fetch a resource from a to-many relationship, the request

GET /api/person/1/articles HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

yields the response

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "id": "2",
      "links": {
        "self": "http://example.com/api/articles/2"
      },
      "relationships": {
        "author": {
          "data": {
            "id": "1",
            "type": "person",
          },
          "links": {
            "related": "http://example.com/api/articles/2/author",
            "self": "http://example.com/api/articles/2/relationships/author"
          }
        }
      },
      "type": "article"
    }
  ],
  "links": {
    "first": "http://example.com/api/person/1/articles?page[number]=1&page[size]=10",
    "last": "http://example.com/api/person/1/articles?page[number]=1&page[size]=10",
    "next": null,
    "prev": null,
    "self": "http://example.com/api/person/1/articles"
  },
  "meta": {
    "total": 1
  }
}

To fetch a single resource from a to-many relationship, the request

GET /api/person/1/articles/2 HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

yields the response

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": {
    "id": "2",
    "links": {
      "self": "http://example.com/api/articles/2"
    },
    "relationships": {
      "author": {
        "data": {
          "id": "1",
          "type": "person"
        },
        "links": {
          "related": "http://example.com/api/articles/2/author",
          "self": "http://example.com/api/articles/2/relationships/author"
        }
      }
    },
    "type": "article"
  }
}

To fetch the link object for a to-one relationship, the request

GET /api/article/1/relationships/author HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

yields the response

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": {
    "id": "1",
    "type": "person"
  }
}

To fetch the link objects for a to-many relationship, the request

GET /api/person/1/relationships/articles HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

yields the response

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "id": "1",
      "type": "article"
    },
    {
      "id": "2",
      "type": "article"
    }
  ]
}

Function evaluation

This section describes behavior that is not part of the JSON API specification.

If the allow_functions keyword argument to APIManager.create_api() is set to True when creating an API for a model, then the endpoint /api/eval/person will be made available for GET requests. This endpoint responds to requests for evaluation of SQL functions on all instances the model.

If the client specifies the functions query parameter, it must be a percent-encoded list of function objects, as described below.

A function object is a JSON object. A function object must be of the form

{"name": <function_name>, "field": <field_name>}

where <function_name> is the name of a SQL function as provided by SQLAlchemy’s func object.

For example, to get the average age of all people in the database,

GET /api/eval/person?functions=[{"name":"avg","field":"age"}] HTTP/1.1
Host: example.com
Accept: application/json

The response will be a JSON object with a single element, data, containing a list of the results of all the function evaluations requested by the client, in the same order as in the functions query parameter. For example, to get the sum and the average ages of all people in the database, the request

GET /api/eval/person?functions=[{"name":"avg","field":"age"},{"name":"sum","field":"age"}] HTTP/1.1
Host: example.com
Accept: application/json

yields the response

HTTP/1.1 200 OK
Content-Type: application/json

[15.0, 60.0]

Example

To get the total number of resources in the collection (that is, the number of instances of the model), you can use the function object

{"name": "count", "field": "id"}

Then the request

GET /api/eval/person?functions=[{"name":"count","field":"id"}] HTTP/1.1
Host: example.com
Accept: application/json

yields the response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": [42]
}

Specifying which fields appear in responses

For more information on client-side sparse fieldsets, see Sparse Fieldsets in the JSON API specification.

Warning

The server-side configuration for specifying which fields appear in resource objects as described in this section is simplistic; a better way to specify which fields are included in your responses is to use a Python object serialization library and specify custom serialization and deserialization functions as described in Custom serialization.

By default, all fields of your model will be exposed by the API. A client can request that only certain fields appear in the resource object in a response to a GET request by using the only query parameter. On the server side, you can specify which fields appear in the resource object representation of an instance of the model by setting the only, exclude and additional_attributes keyword arguments to the APIManager.create_api() method.

If only is an iterable of column names or actual column attributes, only those fields will appear in the resource object that appears in responses to fetch instances of this model. If instead exclude is specified, all fields except those specified in that iterable will appear in responses. If additional_attributes is an iterable of column names, the values of these attributes will also appear in the response; this is useful if you wish to see the value of some attribute that is not a column or relationship.

Attention

The type and id elements will always appear in the resource object, regardless of whether the server or the client tries to exclude them.

For example, if your models are defined like this (using Flask-SQLAlchemy):

class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode)
    birthday = db.Column(db.Date)
    articles = db.relationship('Article')

    # This class attribute is not a column.
    foo = 'bar'

class Article(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    author_id = db.Column(db.Integer, db.ForeignKey('person.id'))

and you want your resource objects to include only the values of the name and birthday columns, create your API with the following arguments:

apimanager.create_api(Person, only=['name', 'birthday'])

Now a request like

GET /api/person/1 HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

yields the response

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": {
    "id": "1",
    "links": {
      "self": "http://example.com/api/person/1"
    },
    "attributes": {
      "birthday": "1969-07-20",
      "name": "foo"
    },
    "type": "person"
  }
}

If you want your resource objects to exclude the birthday and name columns:

apimanager.create_api(Person, exclude=['name', 'birthday'])

Now the same request yields the response

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": {
    "id": "1",
    "links": {
      "self": "http://example.com/api/person/1"
    }
    "relationships": {
      "articles": {
        "data": [],
        "links": {
          "related": "http://example.com/api/person/1/articles",
          "self": "http://example.com/api/person/1/links/articles"
        }
      },
    },
    "type": "person"
  }
}

If you want your resource objects to include the value for the class attribute foo:

apimanager.create_api(Person, additional_attributes=['foo'])

Now the same request yields the response

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": {
    "attributes": {
      "birthday": "1969-07-20",
      "foo": "bar",
      "name": "foo"
    },
    "id": "1",
    "links": {
      "self": "http://example.com/api/person/1"
    }
    "relationships": {
      "articles": {
        "data": [],
        "links": {
          "related": "http://example.com/api/person/1/articles",
          "self": "http://example.com/api/person/1/links/articles"
        }
      }
    },
    "type": "person"
  }
}

Sorting

Clients can sort according to the sorting protocol described in the Sorting section of the JSON API specification. Sorting by a nullable attribute will cause resources with null attributes to appear first.

Clients can also request grouping by using the group query parameter. For example, if your database has two people with name 'foo' and two people with name 'bar', a request like

GET /api/person?group=name HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

yields the response

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "attributes": {
        "name": "foo",
      },
      "id": "1",
      "links": {
        "self": "http://example.com/api/person/1"
      },
      "relationships": {
        "articles": {
          "data": [],
          "links": {
            "related": "http://example.com/api/person/1/articles",
            "self": "http://example.com/api/person/1/relationships/articles"
          }
        }
      },
      "type": "person"
    },
    {
      "attributes": {
        "name": "bar",
      },
      "id": "3",
      "links": {
        "self": "http://example.com/api/person/3"
      },
      "relationships": {
        "articles": {
          "data": [],
          "links": {
            "related": "http://example.com/api/person/3/articles",
            "self": "http://example.com/api/person/3/relationships/articles"
          }
        }
      },
      "type": "person"
    },
  ],
  "links": {
    "first": "http://example.com/api/person?group=name&page[number]=1&page[size]=10",
    "last": "http://example.com/api/person?group=name&page[number]=1&page[size]=10",
    "next": null,
    "prev": null,
    "self": "http://example.com/api/person?group=name"
  },
  "meta": {
    "total": 2
  }
}

Filtering

Requests that would normally return a collection of resources can be filtered so that only a subset of the resources are returned in a response. If the client specifies the filter[objects] query parameter, it must be a URL encoded JSON list of filter objects, as described below.

Quick client examples for filtering

The following are some quick examples of making filtered GET requests from different types of clients. More complete documentation is in subsequent sections. In these examples, each client will filter by instances of the model Person whose names contain the letter “y”.

Using the Python requests library:

import requests
import json

url = 'http://127.0.0.1:5000/api/person'
headers = {'Accept': 'application/vnd.api+json'}

filters = [dict(name='name', op='like', val='%y%')]
params = {'filter[objects]': json.dumps(filters)}

response = requests.get(url, params=params, headers=headers)
assert response.status_code == 200
print(response.json())

Using jQuery:

var filters = [{"name": "id", "op": "like", "val": "%y%"}];
$.ajax({
  data: {"filter[objects]": JSON.stringify(filters)},
  headers: {
    "Accept": JSONAPI_MIMETYPE
  },
  success: function(data) { console.log(data.objects); },
  url: 'http://127.0.0.1:5000/api/person'
});

Using curl:

curl \
  -G \
  -H "Accept: application/vnd.api+json" \
  -d "filter[objects]=[{\"name\":\"name\",\"op\":\"like\",\"val\":\"%y%\"}]" \
  http://127.0.0.1:5000/api/person

The examples/ directory has more complete versions of these examples.

Filter objects

A filter object is a JSON object. Filter objects are defined recursively as follows. A filter object may be of the form

{"name": <field_name>, "op": <unary_operator>}

where <field_name> is the name of a field on the model whose instances are being fetched and <unary_operator> is the name of one of the unary operators supported by Flask-Restless. For example,

{"name": "birthday", "op": "is_null"}

A filter object may be of the form

{"name": <field_name>, "op": <binary_operator>, "val": <argument>}

where <binary_operator> is the name of one of the binary operators supported by Flask-Restless and <argument> is the second argument to that binary operator. For example,

{"name": "age", "op": "gt", "val": 23}

A filter object may be of the form

{"name": <field_name>, "op": <binary_operator>, "field": <field_name>}

The field element indicates that the second argument to the binary operator should be the value of that field. For example, to filter by resources that have a greater width than height,

{"name": "width", "op": "gt", "field": "height"}

A filter object may be of the form

{"name": <relation_name>, "op": <relation_operator>, "val": <filter_object>}

where <relation_name> is the name of a relationship on the model whose resources are being fetched, <relation_operator> is either "has", for a to-one relationship, or "any", for a to-many relationship, and <filter_object> is another filter object. For example, to filter person resources by only those people that have authored an article dated before January 1, 2010,

{
  "name": "articles",
  "op": "any",
  "val": {
    "name": "date",
    "op": "lt",
    "val": "2010-01-01"
  }
}

For another example, to filter article resources by only those articles that have an author of age at most fifty,

{
  "name": "author",
  "op": "has",
  "val": {
    "name": "age",
    "op": "lte",
    "val": 50
  }
}

A filter object may be a conjunction (“and”) or disjunction (“or”) of other filter objects:

{"or": [<filter_object>, <filter_object>, ...]}

or

{"and": [<filter_object>, <filter_object>, ...]}

For example, to filter by resources that have width greater than height, and length of at least ten,

{
  "and": [
    {"name": "width", "op": "gt", "field": "height"},
    {"name": "length", "op": "lte", "val": 10}
  ]
}

How are filter objects used in practice? To get a response in which only those resources that meet the requirements of the filter objects are returned, clients can make requests like this:

GET /api/person?filter[objects]=[{"name":"age","op":"<","val":18}] HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

Operators

Flask-Restless understands the following operators, which correspond to the appropriate SQLAlchemy column operators.

  • ==, eq, equals, equals_to
  • !=, neq, does_not_equal, not_equal_to
  • >, gt, <, lt
  • >=, ge, gte, geq, <=, le, lte, leq
  • in, not_in
  • is_null, is_not_null
  • like, ilike, not_like
  • has
  • any

Flask-Restless also understands the PostgreSQL network address operators <<, <<=, >>, >>=, <>, and &&.

Warning

If you use a percent sign in the argument to the like operator (for example, %somestring%), make sure it is percent-encoded, otherwise the server may interpret the first few characters of that argument as a percent-encoded character when attempting to decode the URL.

Requiring singleton collections

If a client wishes a request for a collection to yield a response with a singleton collection, the client can use the filter[single] query parameter. The value of this parameter must be either 1 or 0. If the value of this parameter is 1 and the response would yield a collection of either zero or more than two resources, the server instead responds with 404 Not Found.

For example, a request like

GET /api/person?filter[single]=1&filter[objects]=[{"name":"id","op":"eq","val":1}] HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

yields the response

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": {
    "id": "1",
    "type": "person",
    "links": {
      "self": "http://example.com/api/person/1"
    }
  },
  "links": {
    "self": "http://example.com/api/person?filter[single]=1&filter[objects]=[{\"name\":\"id\",\"op\":\"eq\",\"val\":1}]"
  },
}

But a request like

GET /api/person?filter[single]=1 HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

would yield an error response if there were more than one Person instance in the database.

Filter object examples

Attribute greater than a value

On request

GET /api/person?filter[objects]=[{"name":"age","op":"gt","val":18}] HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

the response will include only those Person instances that have age attribute greater than or equal to 18:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "attributes": {
        "age": 19
      },
      "id": "2",
      "links": {
        "self": "http://example.com/api/person/2"
      },
      "type": "person"
    },
    {
      "attributes": {
        "age": 29
      },
      "id": "5",
      "links": {
        "self": "http://example.com/api/person/5"
      },
      "type": "person"
    },
  ],
  "links": {
    "self": "/api/person?filter[objects]=[{\"name\":\"age\",\"op\":\"gt\",\"val\":18}]"
  },
  "meta": {
    "total": 2
  }
}

Arbitrary Boolean expression of filters

On request

GET /api/person?filter[objects]=[{"or":[{"name":"age","op":"lt","val":10},{"name":"age","op":"gt","val":20}]}] HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

the response will include only those Person instances that have age attribute either less than 10 or greater than 20:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "attributes": {
        "age": 9
      },
      "id": "1",
      "links": {
        "self": "http://example.com/api/person/1"
      },
      "type": "person"
    },
    {
      "attributes": {
        "age": 25
      },
      "id": "3",
      "links": {
        "self": "http://example.com/api/person/3"
      },
      "type": "person"
    }
  ],
  "links": {
    "self": "/api/person?filter[objects]=[{\"or\":[{\"name\":\"age\",\"op\":\"lt\",\"val\":10},{\"name\":\"age\",\"op\":\"gt\",\"val\":20}]}]"
  },
  "meta": {
    "total": 2
  }
}

Comparing two attributes

On request

GET /api/box?filter[objects]=[{"name":"width","op":"ge","field":"height"}] HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

the response will include only those Box instances that have width attribute greater than or equal to the value of the height attribute:

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "attributes": {
        "height": 10,
        "width": 20
      }
      "id": "1",
      "links": {
        "self": "http://example.com/api/box/1"
      },
      "type": "box"
    },
    {
      "attributes": {
        "height": 15,
        "width": 20
      }
      "id": "2",
      "links": {
        "self": "http://example.com/api/box/2"
      },
      "type": "box"
    }
  ],
  "links": {
    "self": "/api/box?filter[objects]=[{\"name\":\"width\",\"op\":\"ge\",\"field\":\"height\"}]"
  },
  "meta": {
    "total": 100
  }
}

Using has and any

On request

GET /api/person?filter[objects]=[{"name":"articles","op":"any","val":{"name":"date","op":"lt","val":"2010-01-01"}}] HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

the response will include only those people that have authored an article dated before January 1, 2010 (assume in the example below that at least one of the article linkage objects refers to an article that has such a date):

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "id": "1",
      "links": {
        "self": "http://example.com/api/person/1"
      },
      "relationships": {
        "articles": {
          "data": [
            {
              "id": "1",
              "type": "article"
            },
            {
              "id": "2",
              "type": "article"
            }
          ],
          "links": {
            "related": "http://example.com/api/person/1/articles",
            "self": "http://example.com/api/person/1/relationships/articles"
          }
        }
      },
      "type": "person"
    }
  ],
  "links": {
    "self": "/api/person?filter[objects]=[{\"name\":\"articles\",\"op\":\"any\",\"val\":{\"name\":\"date\",\"op\":\"lt\",\"val\":\"2010-01-01\"}}]"
  },
  "meta": {
    "total": 1
  }
}

On request

GET /api/article?filter[objects]=[{"name":"author","op":"has","val":{"name":"age","op":"lte","val":50}}] HTTP/1.1
Host: example.com
Accept: application/vnd.api+json

the response will include only those articles that have an author of age at most fifty (assume in the example below that the author linkage objects refers to a person that has such an age):

HTTP/1.1 200 OK
Content-Type: application/vnd.api+json

{
  "data": [
    {
      "id": "1",
      "links": {
        "self": "http://example.com/api/article/1"
      },
      "relationships": {
        "author": {
          "data": {
            "id": "7",
            "type": "person"
          },
          "links": {
            "related": "http://example.com/api/article/1/author",
            "self": "http://example.com/api/article/1/relationships/author"
          }
        }
      },
      "type": "article"
    }
  ],
  "links": {
    "self": "/api/article?filter[objects]=[{\"name\":\"author\",\"op\":\"has\",\"val\":{\"name\":\"age\",\"op\":\"lte\",\"val\":50}}]"
  },
  "meta": {
    "total": 1
  }
}