Quick tour of Guillotina

Guillotina is powerful datastore, capable of storing and indexing millions of objects.

It is a high performance web server based on many of the technologies and lessons learned from Plone, Pyramid, Django and others all while utilizing Python’s great AsyncIO library.

Using Python’s AsyncIO, it works well with micro-service oriented environments.

Features:

  • REST JSON API
  • Built-in authentication/authorization, built-in JWT support
  • Hierarchical data/url structure. Object storage.
  • Permissions/roles/groups
  • Fully customizable permission/roles/groups based on hierarchical data structure
  • Robust customizable component architecture and configuration syntax
  • Content types, dynamic behaviors, based on python interfaces and json schemas.
  • Built-in CORS support
  • Serialization/Validation library integrated.
  • Elastic search integration through guillotina_elasticsearch, or fallback to PostgreSQL json indexing.
  • Declarative configuration using decorators.
  • Integrated cloud storage file uploads.
  • py.test fixtures for easy service/api/endpoint testing
  • Built-in command system to run jobs.
  • Rich ecosystem of additional packages for adding additional features: Integration with RabbitMQ, batching of queries, redis cache layer.
  • Powerful addon architecture based on Zope Component Architecture.

What is Guillotina like?

Example configuration:

---
applications:
- myapp
databases:
- db:
    storage: postgresql
    dsn:
      scheme: postgres
      dbname: guillotina
      user: postgres
      host: localhost
      password: ''
      port: 5432
port: 8080
root_user:
  password: root

Example service:

See instructions below to play with.

from guillotina import configure


@configure.service(name="@foobar")
async def foobar(context, request):
    return {"foo": "bar"}

Example content type:

See instructions below to play with.

from guillotina import configure
from guillotina import content
from guillotina import Interface
from guillotina import schema


class IMyType(Interface):
    foobar = schema.TextLine()


@configure.contenttype(
    type_name="MyType", schema=IMyType, behaviors=["guillotina.behaviors.dublincore.IDublinCore"]
)
class Foobar(content.Item):
    pass

Example usage:

See instructions below to play with.

POST /db/container/

Create MyType

Example

http

POST /db/container HTTP/1.1
Accept: application/json
Content-Type: application/json
Host: localhost:8080
Authorization: Basic cm9vdDpyb290

{
  "@type": "MyType",
  "id": "foobar",
  "foobar": "foobar"
}

curl

curl -i -X POST http://localhost:8080/db/container -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"@type": "MyType", "foobar": "foobar", "id": "foobar"}' --user root:root

wget

wget -S -O- http://localhost:8080/db/container --header='Accept: application/json' --header='Content-Type: application/json' --post-data='{"@type": "MyType", "foobar": "foobar", "id": "foobar"}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "@type": "MyType",
  "foobar": "foobar",
  "id": "foobar"
}' | http POST http://localhost:8080/db/container Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/container', headers={'Accept': 'application/json', 'Content-Type': 'application/json'}, json={'@type': 'MyType', 'foobar': 'foobar', 'id': 'foobar'}, auth=('root', 'root'))

response

HTTP/1.1 201 OK
Content-Type: application/json
Request Headers:
 
Status Codes:
GET /db/container/foobar/

Get MyType

Example

http

GET /db/container/foobar HTTP/1.1
Accept: application/json
Host: localhost:8080
Authorization: Basic cm9vdDpyb290

curl

curl -i http://localhost:8080/db/container/foobar -H 'Accept: application/json' --user root:root

wget

wget -S -O- http://localhost:8080/db/container/foobar --header='Accept: application/json' --auth-no-challenge --user=root --password=root

httpie

http http://localhost:8080/db/container/foobar Accept:application/json -a root:root

python-requests

requests.get('http://localhost:8080/db/container/foobar', headers={'Accept': 'application/json'}, auth=('root', 'root'))

response

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

{
    "@id": "http://localhost:8080/db/container/foobar",
    "@name": "foobar",
    "@type": "MyType",
    "@uid": "e3f|81c5406638bd4a68b89275f739fc18b2",
    "UID": "e3f|81c5406638bd4a68b89275f739fc18b2",
    "creation_date": "2018-07-21T13:14:15.245181+00:00",
    "foobar": "foobar",
    "guillotina.behaviors.dublincore.IDublinCore": {
        "contributors": [
            "root"
        ],
        "creation_date": "2018-07-21T13:14:15.245181+00:00",
        "creators": [
            "root"
        ],
        "description": null,
        "effective_date": null,
        "expiration_date": null,
        "modification_date": "2018-07-21T13:14:15.245181+00:00",
        "publisher": null,
        "tags": null,
        "title": null
    },
    "is_folderish": false,
    "modification_date": "2018-07-21T13:14:15.245181+00:00",
    "parent": {
        "@id": "http://localhost:8080/db/container",
        "@name": "container",
        "@type": "Container",
        "@uid": "e3f4e401d12843a4a303666da4158458",
        "UID": "e3f4e401d12843a4a303666da4158458"
    }
}
Request Headers:
 
Status Codes:
POST /db/@foobar

Use foobar service

Example

http

POST /db/@foobar HTTP/1.1
Accept: application/json
Host: localhost:8080
Authorization: Basic cm9vdDpyb290

curl

curl -i -X POST http://localhost:8080/db/@foobar -H 'Accept: application/json' --user root:root

wget

wget -S -O- http://localhost:8080/db/@foobar --header='Accept: application/json' --auth-no-challenge --user=root --password=root

httpie

http POST http://localhost:8080/db/@foobar Accept:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/@foobar', headers={'Accept': 'application/json'}, auth=('root', 'root'))

response

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

{ "foo": "bar"}

or

http

POST /db/container/@foobar HTTP/1.1
Accept: application/json
Host: localhost:8080
Authorization: Basic cm9vdDpyb290

curl

curl -i -X POST http://localhost:8080/db/container/@foobar -H 'Accept: application/json' --user root:root

wget

wget -S -O- http://localhost:8080/db/container/@foobar --header='Accept: application/json' --auth-no-challenge --user=root --password=root

httpie

http POST http://localhost:8080/db/container/@foobar Accept:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/container/@foobar', headers={'Accept': 'application/json'}, auth=('root', 'root'))

response

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

{ "foo": "bar"}

or

http

POST /db/container/foobar/@foobar HTTP/1.1
Accept: application/json
Host: localhost:8080
Authorization: Basic cm9vdDpyb290

curl

curl -i -X POST http://localhost:8080/db/container/foobar/@foobar -H 'Accept: application/json' --user root:root

wget

wget -S -O- http://localhost:8080/db/container/foobar/@foobar --header='Accept: application/json' --auth-no-challenge --user=root --password=root

httpie

http POST http://localhost:8080/db/container/foobar/@foobar Accept:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/container/foobar/@foobar', headers={'Accept': 'application/json'}, auth=('root', 'root'))

response

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

{ "foo": "bar"}
Request Headers:
 
Status Codes:

You can see that @foobar service is available on any endpoints.

Playing with those examples

To play with those examples you should install Guillotina and Cookiecutter, let’s do that in a python venv:

python3.7 -m venv .
source ./bin/activate
pip install guillotina cookiecutter

Use Guillotina templates to create an application:

./bin/g create --template=application
full_name []: My App
email []: guillotina@myapp.io
package_name [guillotina_myproject]: myapp
project_short_description [Guillotina server application python project]:
Select open_source_license:
1 - MIT license
2 - BSD license
3 - ISC license
4 - Apache Software License 2.0
5 - GNU General Public License v3
6 - Not open source
Choose from 1, 2, 3, 4, 5, 6 [1]:

You should now have a structure like the following one:

.
└── myapp
    ├── README.rst
    ├── config.yaml
    ├── myapp
    │   ├── __init__.py
    │   ├── api.py
    │   └── install.py
    └── setup.py

Now copy Example content type section content in myapp/myapp/content.py.

Add configure.scan('myapp.content') to myapp/myapp/__init__.py includeme function.

@foobar service is already defined in myapp/mayapp/api.py.

Then install myapp:

pip install -e myapp

Edit myapp/config.yaml to fit your needs, especially in term of db configuration.

And run guillotina with:

g serve -c myapp/config.yaml

Now create a container:

http

POST /db/ HTTP/1.1
Accept: application/json
Content-Type: application/json
Host: localhost:8080
Authorization: Basic cm9vdDpyb290

{
    "@type": "Container",
    "title": "Container 1",
    "id": "container",
    "description": "Description"
}

curl

curl -i -X POST http://localhost:8080/db/ -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"@type": "Container", "description": "Description", "id": "container", "title": "Container 1"}' --user root:root

wget

wget -S -O- http://localhost:8080/db/ --header='Accept: application/json' --header='Content-Type: application/json' --post-data='{"@type": "Container", "description": "Description", "id": "container", "title": "Container 1"}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "@type": "Container",
  "description": "Description",
  "id": "container",
  "title": "Container 1"
}' | http POST http://localhost:8080/db/ Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/', headers={'Accept': 'application/json', 'Content-Type': 'application/json'}, json={'@type': 'Container', 'description': 'Description', 'id': 'container', 'title': 'Container 1'}, auth=('root', 'root'))

response

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

You can now use all above examples.