Introduction¶
pg-jsonapi is an asynchronous Python library for building JSON API v1.0 compliant calls using a very simple declarative syntax.
Only PostgreSQL
is supported. PostgreSQL
integration is powered by the
asyncpgsa library.
SQLAlchemy is required for describing database objects.
Under the hood, the marshmallow library is used
for object serialization. No previous knowledge of marshmallow
is needed.
The user defines models that map to SQLAlchemy
tables. Each model represents a single JSONAPI resource. Each
resource has a type. A set of fields can be defined for each resource. A field can be a simple attribute mapping
directly to a database column, or derived from multiple columns. The user may also define aggregate fields (ex.
counts, max values, etc.). Relationship fields can be used to define relationships between models.
The library supports the fetching of resource data, inclusion of related resources, sparse fieldsets, sorting, pagination, and filtering.
Quick Start¶
As an example, we create a resource model and use it to implement two basic API calls.
First we use SQLAlchemy to describe the database tables:
import datetime as dt
import sqlalchemy as sa
metadata = sa.MetaData()
PASSWORD_HASH_LENGTH = 128
users_t = sa.Table(
'users', metadata,
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('email', sa.Text, unique=True, nullable=False),
sa.Column('created_on', sa.DateTime, nullable=False,
default=dt.datetime.utcnow),
sa.Column('password', sa.String(PASSWORD_HASH_LENGTH),
nullable=False))
user_names_t = sa.Table(
'user_names', metadata,
sa.Column('user_id', sa.Integer, sa.ForeignKey('users.id'),
primary_key=True, autoincrement=False),
sa.Column('title', sa.Text),
sa.Column('first', sa.Text, nullable=False),
sa.Column('middle', sa.Text),
sa.Column('last', sa.Text, nullable=False),
sa.Column('suffix', sa.Text),
sa.Column('nickname', sa.Text))
Then we define the model:
from jsonapi.model import Model
from jsonapi.fields import Derived
class UserModel(Model):
from_ = users_t, user_names_t
fields = ('email', 'first', 'last', 'created_on'
Derived('name', lambda rec: rec.first + ' ' + rec.last))
Note
The id
and type
fields are predefined.
Attempting to do define them explicitly will raise an exception.
The primary key of the mapped table is automatically assigned to the id
field, regardless of what the
database column is called. The type
field is determined by the value of the Model.type_
attribute
(see Defining Models for more details).
Note
Composite primary keys are not allowed in the mapped tables.
Note
You can define fields for a subset of the available database columns. In the example above, we chose not to
expose the password
column, for example.
Now we are ready to implement the API calls. We use the Quart
web framework for demonstration
purposes:
import asyncio
import uvloop
from quart import Quart, jsonify, request
from asyncpgsa import pg
from jsonapi.model import MIME_TYPE
from jsonapi.tests.model import UserModel
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
app = Quart('jsonapi-test')
app.config['JSONIFY_MIMETYPE'] = MIME_TYPE
@app.before_first_request
async def init():
await pg.init(database='jsonapi',
user='jsonapi',
password='jsonapi',
min_size=5, max_size=10)
@app.route('/users/')
async def users():
return jsonify(await UserModel().get_collection(request.args))
@app.route('/users/<int:user_id>')
async def user(user_id):
return jsonify(await UserModel().get_object(request.args, user_id))
if __name__ == "__main__":
app.run(host="localhost", port=8080, loop=asyncio.get_event_loop())
Example 1. Fetching a Single Object¶
GET http://localhost/users/1
?fields[user]=email,name
HTTP/1.1 200
Content-Type: application/vnd.api+json
{
"data": {
"attributes": {
"email": "dianagraham@fisher.com",
"name": "Robert Camacho"
},
"id": "1",
"type": "user"
}
}
Example 2. Fetching a Collection of Objects¶
GET http://localhost/users/
?fields[user]=created-on,name,email
&sort=-created-on
&page[size]=10
HTTP/1.1 200
Content-Type: application/vnd.api+json
{
"data": [
{
"attributes": {
"createdOn": "2019-10-03T16:27:01Z",
"email": "dana58@wall.org",
"name": "Tristan Nguyen"
},
"id": "888",
"type": "user"
},
{
"attributes": {
"createdOn": "2019-10-03T11:18:34Z",
"email": "gilbertjacob@yahoo.com",
"name": "Christian Bennett"
},
"id": "270",
"type": "user"
},
...
],
"meta": {
"total": 1000
}
}
Next Steps¶
In the following sections we will guide you through the different features available.