Type System REST API

For each published resource type, several endpoints are automatically made available by the framework to use. This is done through Morepath view inheritance on model/collection objects.

Lets take for example the following resource type definition:

import typing
from dataclasses import dataclass, field

import morpfw
import morpfw.sql
import sqlalchemy as sa
from morpfw.authz.pas import DefaultAuthzPolicy
from morpfw.crud import permission as crudperm
from morpfw.permission import All


class AppRoot(object):
    def __init__(self, request):
        self.request = request


class App(DefaultAuthzPolicy, morpfw.SQLApp):
    pass


@App.path(model=AppRoot, path="/")
def get_approot(request):
    return AppRoot(request)


@App.permission_rule(model=AppRoot, permission=All)
def allow_all(identity, context, permission):
    """ Default permission rule, allow all """
    return True


@App.json(model=AppRoot)
def index(context, request):
    return {"message": "Hello World"}


@dataclass
class PageSchema(morpfw.Schema):

    body: typing.Optional[str] = field(default=None, metadata={"title": "Body"})
    value: typing.Optional[int] = field(default=0, metadata={"title": "Value"})


class PageCollection(morpfw.Collection):
    schema = PageSchema


class PageModel(morpfw.Model):
    schema = PageSchema

    blob_fields = ['attachment']

# SQLALchemy model
class Page(morpfw.sql.Base):

    __tablename__ = "test_page"

    body = sa.Column(sa.Text())
    value = sa.Collection(sa.Integer())


class PageStorage(morpfw.SQLStorage):
    model = PageModel
    orm_model = Page


@App.storage(model=PageModel)
def get_storage(model, request, blobstorage):
    return PageStorage(request, blobstorage=blobstorage)


@App.path(model=PageCollection, path="/pages")
def get_collection(request):
    storage = request.app.get_storage(PageModel, request)
    return PageCollection(request, storage)


@App.path(model=PageModel, path="/pages/{identifier}")
def get_model(request, identifier):
    col = get_collection(request)
    return col.get(identifier)


class PageStateMachine(morpfw.StateMachine):

    states = ["new", "pending", "approved"]
    transitions = [
        {"trigger": "approve", "source": ["new", "pending"], "dest": "approved"},
        {"trigger": "submit", "source": "new", "dest": "pending"},
    ]


@App.statemachine(model=PageModel)
def get_pagemodel_statemachine(context):
    return PageStateMachine(context)


@App.permission_rule(model=PageCollection, permission=All)
def allow_collection_all(identity, context, permission):
    """ Default permission rule, allow all """
    return True


@App.permission_rule(model=PageModel, permission=All)
def allow_model_all(identity, context, permission):
    """ Default permission rule, allow all """
    return True


@App.typeinfo(name="test.page", schema=PageSchema)
def get_typeinfo(request):
    return {
        "title": "Test Page",
        "description": "",
        "schema": PageSchema,
        "collection": PageCollection,
        "collection_factory": get_collection,
        "model": PageModel,
        "model_factory": get_model,
    }

Collection

GET /pages

Display page collection metadata

Example Response:

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

{
    "schema": {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "type": "object",
        "properties": {
            "id": {
                "type": "integer"
            },
            "uuid": {
                "type": "string"
            },
            "creator": {
                "type": "string"
            },
            "created": {
                "type": "string",
                "format": "date-time"
            },
            "modified": {
                "type": "string",
                "format": "date-time"
            },
            "state": {
                "type": "string"
            },
            "deleted": {
                "type": "string",
                "format": "date-time"
            },
            "blobs": {
                "type": "object"
            },
            "xattrs": {
                "type": "object"
            },
            "body": {
                "type": "string"
            },
            "value": {
                "type": "integer"
            }
        },
        "additionalProperties": true
    },
    "links": [
        {
            "rel": "create",
            "href": "http://localhost:5000/pages",
            "method": "POST"
        },
        {
            "rel": "search",
            "href": "http://localhost:5000/pages/+search"
        }
    ]
}
POST /pages

Create new page

Example request:

POST /pages/ HTTP/1.1
Content-Type: application/json

{
    "body": "Hello world"
}

Example response:

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

{
    "data": {
        "id": 1,
        "uuid": "ea31ebb4eb814572b2cbfc2d30fac7f2",
        "creator": "285969eefd7547d38fb3a5d06996f93e",
        "created": "2019-01-29T08:37:48.653715",
        "modified": "2019-01-29T08:37:48.653715",
        "state": null,
        "deleted": null,
        "body": "Hello world",
        "value": 0
    },
    "links": [
        {
            "rel": "self",
            "href": "http://localhost:5000/pages/ea31ebb4eb814572b2cbfc2d30fac7f2"
        },
        {
            "rel": "update",
            "href": "http://localhost:5000/pages/ea31ebb4eb814572b2cbfc2d30fac7f2",
            "method": "PATCH"
        },
        {
            "rel": "delete",
            "href": "http://localhost:5000/pages/ea31ebb4eb814572b2cbfc2d30fac7f2",
            "method": "DELETE"
        }
    ]
}
GET /pages/+aggregate

The aggregate API allows you to query for aggregate of fields from your resource dataset.

Query Parameters:
  • group – grouping structure

  • qrulez dsl based filter query

  • order_by – string in field:order format where order is asc or asc and field is the field name.

Example request:

from urllib.parse import urlencode
import json
import requests


qs = urlencode({
    'group': ("count:count(uuid), year:year(created), month:month(created),"
              "day:day(created), sum:sum(value), avg:avg(value)")
})
requests.get('/pages/+aggregate?%s' % qs)

Example response:

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

[
    {
        "year": 2019,
        "month": 1,
        "day": 1,
        "sum": 45,
        "count": 11,
        "avg": 4.5
    },
    {
        "year": 2019,
        "month": 1,
        "day": 2,
        "sum": 60,
        "count": 14,
        "avg": 4.6
    }
]
GET /pages/+search

The search API allows you to do advanced querying on your resources using Rulez query structure.

Query Parameters:
  • select – jsonpath field selector

  • qrulez dsl based filter query

  • order_by – string in field:order format where order is asc or asc and field is the field name.

  • offset – result offset

  • limit – result limit

Warning

select query parameter would alter the response data structure from {"data":{},"links":[]} to ["val1","val2","val3" ... ]

Example request:

from urllib.parse import urlencode
import json
import requests

qs = urlencode({
    'q': 'body in ["Hello"]'
})
requests.get('http://localhost:5000/pages/+search?%s' % qs)

Example response:

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

{
    "results": [
        {"data": {}, "links": []},
        {"data": {}, "links": []},
        {"data": {}, "links": []},
        {"data": {}, "links": []}
    ],
    "q": null
}

Model

GET /page/{uuid}

Display resource data

Example response:

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

{
    "data": {
        "id": 1,
        "uuid": "ea31ebb4eb814572b2cbfc2d30fac7f2",
        "creator": "285969eefd7547d38fb3a5d06996f93e",
        "created": "2019-01-29T08:37:48.653715",
        "modified": "2019-01-29T08:37:48.653715",
        "state": null,
        "deleted": null,
        "body": "Hello world",
        "value": 0
    },
    "links": [
        {
            "rel": "self",
            "href": "http://localhost:5000/pages/ea31ebb4eb814572b2cbfc2d30fac7f2"
        },
        {
            "rel": "update",
            "href": "http://localhost:5000/pages/ea31ebb4eb814572b2cbfc2d30fac7f2",
            "method": "PATCH"
        },
        {
            "rel": "delete",
            "href": "http://localhost:5000/pages/ea31ebb4eb814572b2cbfc2d30fac7f2",
            "method": "DELETE"
        }
    ]
}
PATCH /page/{uuid}

Update resource data

Example request:

PATCH /pages/ea31ebb4eb814572b2cbfc2d30fac7f2 HTTP/1.1
Content-Type: application/json

{
    "body": "Foo bar"
}

Example response:

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

{"status": "success"}
DELETE /page/{uuid}

Delete resource

Example request:

DELETE /pages/ea31ebb4eb814572b2cbfc2d30fac7f2 HTTP/1.1

Example response:

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

{"status": "success"}
POST /page/{uuid}/+blobs?field={blobfieldname}

Upload blob using HTTP file upload

Example request:

import json
import requests

requests.post('http://localhost:5000/pages/ea31ebb4eb814572b2cbfc2d30fac7f2/+blobs?field=attachment',
              files={'upload': open('/path/to/file.jpg')})

Example response:

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

{"status": "success"}
GET /page/{uuid}/+blobs?field={blobfieldname}

Download blob

DELETE /page/{uuid}/+blobs?field={blobfieldname}

Delete blob

GET /page/{uuid}/+xattr-schema

Get JSON schema for validating extended attributes. This view is only available if your model have an extended attribute provider registered

GET /page/{uuid}/+xattr

Return extended attributes. This view is only available if your model have an extended attribute provider registered

Example response:

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

{
    "field1": "value1",
    "field2": "value2"
}
PATCH /page/{uuid}/+xattr

Update extended attributes. This view is only available if your model have an extended attribute provider registered

Example request:

PATCH /pages/ea31ebb4eb814572b2cbfc2d30fac7f2/+xattr HTTP/1.1
Content-Type: application/json

{
    "field1": "value3",
    "field3": "value4"
}

Example response:

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

{"status": "success"}
POST /pages/{uuid}/+statemachine

Apply transition. This view is only available if your model have a state machine registered.

Example request:

POST /pages/ea31ebb4eb814572b2cbfc2d30fac7f2 HTTP/1.1
Content-Type: application/json

{"transition": "approve"}

Example response:

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

{"status":"success"}