Guillotina: The Python AsyncIO REST API Framework

Guillotina is the only full-featured Python AsyncIO REST Resource Application Server designed for high-performance, horizontally scaling solutions.

Why Guillotina

  • Performance: Traditional Python web servers limit the number of simultaneous requests to the number of threads running the server. With AsyncIO, you are able to serve many more simultaneous requests.
  • Front-end friendly: Guillotina is designed to make your JavaScript engineers happy. With things like automatic Swagger documentation for endpoints, out of the box CORS and websockets, your front-end team will be happy to work with Guillotina. We speak JSON but can adapt to any content type payload request/response bodies.
  • AsyncIO: With AsyncIO, websockets are simple. More interestingly, AsyncIO is an ideal match with microservice architectures.
  • Object model: Guillotina uses a hierarchial object model. This hierarchy of objects then maps to URLs and is perfect for managing a large number of objects.
  • Security: Guillotina has a granular, hierarchical, multidimensional security system that allows you to manage the security of your content at a level not available to other frameworks.
  • Scale: With integrations like Redis, ElasticSearch and Cockroach, you have the tools to scale.

Getting Started

Are you new to Guillotina? This is the place to start!

Quick tour of Guillotina gives an overview of the major features in Guillotina, covering a little about a lot.

Need help? Join our Gitter channel.

Training / Tutorial

To learn more, go to the training section. You can learn basics and find topics about Guillotina:

Training

Prerequisites:
  • Python >= 3.6
  • Docker
  • Postman

Contents:

Introduction

The Guillotina training is designed to give a complete experience of using and extending Guillotina.

The training can be useful for consumers of the Guillotina API as well as developers extending the framework with customizations/addons.

Please read the about chapter for details about what Guillotina is and why you should use it.

Using the training materials

The training materials make use of Python 3.6, Docker and Postman so please have up-to-date versions of all these ready.

References

Installing Guillotina

Guillotina is a simple Python package so it can be installed with any of the number of installation methods available to Python.

In the traing here, we will focus on using pip and docker. You can use, for example, buildout as well.

with pip

Note

It is recommended you install along with a virtualenv:

virtualenv-3.6 genv
cd genv
source ./bin/activate

It’s as simple as…

pip install guillotina

For the purpose of this training, you’ll also need to install cookiecutter.

pip install cookiecutter

Guillotina also provides docker images.

References

Starting Guillotina

Once you have guillotina installed, you can easily run it with the g executable that it installs.

However, before we begin, we’ll need to run a postgresql server for Guillotina to use.

docker run -e POSTGRES_DB=guillotina -e POSTGRES_USER=guillotina -p 127.0.0.1:5432:5432 postgres:9.6

Note

This particular docker run command produces a volatile database. Stopping and starting it again will cause you to lose any data you pushed into it.

Command

Then, simply run the default Guillotina command g.

g

Which should give you output like:

$ g
Could not find the configuration file config.yaml. Using default settings.
======== Running on http://0.0.0.0:8080 ========
(Press CTRL+C to quit)

The g executable allows you to potentially run a number of commands with Guillotina. The default command is serve if none provided; however, you can explicitly run it with the serve command name as well.

g serve

The serve command also takes --host and --port options to quickly change without touching configuration.

In future sections, we’ll explore other commands available.

Check installation

Open up Postman and do a basic GET against http://localhost:8080 with basic auth credentials for root user and root password.

Also, do a GET on http://localhost:8080/db.

Congratulations! You have Guillotina running!

Useful run options

  • --reload: auto reload on code changes. requires aiohttp_autoreload
  • --profile: profile Guillotina while it’s running
  • --profile-output: where to save profiling output
  • --monitor: run with aiomonitor. requires aiomonitor

References

Configuration

You may have wondered how running g command without any configuration and options knew to connect and configure the database. Well, it’s only because we provide default settings in our application and documentation to make that step easy.

In this section, we’ll talk about working with the Guillotina configuration system.

Getting started

Guillotina provides a command to bootstrap a configuration file for you.

g create --template=configuration

This will produce a config.yaml file in your current path. Inspect the file to see what some of the default configuration options are.

Modifying configuration

A detailed list of configuration options and explanations can be found in the configuration section of the docs.

Note

Guillotina also supports JSON configuration files

Configuration file

To specify a configuration file other than the name config.yaml, you can use the -c or --config command line option.

g -c config-foobar.yaml

Installing applications

Guillotina applications are python packages that you install and then configure in your application settings.

For an example, we’ll go through installing swagger support.

pip install guillotina_swagger

Then, add this to your config.yaml file.

applications:
- guillotina_swagger

Finally, start Guillotina again and visit http://localhost:8080/@docs.

References

Using the Guillotina API

Before we start using the Guillotina API, let’s get us some test data to play with.

Using the testdata command, we’ll populate our database with some data from wikipedia.

g testdata --per-node=5 --depth=2 --container=container

Interacting with the API

You can use whatever you’d like but this training will mention use of Postman.

Open up Postman and do a GET on http://localhost:8080/db/container with the username root and password root for basic auth.

We can not necessarily go over every single API but will touch on a few and give a general understanding of how to explore and use the API.

Creating content

To create content, do a POST request on a container or folder object.

POST /(db)/(container)

Add new resouce inside this container resource

  • Permission: guillotina.AddContent
  • Context: guillotina.interfaces.content.IResource

http

POST /db/container HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

{
    "@type": "Item",
    "id": "foobar5"
}

curl

curl -i -X POST http://nohost/db/container -H 'Accept: application/json' --data-raw '{
    "@type": "Item",
    "id": "foobar5"
}' --user root:root

httpie

echo '{
    "@type": "Item",
    "id": "foobar5"
}' | http POST http://nohost/db/container Accept:application/json -a root:root

response

HTTP/1.1 201 OK
Content-Length: 186
Content-Type: application/json
Location: http://127.0.0.1:60361/db/container/foobar5

{
    "@id": "http://127.0.0.1:60361/db/container/foobar5",
    "@name": "foobar5",
    "@type": "Item",
    "@uid": "b2c|99e3e8cd6b454a98aa4bb6cfbd8732c1",
    "UID": "b2c|99e3e8cd6b454a98aa4bb6cfbd8732c1"
}
Status Codes:

Adding behaviors

To add a dynamic behavior, we use the @behavior endpoint.

PATCH /(db)/(container)/(content)/@behaviors

Add behavior to resource

  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

PATCH /db/container/foobar5/@behaviors HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

{
    "behavior": "guillotina.behaviors.attachment.IAttachment"
}

curl

curl -i -X PATCH http://nohost/db/container/foobar5/@behaviors -H 'Accept: application/json' --data-raw '{
    "behavior": "guillotina.behaviors.attachment.IAttachment"
}' --user root:root

httpie

echo '{
    "behavior": "guillotina.behaviors.attachment.IAttachment"
}' | http PATCH http://nohost/db/container/foobar5/@behaviors Accept:application/json -a root:root

response

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

{}
Status Codes:

Uploading files

Simple file uploads can be done with the @upload endpoint.

PATCH /(db)/(container)/(content)/@upload/file
  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

PATCH /db/container/foobar5/@upload/file HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

<text data>

curl

curl -i -X PATCH http://nohost/db/container/foobar5/@upload/file -H 'Accept: application/json' --data-raw '<text data>' --user root:root

httpie

echo '<text data>' | http PATCH http://nohost/db/container/foobar5/@upload/file Accept:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

Then, to download the file, use the @download endpoint.

GET /(db)/(container)/(content)/@download/file
  • Permission: guillotina.ViewContent
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container/foobar5/@download/file HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

<text data>

curl

curl -i http://nohost/db/container/foobar5/@download/file -H 'Accept: application/json' --data-raw '<text data>' --user root:root

httpie

echo '<text data>' | http http://nohost/db/container/foobar5/@download/file Accept:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Disposition: attachment; filename="9d8a80ee06b343faa8790d996fa49f84"
Content-Length: 11
Content-Type: text/plain

<text data>
Status Codes:

Uploading files with TUS

Guillotina also supports the TUS protocol using the @tusupload endpoint. The TUS protocol allows you to upload large files in chunks and allows you to have resumable uploads.

First, initialize the TUS upload with a POST

POST /(db)/(container)/(content)/@tusupload/file
  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

POST /db/container/foobar5/@tusupload/file HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
TUS-RESUMABLE: 1
UPLOAD-LENGTH: 22

curl

curl -i -X POST http://nohost/db/container/foobar5/@tusupload/file -H 'Accept: application/json' -H 'Tus-Resumable: 1' -H 'Upload-Length: 22' --user root:root

httpie

http POST http://nohost/db/container/foobar5/@tusupload/file Accept:application/json Tus-Resumable:1 Upload-Length:22 -a root:root

response

HTTP/1.1 201 OK
Content-Length: 2
Content-Type: application/json
Location: http://127.0.0.1:60361/db/container/foobar5/@tusupload/file
Tus-Resumable: 1.0.0

{}
Status Codes:

Next, upload the chunks(here we’re doing chunks):

PATCH /(db)/(container)/(content)/@tusupload/file
  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

PATCH /db/container/foobar5/@tusupload/file HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
TUS-RESUMABLE: 1
Upload-Offset: 0

<text data>

curl

curl -i -X PATCH http://nohost/db/container/foobar5/@tusupload/file -H 'Accept: application/json' -H 'Tus-Resumable: 1' -H 'Upload-Offset: 0' --data-raw '<text data>' --user root:root

httpie

echo '<text data>' | http PATCH http://nohost/db/container/foobar5/@tusupload/file Accept:application/json Tus-Resumable:1 Upload-Offset:0 -a root:root

response

HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json
Tus-Resumable: 1.0.0
Upload-Offset: 11

{}
Status Codes:

And final chunk:

PATCH /(db)/(container)/(content)/@tusupload/file
  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

PATCH /db/container/foobar5/@tusupload/file HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
TUS-RESUMABLE: 1
Upload-Offset: 11

<text data>

curl

curl -i -X PATCH http://nohost/db/container/foobar5/@tusupload/file -H 'Accept: application/json' -H 'Tus-Resumable: 1' -H 'Upload-Offset: 11' --data-raw '<text data>' --user root:root

httpie

echo '<text data>' | http PATCH http://nohost/db/container/foobar5/@tusupload/file Accept:application/json Tus-Resumable:1 Upload-Offset:11 -a root:root

response

HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json
Tus-Resumable: 1.0.0
Tus-Upload-Finished: 1
Upload-Offset: 22

{}
Status Codes:

Unknown upload size

Guillotina’s TUS implementation has support for the Upload-Defer-Length header. This means you can upload files with an unknown final upload size.

In order to implement this correctly, you will need to provide the Upload-Defer-Length: 1 header and value on the initial POST to start the TUS upload. You are then not required to provide the UPLOAD-LENGTH header.

Then, before or on your last chunk, provide a UPLOAD-LENGTH value to let TUS know the upload can not finish.

Simultaneous TUS uploads

Guillotina’s TUS implementation also attempts to prevent simultaneous uploaders.

If two users attempt to start an upload on the same object + field at the same time, a 412 error will be thrown. Guillotina tracks upload activity to detect this. If there is no activity detected for 15 seconds with an unfinished TUS upload, no error is thrown.

To override this, send the TUS-OVERRIDE-UPLOAD: 1 header.

Modifying permissions

The @sharing endpoint is available to inspect and modify permissions on an object.

GET /(db)/(container)/(content)/@sharing

Get sharing settings for this resource

  • Permission: guillotina.SeePermissions
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container/foobar5/@sharing HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/foobar5/@sharing -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/foobar5/@sharing Accept:application/json -a root:root

response

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

{
    "local": {
        "roleperm": {},
        "prinperm": {},
        "prinrole": {
            "root": {
                "guillotina.Owner": "Allow"
            }
        }
    },
    "inherit": [
        {
            "@id": "http://127.0.0.1:60361/db/container",
            "roleperm": {},
            "prinperm": {},
            "prinrole": {
                "root": {
                    "guillotina.ContainerAdmin": "Allow",
                    "guillotina.Owner": "Allow"
                }
            }
        }
    ]
}
Status Codes:

To modify, we use the same endpoint but with a POST.

POST /(db)/(container)/(content)/@sharing

Change permissions for a resource

  • Permission: guillotina.ChangePermissions
  • Context: guillotina.interfaces.content.IResource

http

POST /db/container/foobar5/@sharing HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

{
    "prinperm": [
        {
            "principal": "foobar",
            "permission": "guillotina.ModifyContent",
            "setting": "Allow"
        }
    ]
}

curl

curl -i -X POST http://nohost/db/container/foobar5/@sharing -H 'Accept: application/json' --data-raw '{
    "prinperm": [
        {
            "principal": "foobar",
            "permission": "guillotina.ModifyContent",
            "setting": "Allow"
        }
    ]
}' --user root:root

httpie

echo '{
    "prinperm": [
        {
            "principal": "foobar",
            "permission": "guillotina.ModifyContent",
            "setting": "Allow"
        }
    ]
}' | http POST http://nohost/db/container/foobar5/@sharing Accept:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

There are three types of permission settings you can modify:

  • prinperm: principal + permission
  • prinrole: principal + role
  • roleperm: role + permission

Each change can use the following settings:

  • Allow : you set it on the resource and the children will inherit
  • Deny : you set in on the resource and the children will inherit
  • AllowSingle : you set in on the resource and the children will not inherit
  • Unset : you remove the setting

Exploring the API with Swagger

In the previous step, we installed guillotina_swagger. With Swagger, we can inspect any context and explore the API.

Visit http://localhost:8080/@docs

Swaggeralt text

click the Authorize button

Swagger Authalt text

The Base API Endpoint setting is what the current context is that you’re exploring on. If you create content at /db/container/foobar and want to explore that content’s API, you should change the URL. Different content types will have different services available.

References

AsyncIO

Python’s asyncio library allows you to run single threaded “concurrent” code using coroutines inside an event loop.

The event loop is designed for I/O over sockets and other resources, it is especially good for working with client/server network connections.

Python >= 3.4(best features and performance in 3.6)

Explanation

Benefits

The event loop allows you to handle a larger number of network connections at once.

No network blocks, so you can have long running connections with very little performance impact (HTML5 sockets for example).

How web servers are typically designed
  • (Pyramid, Flash, Plone, etc)
  • Processes X Threads = Total number of concurrent connections that can be handled at once.
  • Client makes a request to web server, request is assigned thread, thread handle request and sends response
  • If no threads available, request is blocked, waiting for an open thread
  • Threads are expensive (CPU), Processes are expensive on RAM
How it works with AsyncIO
  • All requests are thrown on thread loop
  • Since we don’t block on network traffic, we can juggle many requests at the same time
  • Modern web application servers connect with many different services that can potentially block on network traffic — BAD
  • Limiting factor is maxed out CPU, not costly thread switching between requests — GOOD
Where is network traffic used?
  • Web Client/App Server
  • App Server/Database
  • App Server/Caching(redis)
  • App Server/OAUTH
  • App Server/Cloud storage
  • App Server/APIs(gdrive, m$, slack, etc)
Implementation details

In order to benefit, the whole stack needs to be asyncio-aware.

Anywhere in your application server that is not and does network traffic WILL BLOCK all other connections while it is doing its network traffic (example: using the requests library instead of aiohttp)

Basics

Get active event loop or create new one

Run coroutine inside event loop with asyncio.run_until_complete

import asyncio


async def hello():
    print('hi')


event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(hello())

Basics(2)

asyncio.run_until_complete automatically wraps your coroutine into a Future object and waits for it to finish.

asyncio.ensure_future will wrap a coroutine in a future and return it to you

So you can schedule multiple coroutines that can run at the same time

import asyncio


async def hello1():
    await asyncio.sleep(0.5)
    print('hi 1')


async def hello2():
    print('hi 2')


event_loop = asyncio.get_event_loop()
future1 = asyncio.ensure_future(hello1(), loop=event_loop)
future2 = asyncio.ensure_future(hello2(), loop=event_loop)
event_loop.run_until_complete(future2)
event_loop.run_until_complete(future1)

Long running tasks

You can also schedule long running tasks on the event loop.

The tasks can run forever…

“Task” objects are the same as “Future” objects(well, close)

import asyncio
import random


async def hello_many():
    while True:
        number = random.randint(0, 3)
        await asyncio.sleep(number)
        print('Hello {}'.format(number))


event_loop = asyncio.get_event_loop()
task = asyncio.Task(hello_many())
print('task running now...')
event_loop.run_until_complete(asyncio.sleep(10))
print('we waited 10 seconds')
task.cancel()
print('task cancelled')

ALL YOUR ASYNC BELONGS TO US

gotcha

If you want part of your code to be async(say a function), the complete stack of the caller must be async and running on the event loop.

import asyncio


async def print_foobar1():
    print('foobar1')


async def print_foobar2():
    print('foobar2')


async def foobar():
    await print_foobar1()
    print_foobar2()  # won't work, never awaited


event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(foobar())
print_foobar1()  # won't work, never awaited
# await print_foobar1()  # error, not running in event loop

“multi” processing

AsyncIO isn’t really multiprocessing but it gives you the illusion of it.

A simple example can be shown with the asyncio.gather function.

import asyncio
import aiohttp


async def download_url(url):
    async with aiohttp.ClientSession() as session:
        resp = await session.get(url)
        text = await resp.text()
        print(f'Downloaded {url}, size {len(text)}')


event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(asyncio.gather(
    download_url('https://www.google.com'),
    download_url('https://www.facebook.com'),
    download_url('https://www.twitter.com'),
    download_url('https://www.stackoverflow.com')
))

asyncio loops

Using yield with loops allows you to “give up” execution on every iteration of the loop.

import asyncio


async def yielding():
    for idx in range(5):
        print(f'Before yield {idx}')
        yield idx


async def foobar2():
    async for idx in yielding():
        print(f"Yay, I've been yield'd {idx}")


event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(foobar2())

Scheduling

loop.call_later: arrange to call on a delay loop.call_at: arrange function to be called at specified time

Executors

An executor is available to use when you have non-async code that needs to be made async.

A typical executor is a thread executor. This means, anything you run in an executor is being thrown in a thread to run.

It’s worse to have non-async code than to use thread executors.

Executors are also good for CPU bound code.

import asyncio
import requests
import concurrent.futures


def download_url(url):
    resp = requests.get(url)
    text = resp.content
    print(f'Downloaded {url}, size {len(text)}')


async def foobar():
    print('foobar')


executor = concurrent.futures.ThreadPoolExecutor(max_workers=5)

event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(asyncio.gather(
    event_loop.run_in_executor(executor, download_url, 'https://www.google.com'),
    event_loop.run_in_executor(executor, download_url, 'https://www.facebook.com'),
    event_loop.run_in_executor(executor, download_url, 'https://www.twitter.com'),
    event_loop.run_in_executor(executor, download_url, 'https://www.stackoverflow.com'),
    foobar()
))

Subprocess

Python also provides a very neat asyncio subprocess module.

import asyncio


async def run_cmd(cmd):
    print(f'Executing: {" ".join(cmd)}')
    process = await asyncio.create_subprocess_exec(*cmd, stdout=asyncio.subprocess.PIPE)
    out, error = await process.communicate()
    print(out.decode('utf8'))


event_loop = asyncio.get_event_loop()
event_loop.run_until_complete(asyncio.gather(
    run_cmd(['sleep', '1']),
    run_cmd(['echo', 'hello'])
))

Extending

In our training, we’ll be working on creating a simple chat application.

To extend Guillotina, we need to write a Python package.

Let’s start by using the cookiecutter to bootstrap an application for us.

g create --template=application

Follow the prompts and name your application guillotina_chat.

Then,

cd guillotina_chat
python setup.py develop

Configuration

All application extension configuration is defined with Guillotina’s configure module and the app_settings object.

Defining content types, behaviors, services, etc all require the use of the configure module. Guillotina reads all the registered configuration in code for each install application and loads it.

app_settings

Guillotina also provides a global app_settings object::

from guillotina import app_settings

This object contains all the settings from your config.yaml file as well as any additional configuration settings defined in addons.

app_settings has an order of precedence it will use pick settings from:

  • guillotina’s default settings
  • each application in order it is defined can override default guillotina settings
  • config.yaml takes final precedence over all configuration

app_settings has an extra key ‘file’ that contains the path of the configuration file, allowing relative paths to be used in an application settings.

Content types

For chatting, we’ll need a content type for conversations and messages.

Create a content.py file in your application and create the content types.

from guillotina import configure, content, Interface, schema


class IConversation(Interface):

    users = schema.List(
        value_type=schema.TextLine(),
        default=list()
    )


@configure.contenttype(
    type_name="Conversation",
    schema=IConversation,
    behaviors=["guillotina.behaviors.dublincore.IDublinCore"],
    allowed_types=['Message'])
class Conversation(content.Folder):
    pass


class IMessage(Interface):
    text = schema.Text(required=True)


@configure.contenttype(
    type_name="Message",
    schema=IMessage,
    behaviors=[
        "guillotina.behaviors.dublincore.IDublinCore",
        "guillotina.behaviors.attachment.IAttachment"
    ])
class Message(content.Item):
    pass

In order for Guillotina to detect your configuration, you’ll need to add a scan call inside your includeme function in the __init__.py file.

from guillotina import configure
configure.scan('guillotina_chat.content')
Test it out

Using Postman test your new content types. First create a Conversation, then create a Message inside of it.

Install an addons

Guillotina differentiates applications from addons.

An application is a python package you install into your environment and add to your list of applications in the configuration file.

Addons on the otherhand are when you want to perform installation logic into a container.

Define addon

To define an addon for Guillotina, we use the @configure.addon decorator in the install.py file.

For our case, we want to create a Folder with all our conversations with some default permissions.

from guillotina import configure
from guillotina.addons import Addon
from guillotina.content import create_content_in_container
from guillotina.interfaces import IRolePermissionManager


@configure.addon(
    name="guillotina_chat",
    title="Guillotina server application python project")
class ManageAddon(Addon):

    @classmethod
    async def install(cls, container, request):
        if not await container.async_contains('conversations'):
            conversations = await create_content_in_container(
                container, 'Folder', 'conversations',
                id='conversations', creators=('root',),
                contributors=('root',))
            roleperm = IRolePermissionManager(conversations)
            roleperm.grant_permission_to_role(
                'guillotina.AddContent', 'guillotina.Member')
            roleperm.grant_permission_to_role(
                'guillotina.AccessContent', 'guillotina.Member')

    @classmethod
    async def uninstall(cls, container, request):
        registry = request.container_settings  # noqa
        # uninstall logic here...
Testing

Then, using Postman, do a POST request to the @addons endpoint:

{"id": "guillotina_chat"}

Permissions/Role

Permissions are defined in your application code.

For our app, we’ll create roles that users are granted inside a conversation.

Add the following inside your __init__.py file.

configure.role("guillotina_chat.ConversationParticipant",
               "Conversation Participant",
               "Users that are part of a conversation", False)
configure.grant(
    permission="guillotina.ViewContent",
    role="guillotina_chat.ConversationParticipant")
configure.grant(
    permission="guillotina.AccessContent",
    role="guillotina_chat.ConversationParticipant")
configure.grant(
    permission="guillotina.AddContent",
    role="guillotina_chat.ConversationParticipant")

Event subscribers

Events in Guillotina are heavily influenced from zope events with the caveat in that we support async event handlers.

For our chat application, we want to make sure every user that is part of a conversation has permission to add new messages and view other messages.

A simple way to do this is with an event handler that modifies permissions.

A an subscribers.py file inside your application.

from guillotina import configure
from guillotina.interfaces import IObjectAddedEvent, IPrincipalRoleManager
from guillotina.utils import get_authenticated_user_id, get_current_request
from guillotina_chat.content import IConversation


@configure.subscriber(for_=(IConversation, IObjectAddedEvent))
async def container_added(conversation, event):
    user_id = get_authenticated_user_id(get_current_request())
    if user_id not in conversation.users:
        conversation.users.append(user_id)

    manager = IPrincipalRoleManager(conversation)
    for user in conversation.users:
        manager.assign_role_to_principal(
            'guillotina_chat.ConversationParticipant', user)

In order for Guillotina to detect your configuration, you’ll need to add a scan call inside your includeme function in the __init__.py file.

from guillotina import configure
configure.scan('guillotina_chat.subscribers')
Test it out

Using Postman, add a Conversation and then a Message to that conversation and then use the @sharing endpoint to inspect the assigned permissions.

Users

Guillotina does not come with any user provider OOTB and is designed to be plugged in with other services.

However, there is a simple provider that stores user data in the database called guillotina_dbusers that we will use for the purpose of our training.

Install guillotina_dbusers

Just use pip

pip install guillotina_dbusers

And add the guillotina_dbusers to the list of applications in your config.yaml. Also make sure you are not overriding the auth_user_identifiers configuration value in your config.yaml as guillotina_dbusers uses that to work.

After you restart guillotina, you can also install dbusers into your container using the @addons endpoint:

http

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

{
    "id": "dbusers"
}

curl

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

wget

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

httpie

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

python-requests

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

response

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

{
    "available": [
        {
            "id": "dbusers",
            "title": "Guillotina DB Users"
        },
        {
            "id": "application_name",
            "title": "Your application title"
        }
    ],
    "installed": [
        "dbusers",
        "application_name"
    ]
}
Add users

Creating users is just creating a user object.

http

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

{
    "@type": "User",
    "email": "bob@domain.io",
    "password": "secret",
    "username": "Bob"
}

curl

curl -i -X POST http://localhost:8080/db/container/users -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"@type": "User", "email": "bob@domain.io", "password": "secret", "username": "Bob"}' --user root:root

wget

wget -S -O- http://localhost:8080/db/container/users --header='Accept: application/json' --header='Content-Type: application/json' --post-data='{"@type": "User", "email": "bob@domain.io", "password": "secret", "username": "Bob"}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "@type": "User",
  "email": "bob@domain.io",
  "password": "secret",
  "username": "Bob"
}' | http POST http://localhost:8080/db/container/users Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/container/users', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    '@type': 'User',
    'email': 'bob@domain.io',
    'password': 'secret',
    'username': 'Bob',
}, auth=('root', 'root'))

response

HTTP/1.1 201 Created
Content-Type: application/json
Location: http://localhost:8080/db/container/users/Bob

{
    "@id": "http://localhost:8080/db/container/users/Bob",
    "@name": "Bob",
    "@type": "User",
    "@uid": "6e6|753|05893a69ee6e4f56b540248b5728c4a4",
    "UID": "6e6|753|05893a69ee6e4f56b540248b5728c4a4"
}

Logging in can be done with the @login endpoint which returns a jwt token.

http

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

{
    "password": "secret",
    "username": "Bob"
}

curl

curl -i -X POST http://localhost:8080/db/container/@login -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"password": "secret", "username": "Bob"}' --user root:root

wget

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

httpie

echo '{
  "password": "secret",
  "username": "Bob"
}' | http POST http://localhost:8080/db/container/@login Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/container/@login', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'password': 'secret',
    'username': 'Bob',
}, auth=('root', 'root'))

response

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

{
    "exp": 1532253747,
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I"
}

Then, future requests are done with a Bearer token with the jwt token. For example, to create a conversation with your user:

http

POST /db/container/conversations/ HTTP/1.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I
Host: localhost:8080

{
  "@type": "Conversation",
  "title": "New convo with foobar2",
  "users": ["foobar", "foobar2"]
}

curl

curl -i -X POST http://localhost:8080/db/container/conversations/ -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I' --data-raw '{
  "@type": "Conversation",
  "title": "New convo with foobar2",
  "users": ["foobar", "foobar2"]
}'

wget

wget -S -O- http://localhost:8080/db/container/conversations/ --header='Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I' --post-data='{
  "@type": "Conversation",
  "title": "New convo with foobar2",
  "users": ["foobar", "foobar2"]
}'

httpie

echo '{
  "@type": "Conversation",
  "title": "New convo with foobar2",
  "users": ["foobar", "foobar2"]
}' | http POST http://localhost:8080/db/container/conversations/ Authorization:'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I'

python-requests

requests.post('http://localhost:8080/db/container/conversations/', headers={
    'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I',
}, data='{\r\n  "@type": "Conversation",\r\n  "title": "New convo with foobar2",\r\n  "users": ["foobar", "foobar2"]\r\n}')

Serialize content

Guillotina provides default serializations for content. It provides mechanisms for giving full content serialization of interfaces and behaviors as well as summary serializations that show in listings.

To customize a serialization on a type, you need to provide a multi adapter for the IResourceSerializeToJsonSummary or IResourceSerializeToJson interfaces.

For our use-case, we want to make sure to include the creation_date and some other data in the summary serialization of conversations and messages so we can get all the info we need for our application without doing full objet serialization.

Defining a custom serialization

Let’s define these serializers in a in a file named serialize.py.

from guillotina import configure
from guillotina.interfaces import IResourceSerializeToJsonSummary
from guillotina.json.serialize_content import DefaultJSONSummarySerializer
from guillotina.utils import get_owners
from guillotina_chat.content import IConversation, IMessage
from zope.interface import Interface


@configure.adapter(
    for_=(IConversation, Interface),
    provides=IResourceSerializeToJsonSummary)
class ConversationJSONSummarySerializer(DefaultJSONSummarySerializer):
    async def __call__(self):
        data = await super().__call__()
        data.update({
            'creation_date': self.context.creation_date,
            'title': self.context.title,
            'users': self.context.users
        })
        return data


@configure.adapter(
    for_=(IMessage, Interface),
    provides=IResourceSerializeToJsonSummary)
class MessageJSONSummarySerializer(DefaultJSONSummarySerializer):
    async def __call__(self):
        data = await super().__call__()
        data.update({
            'creation_date': self.context.creation_date,
            'text': self.context.text,
            'author': get_owners(self.context)[0]
        })
        return data

And make sure to add the scan.

configure.scan('guillotina_chat.serialize')

Services

Services are synonymous with what other frameworks might call endpoints or views.

For the sake of our application, let’s use services for getting a user’s most recent conversations and messages for a conversation.

Creating the services

We’ll name our endpoints @get-conversations and @get-messages and put them in a file named services.py.

from guillotina import configure
from guillotina.component import get_multi_adapter
from guillotina.interfaces import IContainer, IResourceSerializeToJsonSummary
from guillotina.utils import get_authenticated_user_id
from guillotina_chat.content import IConversation


@configure.service(for_=IContainer, name='@get-conversations',
                   permission='guillotina.Authenticated')
async def get_conversations(context, request):
    results = []
    conversations = await context.async_get('conversations')
    user_id = get_authenticated_user_id(request)
    async for conversation in conversations.async_values():
        if user_id in getattr(conversation, 'users', []):
            summary = await get_multi_adapter(
                (conversation, request),
                IResourceSerializeToJsonSummary)()
            results.append(summary)
    results = sorted(results, key=lambda conv: conv['creation_date'])
    return results


@configure.service(for_=IConversation, name='@get-messages',
                   permission='guillotina.AccessContent')
async def get_messages(context, request):
    results = []
    async for message in context.async_values():
        summary = await get_multi_adapter(
            (message, request),
            IResourceSerializeToJsonSummary)()
        results.append(summary)
    results = sorted(results, key=lambda mes: mes['creation_date'])
    return results

And make sure to add the scan.

configure.scan('guillotina_chat.services')

Async Utilities

An async utility is a utility that run persistently on the asyncio event loop. It is useful for long running tasks.

For our training, we’re going to use an async utility with a queue to send messages to logged in users.

Create a utility.py file and put the following code in it.

from guillotina.async_util import IAsyncUtility
from guillotina.component import get_multi_adapter
from guillotina.interfaces import IResourceSerializeToJsonSummary
from guillotina.renderers import GuillotinaJSONEncoder
from guillotina.utils import get_authenticated_user_id, get_current_request

import asyncio
import json
import logging

logger = logging.getLogger('guillotina_chat')


class IMessageSender(IAsyncUtility):
    pass


class MessageSenderUtility:

    def __init__(self, settings=None, loop=None):
        self._loop = loop
        self._settings = {}
        self._webservices = []

    def register_ws(self, ws, request):
        ws.user_id = get_authenticated_user_id(request)
        self._webservices.append(ws)

    def unregister_ws(self, ws):
        self._webservices.remove(ws)

    async def send_message(self, message):
        summary = await get_multi_adapter(
            (message, get_current_request()),
            IResourceSerializeToJsonSummary)()
        await self._queue.put((message, summary))

    async def initialize(self, app=None):
        self._queue = asyncio.Queue()

        while True:
            try:
                message, summary = await self._queue.get()
                for user_id in message.__parent__.users:
                    for ws in self._webservices:
                        if ws.user_id == user_id:
                            await ws.send_str(json.dumps(
                                summary, cls=GuillotinaJSONEncoder))
            except Exception:
                logger.warning(
                    'Error sending message',
                    exc_info=True)
                await asyncio.sleep(1)

Async utilities must implement a initialize method and performs the async task. In our case, it is creating a queue and waiting to process messages in the queue.

In this case, we will send messages to registered websockets.

Make sure, like all other configured modules, to ensure this file is scanned by the packages __init__.py file.

Additionally, async utilities need to also be configured in __init__.py:

app_settings = {
    "load_utilities": {
        "guillotina_chat.message_sender": {
            "provides": "guillotina_chat.utility.IMessageSender",
            "factory": "guillotina_chat.utility.MessageSenderUtility",
            "settings": {}
        },
    }
}
Sending messages

We’ll need to add another event subscriber to the subscribers.py file in order for the utility to know to send out new messages to registered web services.So your subscribers.py file will now look like:

from guillotina import configure
from guillotina.component import get_utility
from guillotina.interfaces import IObjectAddedEvent, IPrincipalRoleManager
from guillotina.utils import get_authenticated_user_id, get_current_request
from guillotina_chat.content import IConversation, IMessage
from guillotina_chat.utility import IMessageSender


@configure.subscriber(for_=(IConversation, IObjectAddedEvent))
async def container_added(conversation, event):
    user_id = get_authenticated_user_id(get_current_request())
    if user_id not in conversation.users:
        conversation.users.append(user_id)

    manager = IPrincipalRoleManager(conversation)
    for user in conversation.users:
        manager.assign_role_to_principal(
            'guillotina_chat.ConversationParticipant', user)


@configure.subscriber(for_=(IMessage, IObjectAddedEvent))
async def message_added(message, event):
    utility = get_utility(IMessageSender)
    await utility.send_message(message)

Websockets

Websocket support is built-in to Guillotina.

It’s as simple as using an aiohttp websocket in a service.

Create a ws.py file and put the following code in:

from aiohttp import web
from guillotina import configure
from guillotina.component import get_utility
from guillotina.interfaces import IContainer
from guillotina.transactions import get_tm
from guillotina_chat.utility import IMessageSender

import aiohttp
import logging

logger = logging.getLogger('guillotina_chat')


@configure.service(
    context=IContainer, method='GET',
    permission='guillotina.AccessContent', name='@conversate')
async def ws_conversate(context, request):
    ws = web.WebSocketResponse()
    utility = get_utility(IMessageSender)
    utility.register_ws(ws, request)

    tm = get_tm(request)
    await tm.abort(request)
    await ws.prepare(request)

    async for msg in ws:
        if msg.tp == aiohttp.WSMsgType.text:
            # ws does not receive any messages, just sends
            pass
        elif msg.tp == aiohttp.WSMsgType.error:
            logger.debug('ws connection closed with exception {0:s}'
                         .format(ws.exception()))

    logger.debug('websocket connection closed')
    utility.unregister_ws(ws)

    return {}

Here, we use the utility = get_utility(IMessageSender) to get our async utility we defined previously. Then we register our webservice with utility.register_ws(ws, request).

Our web service is simple because we do not need to receive any messages and the async utility sends out the messages.

Using websockets

In order to use websockets, you need to request a websocket token first.

GET /db/container/@wstoken
Authentication Bearer <jwt token>

Then, use this token to generate a webservice URL(JavaScript example here):

var url = 'ws://localhost:8080/db/container/@conversate?ws_token=' + ws_token;
SOCKET = new WebSocket(url);
SOCKET.onopen = function(e){
};
SOCKET.onmessage = function(msg){
  var data = JSON.parse(msg.data);
};
SOCKET.onclose = function(e){
};

SOCKET.onerror = function(e){
};

Static files

To pull this all together, we’ll create our web application that uses the api to provide a very simple chat experience.

Copy the following files into a new folder static in your application:

Configure

Then, we’ll setup Guillotina to serve the folder.

Modify your config.yaml file to add:

static:
  status: ./static
JS Applications

You can also serve the static files in a way where it works with JavaScript applications that need to be able to translate URLs from something other than root.

jsapps:
  static: ./static

With this configuration any request to a url like http://localhost:8080/static/foo/bar will serve files from http://localhost:8080/static.

Commands

Guillotina comes with a great set of commands you can use to help debug and inspect your install.

We’ve already gone through the serve, create and testdata commands so we’ll now cover shell and run.

Make sure to also read the commands reference in the docs to learn how to create your own commands.

Shell

The shell command allows you to get an interactive prompt into guillotina.

From here, you can connect to the database, accees objects and commit new data.

g -c config.yml shell

Then, to connect to the database and get your container object.

txn = await use_db('db')
container = await use_container('container')

From here, you can access objects:

conversations = await container.async_get('conversations')
await conversations.async_keys()

Run

The run command allows you to run a python script directly.

g -c config.yaml run --script=path/to/script.py

In order for you to utilize this, the script must have an async function named run inside it.

async def run(container):
    pass

Kitchen Sink

This part of the training material is going to talk about the guillotina_kitchensink repository.

This repository gives you a working configuration and install of:

  • guillotina_dbusers: Store and manage users on the database
  • guillotina_elasticsearch: Index on content in elasticsearch
  • guillotina_swagger: Access site swagger definition at http://localhost:8080/@docs
  • guillotina_rediscache: Cache db objects in redis

The components it runs as part of the docker compose file are:

  • postgresql
  • elasticsearch
  • redis

First off, start by cloning the repository and starting it.

git clone https://github.com/guillotinaweb/guillotina_kitchensink.git
cd guillotina_kitchensink
docker-compose -f docker-compose.yaml run --rm --service-ports guillotina

Add some content using Postman and then let’s do an elasticsearch query:

POST /db/container/@search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "foobar"
          }
        }
      ]
    }
  }
}

REST API

After you’re up and running, primarily, Guillotina provides a REST API to work with and it is what you should become the most familiar with.

Guillotina API structure mirrors the object tree structure. Within the object tree structure, there are four major types of objects you’ll want to be familiar with:

  • Application: The root of the tree: /
  • Database: A configured database: /(db)
  • Container: An main object to add data to: /(db)/(container)
  • Content: Item or Folder by default. This is your dynamic object tree you create

The endpoints available around these objects are detailed below:

Application

GET GET

Get application data

Retrieves serialization of application

  • Permission: guillotina.AccessContent
  • Context: guillotina.interfaces.content.IApplication

http

GET / HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/ -H 'Accept: application/json' --user root:root

httpie

http http://nohost/ Accept:application/json -a root:root

response

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

{
    "databases": [
        "db"
    ],
    "static_file": [],
    "static_directory": [],
    "@type": "Application"
}
Status Codes:

GET GET

Get API Definition

Retrieves information on API configuration

  • Permission: guillotina.GetContainers
  • Context: guillotina.interfaces.content.IApplication

http

GET /@apidefinition HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/@apidefinition -H 'Accept: application/json' --user root:root

httpie

http http://nohost/@apidefinition Accept:application/json -a root:root

response

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

{
    "guillotina.interfaces.content.IContainer": {
        "endpoints": {
            "@addons": {
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "permission": "guillotina.ManageAddons",
                    "name": "@addons",
                    "summary": "Install addon to container",
                    "parameters": [
                        {
                            "name": "body",
                            "in": "body",
                            "schema": {
                                "$ref": "#/definitions/Addon"
                            }
                        }
                    ],
                    "module": "guillotina.api.addons.install"
                },
                "DELETE": {
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "method": "DELETE",
                    "permission": "guillotina.ManageAddons",
                    "name": "@addons",
                    "summary": "Uninstall an addon from container",
                    "parameters": [
                        {
                            "name": "body",
                            "in": "body",
                            "schema": {
                                "$ref": "#/definitions/Addon"
                            }
                        }
                    ],
                    "module": "guillotina.api.addons.uninstall"
                },
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.ManageAddons",
                    "name": "@addons",
                    "summary": "List available addons",
                    "responses": {
                        "200": {
                            "description": "Get list of available and installed addons",
                            "schema": {
                                "$ref": "#/definitions/AddonResponse"
                            }
                        }
                    },
                    "module": "guillotina.api.addons.get_addons"
                }
            },
            "@addons/{addon}": {
                "DELETE": {
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "method": "DELETE",
                    "permission": "guillotina.ManageAddons",
                    "name": "@addons/{addon}",
                    "summary": "Uninstall an addon from container",
                    "parameters": [
                        {
                            "name": "addon",
                            "in": "path"
                        }
                    ],
                    "module": "guillotina.api.addons.uninstall_path"
                }
            },
            "@resolveuid/{uid}": {
                "GET": {
                    "method": "GET",
                    "name": "@resolveuid/{uid}",
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "permission": "guillotina.AccessContent",
                    "summary": "Get content by UID",
                    "responses": {
                        "200": {
                            "description": "Successful"
                        }
                    },
                    "module": "guillotina.api.content.resolve_uid"
                }
            },
            "@login": {
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "permission": "guillotina.Public",
                    "name": "@login",
                    "summary": "Components for a resource",
                    "allow_access": true,
                    "module": "guillotina.api.login.Login"
                }
            },
            "@login-renew": {
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "permission": "guillotina.AccessContent",
                    "name": "@login-renew",
                    "summary": "Refresh to a new token",
                    "module": "guillotina.api.login.Refresh"
                }
            },
            "@registry/{key}": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.ReadConfiguration",
                    "name": "@registry/{key}",
                    "summary": "Read container registry settings",
                    "responses": {
                        "200": {
                            "description": "Successfully registered interface",
                            "type": "object",
                            "schema": {
                                "properties": {
                                    "value": {
                                        "type": "object"
                                    }
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.registry.Read"
                }
            },
            "@registry": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.ReadConfiguration",
                    "name": "@registry",
                    "summary": "Read container registry settings",
                    "responses": {
                        "200": {
                            "description": "Successfully registered interface",
                            "type": "object",
                            "schema": {
                                "properties": {
                                    "value": {
                                        "type": "object"
                                    }
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.registry.get_registry"
                },
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "permission": "guillotina.RegisterConfigurations",
                    "name": "@registry",
                    "summary": "Register a new interface to for registry settings",
                    "parameters": [
                        {
                            "name": "body",
                            "in": "body",
                            "type": "object",
                            "schema": {
                                "properties": {
                                    "interface": {
                                        "type": "string",
                                        "required": true
                                    },
                                    "initial_values": {
                                        "type": "object",
                                        "required": false
                                    }
                                }
                            }
                        }
                    ],
                    "responses": {
                        "200": {
                            "description": "Successfully registered interface"
                        }
                    },
                    "module": "guillotina.api.registry.Register"
                }
            },
            "@registry/{dotted_name}": {
                "PATCH": {
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "method": "PATCH",
                    "permission": "guillotina.WriteConfiguration",
                    "name": "@registry/{dotted_name}",
                    "summary": "Update registry setting",
                    "parameters": {
                        "name": "body",
                        "in": "body",
                        "type": "object",
                        "schema": {
                            "properties": {
                                "value": {
                                    "type": "any",
                                    "required": true
                                }
                            }
                        }
                    },
                    "responses": {
                        "200": {
                            "description": "Successfully wrote configuration"
                        }
                    },
                    "module": "guillotina.api.registry.Write"
                }
            },
            "@types": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.AccessContent",
                    "name": "@types",
                    "summary": "Read information on available types",
                    "responses": {
                        "200": {
                            "description": "Result results on types",
                            "schema": {
                                "properties": {}
                            }
                        }
                    },
                    "module": "guillotina.api.types.get_all_types"
                }
            },
            "@types/{type_name}": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.AccessContent",
                    "name": "@types/{type_name}",
                    "summary": "Read information on available types",
                    "responses": {
                        "200": {
                            "description": "Result results on types",
                            "schema": {
                                "properties": {}
                            }
                        }
                    },
                    "module": "guillotina.api.types.Read"
                }
            },
            "@user": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.AccessContent",
                    "name": "@user",
                    "summary": "Get information on the currently logged in user",
                    "responses": {
                        "200": {
                            "description": "Get information on the user",
                            "schema": {
                                "properties": {}
                            }
                        }
                    },
                    "module": "guillotina.api.user.get_user_info"
                }
            },
            "@wstoken": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.UseWebSockets",
                    "name": "@wstoken",
                    "summary": "Return a web socket token",
                    "responses": {
                        "200": {
                            "description": "The new token",
                            "schema": {
                                "properties": {
                                    "token": {
                                        "type": "string",
                                        "required": true
                                    }
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.ws.WebsocketGetToken"
                }
            },
            "@ws": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IContainer",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.component.interfaces.ISite",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.AccessContent",
                    "name": "@ws",
                    "summary": "Make a web socket connection",
                    "module": "guillotina.api.ws.WebsocketsView"
                }
            }
        },
        "DELETE": {
            "context": [
                "guillotina.interfaces.content.IContainer",
                "guillotina.interfaces.content.IResource",
                "guillotina.interfaces.content.ILocation",
                "guillotina.interfaces.content.IAsyncContainer",
                "guillotina.interfaces.content.ITraversable",
                "guillotina.component.interfaces.ISite",
                "zope.interface.Interface"
            ],
            "method": "DELETE",
            "permission": "guillotina.DeleteContainers",
            "summary": "Delete container",
            "module": "guillotina.api.container.DefaultDELETE"
        }
    },
    "guillotina.interfaces.content.IApplication": {
        "GET": {
            "context": [
                "guillotina.interfaces.content.IApplication",
                "guillotina.interfaces.content.ITraversable",
                "guillotina.interfaces.content.IAsyncContainer",
                "zope.interface.Interface"
            ],
            "method": "GET",
            "permission": "guillotina.AccessContent",
            "summary": "Get application data",
            "description": "Retrieves serialization of application",
            "responses": {
                "200": {
                    "description": "Application data",
                    "schema": {
                        "$ref": "#/definitions/Application"
                    }
                }
            },
            "module": "guillotina.api.app.get"
        },
        "endpoints": {
            "@apidefinition": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IApplication",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.GetContainers",
                    "name": "@apidefinition",
                    "summary": "Get API Definition",
                    "description": "Retrieves information on API configuration",
                    "module": "guillotina.api.app.get_api_definition"
                }
            },
            "@component-subscribers": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IApplication",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "name": "@component-subscribers",
                    "permission": "guillotina.ReadConfiguration",
                    "summary": "Get all registered subscribers",
                    "module": "guillotina.api.app.get_all_subscribers"
                }
            },
            "@login": {
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IApplication",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "permission": "guillotina.Public",
                    "name": "@login",
                    "summary": "Components for a resource",
                    "allow_access": true,
                    "module": "guillotina.api.login.Login"
                }
            },
            "@login-renew": {
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IApplication",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "permission": "guillotina.AccessContent",
                    "name": "@login-renew",
                    "summary": "Refresh to a new token",
                    "module": "guillotina.api.login.Refresh"
                }
            },
            "@storages": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IApplication",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.GetDatabases",
                    "name": "@storages",
                    "module": "guillotina.api.storage.storages_get"
                }
            },
            "@storages/{storage_id}": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IApplication",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.GetDatabases",
                    "name": "@storages/{storage_id}",
                    "module": "guillotina.api.storage.storage_get"
                },
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IApplication",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "permission": "guillotina.MountDatabase",
                    "name": "@storages/{storage_id}",
                    "module": "guillotina.api.storage.storage_create_db"
                }
            },
            "@storages/{storage_id}/{db_id}": {
                "DELETE": {
                    "context": [
                        "guillotina.interfaces.content.IApplication",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "zope.interface.Interface"
                    ],
                    "method": "DELETE",
                    "permission": "guillotina.UmountDatabase",
                    "name": "@storages/{storage_id}/{db_id}",
                    "module": "guillotina.api.storage.delete_db"
                },
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IApplication",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.GetDatabases",
                    "name": "@storages/{storage_id}/{db_id}",
                    "module": "guillotina.api.storage.get_db"
                }
            },
            "@user": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IApplication",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.AccessContent",
                    "name": "@user",
                    "summary": "Get information on the currently logged in user",
                    "responses": {
                        "200": {
                            "description": "Get information on the user",
                            "schema": {
                                "properties": {}
                            }
                        }
                    },
                    "module": "guillotina.api.user.get_user_info"
                }
            },
            "@wstoken": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IApplication",
                        "guillotina.interfaces.content.ITraversable",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.UseWebSockets",
                    "name": "@wstoken",
                    "summary": "Return a web socket token",
                    "responses": {
                        "200": {
                            "description": "The new token",
                            "schema": {
                                "properties": {
                                    "token": {
                                        "type": "string",
                                        "required": true
                                    }
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.ws.WebsocketGetToken"
                }
            }
        },
        "PUT": {
            "context": [
                "guillotina.interfaces.content.IApplication",
                "guillotina.interfaces.content.ITraversable",
                "guillotina.interfaces.content.IAsyncContainer",
                "zope.interface.Interface"
            ],
            "method": "PUT",
            "permission": "guillotina.MountDatabase",
            "ignore": true,
            "module": "guillotina.api.container.NotImplemented"
        }
    },
    "guillotina.interfaces.content.IResource": {
        "endpoints": {
            "@behaviors": {
                "PATCH": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "PATCH",
                    "permission": "guillotina.ModifyContent",
                    "name": "@behaviors",
                    "summary": "Add behavior to resource",
                    "parameters": [
                        {
                            "name": "body",
                            "in": "body",
                            "schema": {
                                "$ref": "#/definitions/Behavior"
                            }
                        }
                    ],
                    "responses": {
                        "200": {
                            "description": "Successfully added behavior"
                        },
                        "412": {
                            "description": "Behavior already assigned here"
                        }
                    },
                    "module": "guillotina.api.behaviors.default_patch"
                },
                "DELETE": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "DELETE",
                    "permission": "guillotina.ModifyContent",
                    "name": "@behaviors",
                    "summary": "Remove behavior from resource",
                    "parameters": [
                        {
                            "name": "body",
                            "in": "body",
                            "schema": {
                                "$ref": "#/definitions/Behavior"
                            }
                        }
                    ],
                    "responses": {
                        "200": {
                            "description": "Successfully removed behavior"
                        },
                        "412": {
                            "description": "Behavior not assigned here"
                        }
                    },
                    "module": "guillotina.api.behaviors.default_delete"
                },
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.AccessContent",
                    "name": "@behaviors",
                    "summary": "Get information on behaviors for this resource",
                    "responses": {
                        "200": {
                            "description": "A listing of behaviors for content",
                            "schema": {
                                "$ref": "#/definitions/BehaviorsResponse"
                            }
                        }
                    },
                    "module": "guillotina.api.behaviors.default_get"
                }
            },
            "@behaviors/{behavior}": {
                "DELETE": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "DELETE",
                    "permission": "guillotina.ModifyContent",
                    "name": "@behaviors/{behavior}",
                    "summary": "Remove behavior from resource",
                    "parameters": [
                        {
                            "name": "behavior",
                            "in": "path",
                            "schema": {
                                "$ref": "#/definitions/Behavior"
                            }
                        }
                    ],
                    "responses": {
                        "200": {
                            "description": "Successfully removed behavior"
                        },
                        "412": {
                            "description": "Behavior not assigned here"
                        }
                    },
                    "module": "guillotina.api.behaviors.default_delete_withparams"
                }
            },
            "@sharing": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.SeePermissions",
                    "name": "@sharing",
                    "summary": "Get sharing settings for this resource",
                    "responses": {
                        "200": {
                            "description": "All the sharing defined on this resource",
                            "schema": {
                                "$ref": "#/definitions/ResourceACL"
                            }
                        }
                    },
                    "module": "guillotina.api.content.sharing_get"
                },
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "permission": "guillotina.ChangePermissions",
                    "name": "@sharing",
                    "summary": "Change permissions for a resource",
                    "parameters": [
                        {
                            "name": "body",
                            "in": "body",
                            "type": "object",
                            "schema": {
                                "$ref": "#/definitions/Permissions"
                            }
                        }
                    ],
                    "responses": {
                        "200": {
                            "description": "Successfully changed permission"
                        }
                    },
                    "module": "guillotina.api.content.SharingPOST"
                },
                "PUT": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "PUT",
                    "permission": "guillotina.ChangePermissions",
                    "name": "@sharing",
                    "summary": "Replace permissions for a resource",
                    "parameters": [
                        {
                            "name": "body",
                            "in": "body",
                            "type": "object",
                            "schema": {
                                "$ref": "#/definitions/Permissions"
                            }
                        }
                    ],
                    "responses": {
                        "200": {
                            "description": "Successfully replaced permissions"
                        }
                    },
                    "module": "guillotina.api.content.SharingPUT"
                }
            },
            "@all_permissions": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.SeePermissions",
                    "name": "@all_permissions",
                    "summary": "See all permission settings for this resource",
                    "responses": {
                        "200": {
                            "description": "All the permissions defined on this resource",
                            "schema": {
                                "$ref": "#/definitions/AllPermissions"
                            }
                        }
                    },
                    "module": "guillotina.api.content.all_permissions"
                }
            },
            "@canido": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.AccessContent",
                    "name": "@canido",
                    "summary": "Check if user has permissions on context",
                    "parameters": [
                        {
                            "name": "permission",
                            "in": "query",
                            "required": true,
                            "type": "string"
                        }
                    ],
                    "responses": {
                        "200": {
                            "description": "Successfully changed permission"
                        }
                    },
                    "module": "guillotina.api.content.can_i_do"
                }
            },
            "@move": {
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "name": "@move",
                    "permission": "guillotina.MoveContent",
                    "summary": "Move resource",
                    "parameters": [
                        {
                            "name": "body",
                            "in": "body",
                            "type": "object",
                            "schema": {
                                "properties": {
                                    "destination": {
                                        "type": "string",
                                        "description": "Absolute path to destination object from container",
                                        "required": false
                                    },
                                    "new_id": {
                                        "type": "string",
                                        "description": "Optional new id to assign object",
                                        "required": false
                                    }
                                }
                            }
                        }
                    ],
                    "responses": {
                        "200": {
                            "description": "Successfully moved resource"
                        }
                    },
                    "module": "guillotina.api.content.move"
                }
            },
            "@duplicate": {
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "name": "@duplicate",
                    "permission": "guillotina.DuplicateContent",
                    "summary": "Duplicate resource",
                    "parameters": [
                        {
                            "name": "body",
                            "in": "body",
                            "type": "object",
                            "schema": {
                                "properties": {
                                    "destination": {
                                        "type": "string",
                                        "description": "Absolute path to destination object from container",
                                        "required": false
                                    },
                                    "new_id": {
                                        "type": "string",
                                        "description": "Optional new id to assign object",
                                        "required": false
                                    }
                                }
                            }
                        }
                    ],
                    "responses": {
                        "200": {
                            "description": "Successfully duplicated object"
                        }
                    },
                    "module": "guillotina.api.content.duplicate"
                }
            },
            "@fieldvalue/{dotted_name}": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.ViewContent",
                    "name": "@fieldvalue/{dotted_name}",
                    "summary": "Get field value",
                    "module": "guillotina.api.content.get_field_value"
                }
            },
            "@dynamic-fields": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "name": "@dynamic-fields",
                    "permission": "guillotina.ModifyContent",
                    "summary": "Get a list of available fields",
                    "module": "guillotina.api.dynamic.available_dynamic_fields"
                }
            },
            "@upload/{field_name}/{filename}": {
                "PATCH": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "PATCH",
                    "permission": "guillotina.ModifyContent",
                    "name": "@upload/{field_name}/{filename}",
                    "traversed_service_definitions": {
                        "{field_name}": {
                            "summary": "Update the content of a file",
                            "parameters": [
                                {
                                    "name": "field_name",
                                    "in": "path",
                                    "description": "Name of file field",
                                    "required": true
                                }
                            ],
                            "responses": {
                                "200": {
                                    "description": "Successfully updated content"
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.files.UploadFile"
                }
            },
            "@upload/{field_name}": {
                "PATCH": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "PATCH",
                    "permission": "guillotina.ModifyContent",
                    "name": "@upload/{field_name}",
                    "traversed_service_definitions": {
                        "{field_name}": {
                            "summary": "Update the content of a file",
                            "parameters": [
                                {
                                    "name": "field_name",
                                    "in": "path",
                                    "description": "Name of file field",
                                    "required": true
                                }
                            ],
                            "responses": {
                                "200": {
                                    "description": "Successfully updated content"
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.files.UploadFile"
                }
            },
            "@download/{field_name}/{filename}": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.ViewContent",
                    "name": "@download/{field_name}/{filename}",
                    "traversed_service_definitions": {
                        "{field_name}": {
                            "summary": "Download the content of a file",
                            "parameters": [
                                {
                                    "name": "field_name",
                                    "in": "path",
                                    "description": "Name of file field",
                                    "required": true
                                }
                            ],
                            "responses": {
                                "200": {
                                    "description": "Successfully updated content"
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.files.DownloadFile"
                },
                "HEAD": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "HEAD",
                    "permission": "guillotina.ViewContent",
                    "name": "@download/{field_name}/{filename}",
                    "module": "guillotina.api.files.HeadFile"
                }
            },
            "@download/{field_name}": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.ViewContent",
                    "name": "@download/{field_name}",
                    "traversed_service_definitions": {
                        "{field_name}": {
                            "summary": "Download the content of a file",
                            "parameters": [
                                {
                                    "name": "field_name",
                                    "in": "path",
                                    "description": "Name of file field",
                                    "required": true
                                }
                            ],
                            "responses": {
                                "200": {
                                    "description": "Successfully updated content"
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.files.DownloadFile"
                },
                "HEAD": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "HEAD",
                    "permission": "guillotina.ViewContent",
                    "name": "@download/{field_name}",
                    "module": "guillotina.api.files.HeadFile"
                }
            },
            "@tusupload/{field_name}/{filename}": {
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "permission": "guillotina.ModifyContent",
                    "name": "@tusupload/{field_name}/{filename}",
                    "traversed_service_definitions": {
                        "{field_name}": {
                            "summary": "TUS endpoint",
                            "parameters": [
                                {
                                    "name": "field_name",
                                    "in": "path",
                                    "description": "Name of file field",
                                    "required": true
                                },
                                {
                                    "name": "Upload-Offset",
                                    "in": "headers",
                                    "type": "integer",
                                    "required": true
                                },
                                {
                                    "name": "UPLOAD-LENGTH",
                                    "in": "headers",
                                    "type": "integer",
                                    "required": true
                                },
                                {
                                    "name": "UPLOAD-MD5",
                                    "in": "headers",
                                    "type": "string",
                                    "required": false
                                },
                                {
                                    "name": "UPLOAD-EXTENSION",
                                    "in": "headers",
                                    "type": "string",
                                    "required": false
                                },
                                {
                                    "name": "TUS-RESUMABLE",
                                    "in": "headers",
                                    "type": "string",
                                    "required": true
                                },
                                {
                                    "name": "UPLOAD-METADATA",
                                    "in": "headers",
                                    "type": "string",
                                    "required": false
                                }
                            ],
                            "responses": {
                                "204": {
                                    "description": "Successfully patched data",
                                    "headers": {
                                        "Location": {
                                            "type": "string"
                                        },
                                        "Tus-Resumable": {
                                            "type": "string"
                                        },
                                        "Access-Control-Expose-Headers": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.files.TusCreateFile"
                },
                "HEAD": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "HEAD",
                    "permission": "guillotina.ModifyContent",
                    "name": "@tusupload/{field_name}/{filename}",
                    "traversed_service_definitions": {
                        "{field_name}": {
                            "summary": "TUS endpoint",
                            "parameters": [
                                {
                                    "name": "field_name",
                                    "in": "path",
                                    "description": "Name of file field",
                                    "required": true
                                }
                            ],
                            "responses": {
                                "200": {
                                    "description": "Successfully patched data",
                                    "headers": {
                                        "Upload-Offset": {
                                            "type": "integer"
                                        },
                                        "Tus-Resumable": {
                                            "type": "string"
                                        },
                                        "Access-Control-Expose-Headers": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.files.TusHeadFile"
                },
                "PATCH": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "PATCH",
                    "permission": "guillotina.ModifyContent",
                    "name": "@tusupload/{field_name}/{filename}",
                    "traversed_service_definitions": {
                        "{field_name}": {
                            "summary": "TUS endpoint",
                            "parameters": [
                                {
                                    "name": "field_name",
                                    "in": "path",
                                    "description": "Name of file field",
                                    "required": true
                                },
                                {
                                    "name": "Upload-Offset",
                                    "in": "headers",
                                    "type": "integer",
                                    "required": true
                                },
                                {
                                    "name": "CONTENT-LENGTH",
                                    "in": "headers",
                                    "type": "integer",
                                    "required": true
                                }
                            ],
                            "responses": {
                                "204": {
                                    "description": "Successfully patched data",
                                    "headers": {
                                        "Upload-Offset": {
                                            "type": "integer"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.files.TusPatchFile"
                },
                "OPTIONS": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "OPTIONS",
                    "permission": "guillotina.AccessPreflight",
                    "name": "@tusupload/{field_name}/{filename}",
                    "traversed_service_definitions": {
                        "{field_name}": {
                            "summary": "TUS endpoint",
                            "parameters": [
                                {
                                    "name": "field_name",
                                    "in": "path",
                                    "description": "Name of file field",
                                    "required": true
                                }
                            ],
                            "responses": {
                                "200": {
                                    "description": "Successfully returned tus info",
                                    "headers": {
                                        "Tus-Version": {
                                            "type": "string"
                                        },
                                        "Tus-Resumable": {
                                            "type": "string"
                                        },
                                        "Tus-Max-Size": {
                                            "type": "integer"
                                        },
                                        "Tus-Extension": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.files.TusOptionsFile"
                }
            },
            "@tusupload/{field_name}": {
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "permission": "guillotina.ModifyContent",
                    "name": "@tusupload/{field_name}",
                    "traversed_service_definitions": {
                        "{field_name}": {
                            "summary": "TUS endpoint",
                            "parameters": [
                                {
                                    "name": "field_name",
                                    "in": "path",
                                    "description": "Name of file field",
                                    "required": true
                                },
                                {
                                    "name": "Upload-Offset",
                                    "in": "headers",
                                    "type": "integer",
                                    "required": true
                                },
                                {
                                    "name": "UPLOAD-LENGTH",
                                    "in": "headers",
                                    "type": "integer",
                                    "required": true
                                },
                                {
                                    "name": "UPLOAD-MD5",
                                    "in": "headers",
                                    "type": "string",
                                    "required": false
                                },
                                {
                                    "name": "UPLOAD-EXTENSION",
                                    "in": "headers",
                                    "type": "string",
                                    "required": false
                                },
                                {
                                    "name": "TUS-RESUMABLE",
                                    "in": "headers",
                                    "type": "string",
                                    "required": true
                                },
                                {
                                    "name": "UPLOAD-METADATA",
                                    "in": "headers",
                                    "type": "string",
                                    "required": false
                                }
                            ],
                            "responses": {
                                "204": {
                                    "description": "Successfully patched data",
                                    "headers": {
                                        "Location": {
                                            "type": "string"
                                        },
                                        "Tus-Resumable": {
                                            "type": "string"
                                        },
                                        "Access-Control-Expose-Headers": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.files.TusCreateFile"
                },
                "HEAD": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "HEAD",
                    "permission": "guillotina.ModifyContent",
                    "name": "@tusupload/{field_name}",
                    "traversed_service_definitions": {
                        "{field_name}": {
                            "summary": "TUS endpoint",
                            "parameters": [
                                {
                                    "name": "field_name",
                                    "in": "path",
                                    "description": "Name of file field",
                                    "required": true
                                }
                            ],
                            "responses": {
                                "200": {
                                    "description": "Successfully patched data",
                                    "headers": {
                                        "Upload-Offset": {
                                            "type": "integer"
                                        },
                                        "Tus-Resumable": {
                                            "type": "string"
                                        },
                                        "Access-Control-Expose-Headers": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.files.TusHeadFile"
                },
                "PATCH": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "PATCH",
                    "permission": "guillotina.ModifyContent",
                    "name": "@tusupload/{field_name}",
                    "traversed_service_definitions": {
                        "{field_name}": {
                            "summary": "TUS endpoint",
                            "parameters": [
                                {
                                    "name": "field_name",
                                    "in": "path",
                                    "description": "Name of file field",
                                    "required": true
                                },
                                {
                                    "name": "Upload-Offset",
                                    "in": "headers",
                                    "type": "integer",
                                    "required": true
                                },
                                {
                                    "name": "CONTENT-LENGTH",
                                    "in": "headers",
                                    "type": "integer",
                                    "required": true
                                }
                            ],
                            "responses": {
                                "204": {
                                    "description": "Successfully patched data",
                                    "headers": {
                                        "Upload-Offset": {
                                            "type": "integer"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.files.TusPatchFile"
                },
                "OPTIONS": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "OPTIONS",
                    "permission": "guillotina.AccessPreflight",
                    "name": "@tusupload/{field_name}",
                    "traversed_service_definitions": {
                        "{field_name}": {
                            "summary": "TUS endpoint",
                            "parameters": [
                                {
                                    "name": "field_name",
                                    "in": "path",
                                    "description": "Name of file field",
                                    "required": true
                                }
                            ],
                            "responses": {
                                "200": {
                                    "description": "Successfully returned tus info",
                                    "headers": {
                                        "Tus-Version": {
                                            "type": "string"
                                        },
                                        "Tus-Resumable": {
                                            "type": "string"
                                        },
                                        "Tus-Max-Size": {
                                            "type": "integer"
                                        },
                                        "Tus-Extension": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "module": "guillotina.api.files.TusOptionsFile"
                }
            },
            "@search": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "permission": "guillotina.SearchContent",
                    "name": "@search",
                    "summary": "Make search request",
                    "parameters": [
                        {
                            "name": "q",
                            "in": "query",
                            "required": true,
                            "type": "string"
                        }
                    ],
                    "responses": {
                        "200": {
                            "description": "Search results",
                            "type": "object",
                            "schema": {
                                "$ref": "#/definitions/SearchResults"
                            }
                        }
                    },
                    "module": "guillotina.api.search.search_get"
                },
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "permission": "guillotina.RawSearchContent",
                    "name": "@search",
                    "summary": "Make a complex search query",
                    "parameters": [
                        {
                            "name": "body",
                            "in": "body",
                            "schema": {
                                "properties": {}
                            }
                        }
                    ],
                    "responses": {
                        "200": {
                            "description": "Search results",
                            "type": "object",
                            "schema": {
                                "$ref": "#/definitions/SearchResults"
                            }
                        }
                    },
                    "module": "guillotina.api.search.search_post"
                }
            },
            "@catalog-reindex": {
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "permission": "guillotina.ReindexContent",
                    "name": "@catalog-reindex",
                    "summary": "Reindex entire container content",
                    "responses": {
                        "200": {
                            "description": "Successfully reindexed content"
                        }
                    },
                    "module": "guillotina.api.search.CatalogReindex"
                }
            },
            "@async-catalog-reindex": {
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "permission": "guillotina.ReindexContent",
                    "name": "@async-catalog-reindex",
                    "summary": "Asynchronously reindex entire container content",
                    "responses": {
                        "200": {
                            "description": "Successfully initiated reindexing"
                        }
                    },
                    "module": "guillotina.api.search.AsyncCatalogReindex"
                }
            },
            "@catalog": {
                "POST": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "POST",
                    "permission": "guillotina.ManageCatalog",
                    "name": "@catalog",
                    "summary": "Initialize catalog",
                    "responses": {
                        "200": {
                            "description": "Successfully initialized catalog"
                        }
                    },
                    "module": "guillotina.api.search.catalog_post"
                },
                "DELETE": {
                    "context": [
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "zope.interface.Interface"
                    ],
                    "method": "DELETE",
                    "permission": "guillotina.ManageCatalog",
                    "name": "@catalog",
                    "summary": "Delete search catalog",
                    "responses": {
                        "200": {
                            "description": "Successfully deleted catalog"
                        }
                    },
                    "module": "guillotina.api.search.catalog_delete"
                }
            }
        },
        "HEAD": {
            "context": [
                "guillotina.interfaces.content.IResource",
                "guillotina.interfaces.content.ILocation",
                "zope.interface.Interface"
            ],
            "method": "HEAD",
            "permission": "guillotina.ViewContent",
            "module": "guillotina.api.content.default_head"
        },
        "GET": {
            "context": [
                "guillotina.interfaces.content.IResource",
                "guillotina.interfaces.content.ILocation",
                "zope.interface.Interface"
            ],
            "method": "GET",
            "permission": "guillotina.ViewContent",
            "summary": "Retrieves serialization of resource",
            "responses": "guillotina.api.content.get_content_json_schema_responses",
            "parameters": [
                {
                    "name": "include",
                    "in": "query",
                    "type": "string",
                    "description": ""
                },
                {
                    "name": "omit",
                    "in": "query",
                    "type": "string",
                    "description": ""
                }
            ],
            "module": "guillotina.api.content.DefaultGET"
        },
        "POST": {
            "context": [
                "guillotina.interfaces.content.IResource",
                "guillotina.interfaces.content.ILocation",
                "zope.interface.Interface"
            ],
            "method": "POST",
            "permission": "guillotina.AddContent",
            "summary": "Add new resouce inside this container resource",
            "parameters": [
                {
                    "name": "body",
                    "in": "body",
                    "schema": {
                        "$ref": "#/definitions/AddableResource"
                    }
                }
            ],
            "responses": {
                "200": {
                    "description": "Resource data",
                    "schema": {
                        "$ref": "#/definitions/ResourceFolder"
                    }
                }
            },
            "module": "guillotina.api.content.DefaultPOST"
        },
        "PATCH": {
            "context": [
                "guillotina.interfaces.content.IResource",
                "guillotina.interfaces.content.ILocation",
                "zope.interface.Interface"
            ],
            "method": "PATCH",
            "permission": "guillotina.ModifyContent",
            "summary": "Modify the content of this resource",
            "parameters": "guillotina.api.content.patch_content_json_schema_parameters",
            "responses": {
                "200": {
                    "description": "Resource data",
                    "schema": {
                        "$ref": "#/definitions/Resource"
                    }
                }
            },
            "module": "guillotina.api.content.DefaultPATCH"
        },
        "PUT": {
            "context": [
                "guillotina.interfaces.content.IResource",
                "guillotina.interfaces.content.ILocation",
                "zope.interface.Interface"
            ],
            "method": "PUT",
            "permission": "guillotina.ModifyContent",
            "summary": "Replace the content of this resource",
            "parameters": "guillotina.api.content.patch_content_json_schema_parameters",
            "responses": {
                "200": {
                    "description": "Resource data",
                    "schema": {
                        "$ref": "#/definitions/Resource"
                    }
                }
            },
            "module": "guillotina.api.content.DefaultPUT"
        },
        "DELETE": {
            "context": [
                "guillotina.interfaces.content.IResource",
                "guillotina.interfaces.content.ILocation",
                "zope.interface.Interface"
            ],
            "method": "DELETE",
            "permission": "guillotina.DeleteContent",
            "summary": "Delete resource",
            "responses": {
                "200": {
                    "description": "Successfully deleted resource"
                }
            },
            "module": "guillotina.api.content.DefaultDELETE"
        },
        "OPTIONS": {
            "context": [
                "guillotina.interfaces.content.IResource",
                "guillotina.interfaces.content.ILocation",
                "zope.interface.Interface"
            ],
            "method": "OPTIONS",
            "permission": "guillotina.AccessPreflight",
            "summary": "Get CORS information for resource",
            "module": "guillotina.api.content.DefaultOPTIONS"
        }
    },
    "guillotina.interfaces.content.IFolder": {
        "endpoints": {
            "@ids": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IFolder",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "name": "@ids",
                    "permission": "guillotina.Manage",
                    "summary": "Return a list of ids in the resource",
                    "responses": {
                        "200": {
                            "description": "Successfully returned list of ids"
                        }
                    },
                    "module": "guillotina.api.content.ids"
                }
            },
            "@items": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IFolder",
                        "guillotina.interfaces.content.IResource",
                        "guillotina.interfaces.content.ILocation",
                        "guillotina.interfaces.content.IAsyncContainer",
                        "guillotina.interfaces.content.ITraversable",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "name": "@items",
                    "permission": "guillotina.Manage",
                    "summary": "Paginated list of sub objects",
                    "parameters": [
                        {
                            "name": "include",
                            "in": "query",
                            "type": "string"
                        },
                        {
                            "name": "omit",
                            "in": "query",
                            "type": "string"
                        },
                        {
                            "name": "page_size",
                            "in": "query",
                            "type": "number",
                            "default": 20
                        },
                        {
                            "name": "page",
                            "in": "query",
                            "type": "number",
                            "default": 1
                        }
                    ],
                    "responses": {
                        "200": {
                            "description": "Successfully returned response object"
                        }
                    },
                    "module": "guillotina.api.content.items"
                }
            }
        }
    },
    "guillotina.interfaces.content.IAsyncContainer": {
        "endpoints": {
            "@addable-types": {
                "GET": {
                    "context": [
                        "guillotina.interfaces.content.IAsyncContainer",
                        "zope.interface.Interface"
                    ],
                    "method": "GET",
                    "name": "@addable-types",
                    "permission": "guillotina.AddContent",
                    "summary": "Return a list of type names that can be added to container",
                    "responses": {
                        "200": {
                            "description": "Successfully returned list of type names"
                        }
                    },
                    "module": "guillotina.api.content.addable_types"
                }
            }
        }
    },
    "zope.interface.Interface": {
        "endpoints": {
            "@invalidate-cache": {
                "GET": {
                    "method": "GET",
                    "name": "@invalidate-cache",
                    "permission": "guillotina.ModifyContent",
                    "summary": "Invalidate cache of object",
                    "responses": {
                        "200": {
                            "description": "Successfully invalidated"
                        }
                    },
                    "module": "guillotina.api.content.invalidate_cache"
                }
            }
        }
    },
    "guillotina.interfaces.content.IDatabase": {
        "GET": {
            "context": [
                "guillotina.interfaces.content.IDatabase",
                "guillotina.interfaces.content.ITraversable",
                "guillotina.interfaces.content.IAsyncContainer",
                "zope.interface.Interface"
            ],
            "method": "GET",
            "permission": "guillotina.GetContainers",
            "summary": "Get list of containers",
            "responses": {
                "200": {
                    "description": "Get a list of containers",
                    "schema": {
                        "properties": {
                            "containers": {
                                "type": "array",
                                "items": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                }
            },
            "module": "guillotina.api.container.DefaultGET"
        },
        "POST": {
            "context": [
                "guillotina.interfaces.content.IDatabase",
                "guillotina.interfaces.content.ITraversable",
                "guillotina.interfaces.content.IAsyncContainer",
                "zope.interface.Interface"
            ],
            "method": "POST",
            "permission": "guillotina.AddContainer",
            "summary": "Create a new Container",
            "description": "Creates a new container on the database",
            "parameters": [
                {
                    "name": "body",
                    "in": "body",
                    "schema": {
                        "$ref": "#/definitions/BaseResource",
                        "properties": {
                            "@addons": {
                                "type": "string"
                            }
                        }
                    }
                }
            ],
            "responses": {
                "200": {
                    "description": "Container result",
                    "schema": {
                        "$ref": "#/definitions/BaseResource"
                    }
                }
            },
            "module": "guillotina.api.container.DefaultPOST"
        },
        "DELETE": {
            "context": [
                "guillotina.interfaces.content.IDatabase",
                "guillotina.interfaces.content.ITraversable",
                "guillotina.interfaces.content.IAsyncContainer",
                "zope.interface.Interface"
            ],
            "method": "DELETE",
            "permission": "guillotina.UmountDatabase",
            "ignore": true,
            "module": "guillotina.api.container.NotImplemented"
        }
    },
    "guillotina.interfaces.content.IStaticFile": {
        "GET": {
            "context": [
                "guillotina.interfaces.content.IStaticFile",
                "zope.interface.Interface"
            ],
            "method": "GET",
            "permission": "guillotina.AccessContent",
            "module": "guillotina.api.files.FileGET"
        }
    },
    "guillotina.interfaces.content.IStaticDirectory": {
        "GET": {
            "context": [
                "guillotina.interfaces.content.IStaticDirectory",
                "guillotina.interfaces.content.ITraversable",
                "zope.interface.Interface"
            ],
            "method": "GET",
            "permission": "guillotina.AccessContent",
            "module": "guillotina.api.files.DirectoryGET"
        }
    }
}
Status Codes:

GET GET

Get all registered subscribers

  • Permission: guillotina.ReadConfiguration
  • Context: guillotina.interfaces.content.IApplication

http

GET /@component-subscribers HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/@component-subscribers -H 'Accept: application/json' --user root:root

httpie

http http://nohost/@component-subscribers Accept:application/json -a root:root

response

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

{
    "guillotina.interfaces.content.IResource": {
        "guillotina.interfaces.events.IObjectPermissionsModifiedEvent": [
            "guillotina.catalog.index.security_changed"
        ],
        "guillotina.interfaces.events.IObjectMovedEvent": [
            "guillotina.catalog.index.moved_object"
        ],
        "guillotina.interfaces.events.IObjectRemovedEvent": [
            "guillotina.catalog.index.remove_object"
        ],
        "guillotina.interfaces.events.IObjectModifiedEvent": [
            "guillotina.catalog.index.add_object",
            "guillotina.subscribers.modified_object"
        ],
        "guillotina.interfaces.events.IObjectAddedEvent": [
            "guillotina.catalog.index.add_object"
        ]
    },
    "guillotina.interfaces.content.IContainer": {
        "guillotina.interfaces.events.IObjectAddedEvent": [
            "guillotina.catalog.index.initialize_catalog"
        ],
        "guillotina.interfaces.events.IObjectRemovedEvent": [
            "guillotina.catalog.index.remove_catalog"
        ]
    }
}
Status Codes:

Database

POST /(db)

Create a new Container

Creates a new container on the database

  • Permission: guillotina.AddContainer
  • Context: guillotina.interfaces.content.IDatabase

http

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

{
    "@type": "Container",
    "id": "container"
}

curl

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

httpie

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

response

HTTP/1.1 200 OK
Content-Length: 63
Content-Type: application/json
Location: /db/container

{
    "@type": "Container",
    "id": "container",
    "title": "container"
}
Status Codes:

GET /(db)

Get list of containers

  • Permission: guillotina.GetContainers
  • Context: guillotina.interfaces.content.IDatabase

http

GET /db HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db Accept:application/json -a root:root

response

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

{
    "containers": [
        "container"
    ],
    "@type": "Database"
}
Status Codes:

Container

GET /(db)/(container)

Retrieves serialization of resource

  • Permission: guillotina.ViewContent
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container Accept:application/json -a root:root

response

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

{
    "@id": "http://127.0.0.1:60361/db/container",
    "@type": "Container",
    "@name": "container",
    "@uid": "380cfe6d1baf4d1a99f7d29a9440fb51",
    "@static_behaviors": [],
    "parent": {},
    "is_folderish": true,
    "creation_date": "2019-08-24T12:41:32.767383+00:00",
    "modification_date": "2019-08-24T12:41:32.767383+00:00",
    "UID": "380cfe6d1baf4d1a99f7d29a9440fb51",
    "type_name": "Container",
    "title": "container",
    "uuid": "380cfe6d1baf4d1a99f7d29a9440fb51",
    "__behaviors__": [],
    "__name__": "container",
    "items": [],
    "length": 0
}
Query Parameters:
 
  • include (string) –
  • omit (string) –
Status Codes:

Types

GET /(db)/(container)/@types

Read information on available types

  • Permission: guillotina.AccessContent
  • Context: guillotina.interfaces.content.IContainer

http

GET /db/container/@types HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/@types -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/@types Accept:application/json -a root:root

response

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

[
    {
        "title": "Item",
        "$schema": "http://json-schema.org/draft-04/schema#",
        "type": "object",
        "required": [
            "type_name",
            "uuid"
        ],
        "definitions": {
            "guillotina.behaviors.dublincore.IDublinCore": {
                "type": "object",
                "properties": {
                    "title": {
                        "type": "string",
                        "description": "The first unqualified Dublin Core 'Title' element value.",
                        "title": "Title"
                    },
                    "description": {
                        "type": "string",
                        "description": "The first unqualified Dublin Core 'Description' element value.",
                        "title": "Description"
                    },
                    "creation_date": {
                        "type": "datetime",
                        "description": "The date and time that an object is created. \nThis is normally set automatically.",
                        "title": "Creation Date"
                    },
                    "modification_date": {
                        "type": "datetime",
                        "description": "The date and time that the object was last modified in a\nmeaningful way.",
                        "title": "Modification Date"
                    },
                    "effective_date": {
                        "type": "datetime",
                        "description": "The date and time that an object should be published. ",
                        "title": "Effective Date"
                    },
                    "expiration_date": {
                        "type": "datetime",
                        "description": "The date and time that the object should become unpublished.",
                        "title": "Expiration Date"
                    },
                    "creators": {
                        "type": "array",
                        "description": "The unqualified Dublin Core 'Creator' element values",
                        "title": "Creators",
                        "items": {
                            "type": "string"
                        }
                    },
                    "tags": {
                        "type": "array",
                        "description": "The unqualified Dublin Core 'Tags' element values",
                        "title": "Tags",
                        "items": {
                            "type": "string"
                        }
                    },
                    "publisher": {
                        "type": "string",
                        "description": "The first unqualified Dublin Core 'Publisher' element value.",
                        "title": "Publisher"
                    },
                    "contributors": {
                        "type": "array",
                        "description": "The unqualified Dublin Core 'Contributor' element values",
                        "title": "Contributors",
                        "items": {
                            "type": "string"
                        }
                    }
                },
                "required": [],
                "invariants": [],
                "title": "Dublin Core fields",
                "description": ""
            }
        },
        "properties": {
            "__name__": {
                "type": "string",
                "description": "The object can be looked up from the parent's sublocations using this name.",
                "readonly": true,
                "title": "The name within the parent"
            },
            "type_name": {
                "type": "string",
                "readonly": true
            },
            "title": {
                "type": "string",
                "description": "Title of the Resource",
                "title": "Title"
            },
            "uuid": {
                "type": "string",
                "readonly": true,
                "title": "UUID"
            },
            "modification_date": {
                "type": "datetime",
                "title": "Modification date"
            },
            "creation_date": {
                "type": "datetime",
                "title": "Creation date"
            },
            "__behaviors__": {
                "type": "array",
                "description": "Dynamic behaviors for the content type",
                "readonly": true,
                "title": "Enabled behaviors"
            },
            "guillotina.behaviors.dublincore.IDublinCore": [
                {
                    "$ref": "#/definitions/guillotina.behaviors.dublincore.IDublinCore"
                }
            ]
        },
        "invariants": []
    },
    {
        "title": "Folder",
        "$schema": "http://json-schema.org/draft-04/schema#",
        "type": "object",
        "required": [
            "type_name",
            "uuid"
        ],
        "definitions": {
            "guillotina.behaviors.dublincore.IDublinCore": {
                "type": "object",
                "properties": {
                    "title": {
                        "type": "string",
                        "description": "The first unqualified Dublin Core 'Title' element value.",
                        "title": "Title"
                    },
                    "description": {
                        "type": "string",
                        "description": "The first unqualified Dublin Core 'Description' element value.",
                        "title": "Description"
                    },
                    "creation_date": {
                        "type": "datetime",
                        "description": "The date and time that an object is created. \nThis is normally set automatically.",
                        "title": "Creation Date"
                    },
                    "modification_date": {
                        "type": "datetime",
                        "description": "The date and time that the object was last modified in a\nmeaningful way.",
                        "title": "Modification Date"
                    },
                    "effective_date": {
                        "type": "datetime",
                        "description": "The date and time that an object should be published. ",
                        "title": "Effective Date"
                    },
                    "expiration_date": {
                        "type": "datetime",
                        "description": "The date and time that the object should become unpublished.",
                        "title": "Expiration Date"
                    },
                    "creators": {
                        "type": "array",
                        "description": "The unqualified Dublin Core 'Creator' element values",
                        "title": "Creators",
                        "items": {
                            "type": "string"
                        }
                    },
                    "tags": {
                        "type": "array",
                        "description": "The unqualified Dublin Core 'Tags' element values",
                        "title": "Tags",
                        "items": {
                            "type": "string"
                        }
                    },
                    "publisher": {
                        "type": "string",
                        "description": "The first unqualified Dublin Core 'Publisher' element value.",
                        "title": "Publisher"
                    },
                    "contributors": {
                        "type": "array",
                        "description": "The unqualified Dublin Core 'Contributor' element values",
                        "title": "Contributors",
                        "items": {
                            "type": "string"
                        }
                    }
                },
                "required": [],
                "invariants": [],
                "title": "Dublin Core fields",
                "description": ""
            }
        },
        "properties": {
            "__name__": {
                "type": "string",
                "description": "The object can be looked up from the parent's sublocations using this name.",
                "readonly": true,
                "title": "The name within the parent"
            },
            "type_name": {
                "type": "string",
                "readonly": true
            },
            "title": {
                "type": "string",
                "description": "Title of the Resource",
                "title": "Title"
            },
            "uuid": {
                "type": "string",
                "readonly": true,
                "title": "UUID"
            },
            "modification_date": {
                "type": "datetime",
                "title": "Modification date"
            },
            "creation_date": {
                "type": "datetime",
                "title": "Creation date"
            },
            "__behaviors__": {
                "type": "array",
                "description": "Dynamic behaviors for the content type",
                "readonly": true,
                "title": "Enabled behaviors"
            },
            "guillotina.behaviors.dublincore.IDublinCore": [
                {
                    "$ref": "#/definitions/guillotina.behaviors.dublincore.IDublinCore"
                }
            ]
        },
        "invariants": []
    },
    {
        "title": "Container",
        "$schema": "http://json-schema.org/draft-04/schema#",
        "type": "object",
        "required": [
            "type_name",
            "uuid"
        ],
        "definitions": {},
        "properties": {
            "__name__": {
                "type": "string",
                "description": "The object can be looked up from the parent's sublocations using this name.",
                "readonly": true,
                "title": "The name within the parent"
            },
            "type_name": {
                "type": "string",
                "readonly": true
            },
            "title": {
                "type": "string",
                "description": "Title of the Resource",
                "title": "Title"
            },
            "uuid": {
                "type": "string",
                "readonly": true,
                "title": "UUID"
            },
            "modification_date": {
                "type": "datetime",
                "title": "Modification date"
            },
            "creation_date": {
                "type": "datetime",
                "title": "Creation date"
            },
            "__behaviors__": {
                "type": "array",
                "description": "Dynamic behaviors for the content type",
                "readonly": true,
                "title": "Enabled behaviors"
            }
        },
        "invariants": []
    }
]
Status Codes:

GET /(db)/(container)/@types/(type_name)

Read information on available types

  • Permission: guillotina.AccessContent
  • Context: guillotina.interfaces.content.IContainer

http

GET /db/container/@types/Item HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/@types/Item -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/@types/Item Accept:application/json -a root:root

response

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

{
    "title": "Item",
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "required": [
        "type_name",
        "uuid"
    ],
    "definitions": {
        "guillotina.behaviors.dublincore.IDublinCore": {
            "type": "object",
            "properties": {
                "title": {
                    "type": "string",
                    "description": "The first unqualified Dublin Core 'Title' element value.",
                    "title": "Title"
                },
                "description": {
                    "type": "string",
                    "description": "The first unqualified Dublin Core 'Description' element value.",
                    "title": "Description"
                },
                "creation_date": {
                    "type": "datetime",
                    "description": "The date and time that an object is created. \nThis is normally set automatically.",
                    "title": "Creation Date"
                },
                "modification_date": {
                    "type": "datetime",
                    "description": "The date and time that the object was last modified in a\nmeaningful way.",
                    "title": "Modification Date"
                },
                "effective_date": {
                    "type": "datetime",
                    "description": "The date and time that an object should be published. ",
                    "title": "Effective Date"
                },
                "expiration_date": {
                    "type": "datetime",
                    "description": "The date and time that the object should become unpublished.",
                    "title": "Expiration Date"
                },
                "creators": {
                    "type": "array",
                    "description": "The unqualified Dublin Core 'Creator' element values",
                    "title": "Creators",
                    "items": {
                        "type": "string"
                    }
                },
                "tags": {
                    "type": "array",
                    "description": "The unqualified Dublin Core 'Tags' element values",
                    "title": "Tags",
                    "items": {
                        "type": "string"
                    }
                },
                "publisher": {
                    "type": "string",
                    "description": "The first unqualified Dublin Core 'Publisher' element value.",
                    "title": "Publisher"
                },
                "contributors": {
                    "type": "array",
                    "description": "The unqualified Dublin Core 'Contributor' element values",
                    "title": "Contributors",
                    "items": {
                        "type": "string"
                    }
                }
            },
            "required": [],
            "invariants": [],
            "title": "Dublin Core fields",
            "description": ""
        }
    },
    "properties": {
        "__name__": {
            "type": "string",
            "description": "The object can be looked up from the parent's sublocations using this name.",
            "readonly": true,
            "title": "The name within the parent"
        },
        "type_name": {
            "type": "string",
            "readonly": true
        },
        "title": {
            "type": "string",
            "description": "Title of the Resource",
            "title": "Title"
        },
        "uuid": {
            "type": "string",
            "readonly": true,
            "title": "UUID"
        },
        "modification_date": {
            "type": "datetime",
            "title": "Modification date"
        },
        "creation_date": {
            "type": "datetime",
            "title": "Creation date"
        },
        "__behaviors__": {
            "type": "array",
            "description": "Dynamic behaviors for the content type",
            "readonly": true,
            "title": "Enabled behaviors"
        },
        "guillotina.behaviors.dublincore.IDublinCore": [
            {
                "$ref": "#/definitions/guillotina.behaviors.dublincore.IDublinCore"
            }
        ]
    },
    "invariants": []
}
Status Codes:

User

GET /(db)/(container)/@user

Get information on the currently logged in user

  • Permission: guillotina.AccessContent
  • Context: guillotina.interfaces.content.IContainer

http

GET /db/container/@user HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/@user -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/@user Accept:application/json -a root:root

response

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

{
    "root": {
        "roles": {
            "guillotina.Authenticated": 1
        },
        "groups": [
            "Managers"
        ],
        "permissions": {},
        "properties": {}
    },
    "groups": {
        "Managers": {
            "roles": {
                "guillotina.ContainerAdmin": 1,
                "guillotina.ContainerDeleter": 1,
                "guillotina.Owner": 1,
                "guillotina.Member": 1,
                "guillotina.Manager": 1
            },
            "groups": []
        }
    }
}
Status Codes:

Registry

GET /(db)/(container)/@registry

Read container registry settings

  • Permission: guillotina.ReadConfiguration
  • Context: guillotina.interfaces.content.IContainer

http

GET /db/container/@registry HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/@registry -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/@registry Accept:application/json -a root:root

response

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

{
    "value": {
        "guillotina.interfaces.registry.ILayers.active_layers": [],
        "guillotina.interfaces.registry.IAddons.enabled": []
    }
}
Status Codes:

POST /(db)/(container)/@registry

Register a new interface to for registry settings

  • Permission: guillotina.RegisterConfigurations
  • Context: guillotina.interfaces.content.IContainer

http

POST /db/container/@registry HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

{
    "interface": "guillotina.documentation.IRegistryData"
}

curl

curl -i -X POST http://nohost/db/container/@registry -H 'Accept: application/json' --data-raw '{
    "interface": "guillotina.documentation.IRegistryData"
}' --user root:root

httpie

echo '{
    "interface": "guillotina.documentation.IRegistryData"
}' | http POST http://nohost/db/container/@registry Accept:application/json -a root:root

response

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

{}
Status Codes:

PATCH /(db)/(container)/@registry/(key)

Update registry setting

  • Permission: guillotina.WriteConfiguration
  • Context: guillotina.interfaces.content.IContainer

http

PATCH /db/container/@registry/guillotina.documentation.IRegistryData.foobar HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

{
    "value": "something"
}

curl

curl -i -X PATCH http://nohost/db/container/@registry/guillotina.documentation.IRegistryData.foobar -H 'Accept: application/json' --data-raw '{
    "value": "something"
}' --user root:root

httpie

echo '{
    "value": "something"
}' | http PATCH http://nohost/db/container/@registry/guillotina.documentation.IRegistryData.foobar Accept:application/json -a root:root

response

HTTP/1.1 204 OK
Content-Length: 0
Content-Type: application/json
Query Parameters:
 
  • name (string) –
  • in (string) –
  • type (string) –
  • schema (string) –
Status Codes:

GET /(db)/(container)/@registry/(key)

Read container registry settings

  • Permission: guillotina.ReadConfiguration
  • Context: guillotina.interfaces.content.IContainer

http

GET /db/container/@registry/guillotina.documentation.IRegistryData.foobar HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/@registry/guillotina.documentation.IRegistryData.foobar -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/@registry/guillotina.documentation.IRegistryData.foobar Accept:application/json -a root:root

response

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

{
    "value": "something"
}
Status Codes:

Addons

GET /(db)/(container)/@addons

List available addons

  • Permission: guillotina.ManageAddons
  • Context: guillotina.interfaces.content.IContainer

http

GET /db/container/@addons HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/@addons -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/@addons Accept:application/json -a root:root

response

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

{
    "available": [
        {
            "id": "docaddon",
            "title": "Doc addon",
            "dependencies": []
        }
    ],
    "installed": []
}
Status Codes:

POST /(db)/(container)/@addons

Install addon to container

  • Permission: guillotina.ManageAddons
  • Context: guillotina.interfaces.content.IContainer

http

POST /db/container/@addons HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

{
    "id": "docaddon"
}

curl

curl -i -X POST http://nohost/db/container/@addons -H 'Accept: application/json' --data-raw '{
    "id": "docaddon"
}' --user root:root

httpie

echo '{
    "id": "docaddon"
}' | http POST http://nohost/db/container/@addons Accept:application/json -a root:root

response

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

{
    "available": [
        {
            "id": "docaddon",
            "title": "Doc addon",
            "dependencies": []
        }
    ],
    "installed": [
        "docaddon"
    ]
}
Status Codes:

DELETE /(db)/(container)/@addons

Uninstall an addon from container

  • Permission: guillotina.ManageAddons
  • Context: guillotina.interfaces.content.IContainer

http

DELETE /db/container/@addons HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

{
    "id": "docaddon"
}

curl

curl -i -X DELETE http://nohost/db/container/@addons -H 'Accept: application/json' --data-raw '{
    "id": "docaddon"
}' --user root:root

httpie

echo '{
    "id": "docaddon"
}' | http DELETE http://nohost/db/container/@addons Accept:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

Dynamic Fields

Dynamic fields are done with the IDynamicFields behavior so first we add the behavior.

PATCH /(db)/(container)/@behaviors

Add behavior to resource

  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

PATCH /db/container/@behaviors HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

{
    "behavior": "guillotina.behaviors.dynamic.IDynamicFields"
}

curl

curl -i -X PATCH http://nohost/db/container/@behaviors -H 'Accept: application/json' --data-raw '{
    "behavior": "guillotina.behaviors.dynamic.IDynamicFields"
}' --user root:root

httpie

echo '{
    "behavior": "guillotina.behaviors.dynamic.IDynamicFields"
}' | http PATCH http://nohost/db/container/@behaviors Accept:application/json -a root:root

response

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

{}
Status Codes:

Then, we can add a field.

PATCH /(db)/(container)

Modify the content of this resource

  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

PATCH /db/container HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

{
    "guillotina.behaviors.dynamic.IDynamicFields": {
        "fields": {
            "foobar": {
                "title": "Hello field",
                "type": "text"
            }
        }
    }
}

curl

curl -i -X PATCH http://nohost/db/container -H 'Accept: application/json' --data-raw '{
    "guillotina.behaviors.dynamic.IDynamicFields": {
        "fields": {
            "foobar": {
                "title": "Hello field",
                "type": "text"
            }
        }
    }
}' --user root:root

httpie

echo '{
    "guillotina.behaviors.dynamic.IDynamicFields": {
        "fields": {
            "foobar": {
                "title": "Hello field",
                "type": "text"
            }
        }
    }
}' | http PATCH http://nohost/db/container Accept:application/json -a root:root

response

HTTP/1.1 204 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

To inspect the dynamic fields available on content

GET /(db)/(container)/@dynamic-fields

Get a list of available fields

  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container/@dynamic-fields HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/@dynamic-fields -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/@dynamic-fields Accept:application/json -a root:root

response

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

{
    "foobar": {
        "title": "Hello field",
        "description": null,
        "type": "text",
        "required": false,
        "meta": {}
    }
}
Status Codes:

Update dynamic field values

POST /(db)/(container)/(id)

Add new resouce inside this container resource

  • Permission: guillotina.AddContent
  • Context: guillotina.interfaces.content.IResource

http

POST /db/container HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

{
    "@type": "Item",
    "id": "foobar-fields",
    "@behaviors": [
        "guillotina.behaviors.dynamic.IDynamicFieldValues"
    ]
}

curl

curl -i -X POST http://nohost/db/container -H 'Accept: application/json' --data-raw '{
    "@type": "Item",
    "id": "foobar-fields",
    "@behaviors": [
        "guillotina.behaviors.dynamic.IDynamicFieldValues"
    ]
}' --user root:root

httpie

echo '{
    "@type": "Item",
    "id": "foobar-fields",
    "@behaviors": [
        "guillotina.behaviors.dynamic.IDynamicFieldValues"
    ]
}' | http POST http://nohost/db/container Accept:application/json -a root:root

response

HTTP/1.1 201 OK
Content-Length: 198
Content-Type: application/json
Location: http://127.0.0.1:60361/db/container/foobar-fields

{
    "@id": "http://127.0.0.1:60361/db/container/foobar-fields",
    "@name": "foobar-fields",
    "@type": "Item",
    "@uid": "380|bc2d502801d44383b5d6e9f6fa5cdd41",
    "UID": "380|bc2d502801d44383b5d6e9f6fa5cdd41"
}
Status Codes:

PATCH /(db)/(container)/(id)

Modify the content of this resource

  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

PATCH /db/container/foobar-fields HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

{
    "guillotina.behaviors.dynamic.IDynamicFieldValues": {
        "values": {
            "op": "update",
            "value": [
                {
                    "key": "foobar",
                    "value": "value"
                }
            ]
        }
    }
}

curl

curl -i -X PATCH http://nohost/db/container/foobar-fields -H 'Accept: application/json' --data-raw '{
    "guillotina.behaviors.dynamic.IDynamicFieldValues": {
        "values": {
            "op": "update",
            "value": [
                {
                    "key": "foobar",
                    "value": "value"
                }
            ]
        }
    }
}' --user root:root

httpie

echo '{
    "guillotina.behaviors.dynamic.IDynamicFieldValues": {
        "values": {
            "op": "update",
            "value": [
                {
                    "key": "foobar",
                    "value": "value"
                }
            ]
        }
    }
}' | http PATCH http://nohost/db/container/foobar-fields Accept:application/json -a root:root

response

HTTP/1.1 204 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

GET /(db)/(container)/(id)

Retrieves serialization of resource

  • Permission: guillotina.ViewContent
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container/foobar-fields?include=guillotina.behaviors.dynamic.IDynamicFieldValues HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i 'http://nohost/db/container/foobar-fields?include=guillotina.behaviors.dynamic.IDynamicFieldValues' -H 'Accept: application/json' --user root:root

httpie

http 'http://nohost/db/container/foobar-fields?include=guillotina.behaviors.dynamic.IDynamicFieldValues' Accept:application/json -a root:root

response

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

{
    "@id": "http://127.0.0.1:60361/db/container/foobar-fields",
    "@type": "Item",
    "@name": "foobar-fields",
    "@uid": "380|bc2d502801d44383b5d6e9f6fa5cdd41",
    "@static_behaviors": [
        "guillotina.behaviors.dublincore.IDublinCore"
    ],
    "parent": {
        "@id": "http://127.0.0.1:60361/db/container",
        "@name": "container",
        "@type": "Container",
        "@uid": "380cfe6d1baf4d1a99f7d29a9440fb51",
        "UID": "380cfe6d1baf4d1a99f7d29a9440fb51"
    },
    "is_folderish": false,
    "creation_date": "2019-08-24T12:41:34.046927+00:00",
    "modification_date": "2019-08-24T12:41:34.046927+00:00",
    "UID": "380|bc2d502801d44383b5d6e9f6fa5cdd41",
    "guillotina.behaviors.dynamic.IDynamicFieldValues": {
        "values": {
            "foobar": "value"
        }
    }
}
Query Parameters:
 
  • include (string) –
  • omit (string) –
Status Codes:

Item

POST /(db)/(container)

Add new resouce inside this container resource

  • Permission: guillotina.AddContent
  • Context: guillotina.interfaces.content.IResource

http

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

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

curl

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

httpie

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

response

HTTP/1.1 201 OK
Content-Length: 184
Content-Type: application/json
Location: http://127.0.0.1:60361/db/container/foobar

{
    "@id": "http://127.0.0.1:60361/db/container/foobar",
    "@name": "foobar",
    "@type": "Item",
    "@uid": "b2c|22f1d8a853094ec0bd97ccdd1e64c9b1",
    "UID": "b2c|22f1d8a853094ec0bd97ccdd1e64c9b1"
}
Status Codes:

PATCH /(db)/(container)/(id)

Modify the content of this resource

  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

PATCH /db/container/foobar HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json

{
    "title": "foobar"
}

curl

curl -i -X PATCH http://nohost/db/container/foobar -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"title": "foobar"}' --user root:root

httpie

echo '{
  "title": "foobar"
}' | http PATCH http://nohost/db/container/foobar Accept:application/json Content-Type:application/json -a root:root

response

HTTP/1.1 204 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

GET /(db)/(container)/(id)

Retrieves serialization of resource

  • Permission: guillotina.ViewContent
  • Context: guillotina.interfaces.content.IResource

http

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

curl

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

httpie

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

response

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

{
    "@id": "http://127.0.0.1:60361/db/container/foobar",
    "@type": "Item",
    "@name": "foobar",
    "@uid": "b2c|22f1d8a853094ec0bd97ccdd1e64c9b1",
    "@static_behaviors": [
        "guillotina.behaviors.dublincore.IDublinCore"
    ],
    "parent": {
        "@id": "http://127.0.0.1:60361/db/container",
        "@name": "container",
        "@type": "Container",
        "@uid": "b2c5ad1a66954773879f02c5d41ccf0d",
        "UID": "b2c5ad1a66954773879f02c5d41ccf0d"
    },
    "is_folderish": false,
    "creation_date": "2019-08-24T12:41:34.831634+00:00",
    "modification_date": "2019-08-24T12:41:34.846572+00:00",
    "UID": "b2c|22f1d8a853094ec0bd97ccdd1e64c9b1",
    "type_name": "Item",
    "title": "foobar",
    "uuid": "b2c|22f1d8a853094ec0bd97ccdd1e64c9b1",
    "__behaviors__": [],
    "__name__": "foobar",
    "guillotina.behaviors.dublincore.IDublinCore": {
        "title": "foobar",
        "description": null,
        "creation_date": "2019-08-24T12:41:34.831634+00:00",
        "modification_date": "2019-08-24T12:41:34.846572+00:00",
        "effective_date": null,
        "expiration_date": null,
        "creators": [
            "root"
        ],
        "tags": null,
        "publisher": null,
        "contributors": [
            "root"
        ]
    }
}
Query Parameters:
 
  • include (string) –
  • omit (string) –
Status Codes:

DELETE /(db)/(container)/(id)

Delete resource

  • Permission: guillotina.DeleteContent
  • Context: guillotina.interfaces.content.IResource

http

DELETE /db/container/foobar HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i -X DELETE http://nohost/db/container/foobar -H 'Accept: application/json' --user root:root

httpie

http DELETE http://nohost/db/container/foobar Accept:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

Behaviors

GET /(db)/(container)/(id)/@behaviors

Get information on behaviors for this resource

  • Permission: guillotina.AccessContent
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container/foobar/@behaviors HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/foobar/@behaviors -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/foobar/@behaviors Accept:application/json -a root:root

response

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

{
    "static": [
        "guillotina.behaviors.dublincore.IDublinCore"
    ],
    "dynamic": [],
    "available": [
        "guillotina.behaviors.attachment.IAttachment",
        "guillotina.behaviors.attachment.IMultiAttachment",
        "guillotina.behaviors.dynamic.IDynamicFields",
        "guillotina.behaviors.dynamic.IDynamicFieldValues"
    ],
    "guillotina.behaviors.attachment.IAttachment": {
        "type": "object",
        "properties": {
            "file": {
                "type": "object",
                "properties": {
                    "type": "object",
                    "properties": {
                        "content_type": {
                            "type": "string",
                            "description": "The content type identifies the type of data.",
                            "title": "Content Type"
                        },
                        "filename": {
                            "type": "string",
                            "title": "Filename"
                        },
                        "extension": {
                            "type": "string",
                            "title": "Extension of the file"
                        },
                        "md5": {
                            "type": "string",
                            "title": "MD5"
                        },
                        "size": {
                            "type": "integer",
                            "title": "Size"
                        }
                    },
                    "required": [
                        "size"
                    ],
                    "invariants": []
                }
            }
        },
        "required": [
            "file"
        ],
        "invariants": []
    },
    "guillotina.behaviors.attachment.IMultiAttachment": {
        "type": "object",
        "properties": {
            "files": {
                "type": "object",
                "additionalProperties": {
                    "type": "object",
                    "properties": {
                        "type": "object",
                        "properties": {
                            "content_type": {
                                "type": "string",
                                "description": "The content type identifies the type of data.",
                                "title": "Content Type"
                            },
                            "filename": {
                                "type": "string",
                                "title": "Filename"
                            },
                            "extension": {
                                "type": "string",
                                "title": "Extension of the file"
                            },
                            "md5": {
                                "type": "string",
                                "title": "MD5"
                            },
                            "size": {
                                "type": "integer",
                                "title": "Size"
                            }
                        },
                        "required": [
                            "size"
                        ],
                        "invariants": []
                    }
                }
            }
        },
        "required": [
            "files"
        ],
        "invariants": []
    },
    "guillotina.behaviors.dublincore.IDublinCore": {
        "type": "object",
        "properties": {
            "title": {
                "type": "string",
                "description": "The first unqualified Dublin Core 'Title' element value.",
                "title": "Title"
            },
            "description": {
                "type": "string",
                "description": "The first unqualified Dublin Core 'Description' element value.",
                "title": "Description"
            },
            "creation_date": {
                "type": "datetime",
                "description": "The date and time that an object is created. \nThis is normally set automatically.",
                "title": "Creation Date"
            },
            "modification_date": {
                "type": "datetime",
                "description": "The date and time that the object was last modified in a\nmeaningful way.",
                "title": "Modification Date"
            },
            "effective_date": {
                "type": "datetime",
                "description": "The date and time that an object should be published. ",
                "title": "Effective Date"
            },
            "expiration_date": {
                "type": "datetime",
                "description": "The date and time that the object should become unpublished.",
                "title": "Expiration Date"
            },
            "creators": {
                "type": "array",
                "description": "The unqualified Dublin Core 'Creator' element values",
                "title": "Creators",
                "items": {
                    "type": "string"
                }
            },
            "tags": {
                "type": "array",
                "description": "The unqualified Dublin Core 'Tags' element values",
                "title": "Tags",
                "items": {
                    "type": "string"
                }
            },
            "publisher": {
                "type": "string",
                "description": "The first unqualified Dublin Core 'Publisher' element value.",
                "title": "Publisher"
            },
            "contributors": {
                "type": "array",
                "description": "The unqualified Dublin Core 'Contributor' element values",
                "title": "Contributors",
                "items": {
                    "type": "string"
                }
            }
        },
        "required": [],
        "invariants": []
    },
    "guillotina.behaviors.dynamic.IDynamicFields": {
        "type": "object",
        "properties": {
            "fields": {
                "type": "object",
                "additionalProperties": {
                    "type": "object",
                    "properties": {
                        "type": "object",
                        "properties": {
                            "title": {
                                "type": "string"
                            },
                            "description": {
                                "type": "string"
                            },
                            "type": {
                                "type": "string",
                                "vocabulary": [
                                    "date",
                                    "integer",
                                    "text",
                                    "float",
                                    "keyword",
                                    "boolean"
                                ]
                            },
                            "required": {
                                "type": "boolean"
                            },
                            "meta": {
                                "type": "object",
                                "title": "Additional information on field",
                                "properties": {}
                            }
                        },
                        "required": [
                            "type"
                        ],
                        "invariants": []
                    }
                }
            }
        },
        "required": [
            "fields"
        ],
        "invariants": []
    },
    "guillotina.behaviors.dynamic.IDynamicFieldValues": {
        "type": "object",
        "properties": {
            "values": {
                "type": "object",
                "additionalProperties": true
            }
        },
        "required": [
            "values"
        ],
        "invariants": []
    }
}
Status Codes:

PATCH /(db)/(container)/(id)/@behaviors

Add behavior to resource

  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

PATCH /db/container/foobar/@behaviors HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json

{
    "behavior": "guillotina.behaviors.attachment.IAttachment"
}

curl

curl -i -X PATCH http://nohost/db/container/foobar/@behaviors -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"behavior": "guillotina.behaviors.attachment.IAttachment"}' --user root:root

httpie

echo '{
  "behavior": "guillotina.behaviors.attachment.IAttachment"
}' | http PATCH http://nohost/db/container/foobar/@behaviors Accept:application/json Content-Type:application/json -a root:root

response

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

{}
Status Codes:

DELETE /(db)/(container)/(id)/@behaviors

Remove behavior from resource

  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

DELETE /db/container/foobar/@behaviors HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json

{
    "behavior": "guillotina.behaviors.attachment.IAttachment"
}

curl

curl -i -X DELETE http://nohost/db/container/foobar/@behaviors -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"behavior": "guillotina.behaviors.attachment.IAttachment"}' --user root:root

httpie

echo '{
  "behavior": "guillotina.behaviors.attachment.IAttachment"
}' | http DELETE http://nohost/db/container/foobar/@behaviors Accept:application/json Content-Type:application/json -a root:root

response

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

{}
Status Codes:

Files

First, add the IAttachment behavior.

We have simple @upload and @download endpoints

PATCH /(db)/(container)/(id)/@upload/(field_name)
  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

PATCH /db/container/foobar/@upload/file HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

foobar data

curl

curl -i -X PATCH http://nohost/db/container/foobar/@upload/file -H 'Accept: application/json' --data-raw 'foobar data' --user root:root

httpie

echo 'foobar data' | http PATCH http://nohost/db/container/foobar/@upload/file Accept:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

GET /(db)/(container)/(id)/@download/(field_name)
  • Permission: guillotina.ViewContent
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container/foobar/@download/file HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/foobar/@download/file -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/foobar/@download/file Accept:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Disposition: attachment; filename="bf7ad2451cc94adc8343c1d95d26fc74"
Content-Length: 11
Content-Type: text/plain

foobar data
Status Codes:

But we also support TUS.

POST /(db)/(container)/(content)/@tusupload/file
  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

POST /db/container/foobar/@tusupload/file HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
TUS-RESUMABLE: 1
UPLOAD-LENGTH: 22

curl

curl -i -X POST http://nohost/db/container/foobar/@tusupload/file -H 'Accept: application/json' -H 'Tus-Resumable: 1' -H 'Upload-Length: 22' --user root:root

httpie

http POST http://nohost/db/container/foobar/@tusupload/file Accept:application/json Tus-Resumable:1 Upload-Length:22 -a root:root

response

HTTP/1.1 201 OK
Content-Length: 2
Content-Type: application/json
Location: http://127.0.0.1:60361/db/container/foobar/@tusupload/file
Tus-Resumable: 1.0.0

{}
Status Codes:

PATCH /(db)/(container)/(content)/@tusupload/file
  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

PATCH /db/container/foobar/@tusupload/file HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
TUS-RESUMABLE: 1
Upload-Offset: 0

<text data>

curl

curl -i -X PATCH http://nohost/db/container/foobar/@tusupload/file -H 'Accept: application/json' -H 'Tus-Resumable: 1' -H 'Upload-Offset: 0' --data-raw '<text data>' --user root:root

httpie

echo '<text data>' | http PATCH http://nohost/db/container/foobar/@tusupload/file Accept:application/json Tus-Resumable:1 Upload-Offset:0 -a root:root

response

HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json
Tus-Resumable: 1.0.0
Upload-Offset: 11

{}
Status Codes:

PATCH /(db)/(container)/(content)/@tusupload/file
  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

PATCH /db/container/foobar/@tusupload/file HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
TUS-RESUMABLE: 1
Upload-Offset: 11

<text data>

curl

curl -i -X PATCH http://nohost/db/container/foobar/@tusupload/file -H 'Accept: application/json' -H 'Tus-Resumable: 1' -H 'Upload-Offset: 11' --data-raw '<text data>' --user root:root

httpie

echo '<text data>' | http PATCH http://nohost/db/container/foobar/@tusupload/file Accept:application/json Tus-Resumable:1 Upload-Offset:11 -a root:root

response

HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json
Tus-Resumable: 1.0.0
Tus-Upload-Finished: 1
Upload-Offset: 22

{}
Status Codes:

Download again, see what we have.

GET /(db)/(container)/(id)/@download/(field_name)
  • Permission: guillotina.ViewContent
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container/foobar/@download/file HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/foobar/@download/file -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/foobar/@download/file Accept:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Disposition: attachment; filename="891ec2d641ab4cde85bd2c7ce0281be8"
Content-Length: 22
Content-Type: application/octet-stream

<text data><text data>
Status Codes:

Security

GET /(db)/(container)/(id)/@all_permissions

See all permission settings for this resource

  • Permission: guillotina.SeePermissions
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container/foobar/@all_permissions HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/foobar/@all_permissions -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/foobar/@all_permissions Accept:application/json -a root:root

response

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

[
    {
        "foobar": {
            "prinperm": [],
            "prinrole": [
                {
                    "principal": "root",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                }
            ],
            "roleperm": [],
            "perminhe": []
        }
    },
    {
        "container": {
            "prinperm": [],
            "prinrole": [
                {
                    "principal": "root",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                }
            ],
            "roleperm": [],
            "perminhe": []
        }
    },
    {
        "(no name)": {
            "prinperm": [
                {
                    "principal": "root",
                    "permission": "guillotina.AccessContent",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "permission": "guillotina.AddContainer",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "permission": "guillotina.DeleteContainers",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "permission": "guillotina.GetAPIDefinition",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "permission": "guillotina.GetContainers",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "permission": "guillotina.GetDatabases",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "permission": "guillotina.MountDatabase",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "permission": "guillotina.UmountDatabase",
                    "setting": "Allow"
                }
            ],
            "perminhe": []
        }
    },
    {
        "system": {
            "prinperm": [],
            "prinrole": [],
            "roleperm": [
                {
                    "permission": "guillotina.AccessPreflight",
                    "role": "guillotina.Anonymous",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.AccessPreflight",
                    "role": "guillotina.Authenticated",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.Public",
                    "role": "guillotina.Anonymous",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.Public",
                    "role": "guillotina.Authenticated",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.UseWebSockets",
                    "role": "guillotina.Authenticated",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ViewContent",
                    "role": "guillotina.Reader",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ViewContent",
                    "role": "guillotina.Reviewer",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ViewContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ViewContent",
                    "role": "guillotina.Editor",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.AccessContent",
                    "role": "guillotina.Reader",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.AccessContent",
                    "role": "guillotina.Reviewer",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.AccessContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.AccessContent",
                    "role": "guillotina.Editor",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.AccessContent",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.DuplicateContent",
                    "role": "guillotina.Reader",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.DuplicateContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.DuplicateContent",
                    "role": "guillotina.Editor",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.DeleteContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.AddContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.MoveContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.MoveContent",
                    "role": "guillotina.Editor",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ModifyContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ModifyContent",
                    "role": "guillotina.Editor",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ChangePermissions",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.SeePermissions",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ReindexContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ReindexContent",
                    "role": "guillotina.Editor",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ManageAddons",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ReadConfiguration",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.WriteConfiguration",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.RegisterConfigurations",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ManageCatalog",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.RawSearchContent",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.Manage",
                    "role": "guillotina.Manager",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.DeleteContainers",
                    "role": "guillotina.ContainerDeleter",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.SearchContent",
                    "role": "guillotina.Member",
                    "setting": "Allow"
                }
            ]
        }
    }
]
Status Codes:

GET /(db)/(container)/(id)/@canido

Check if user has permissions on context

  • Permission: guillotina.AccessContent
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container/foobar/@canido?permissions=guillotina.ModifyContent,guillotina.AccessContent HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i 'http://nohost/db/container/foobar/@canido?permissions=guillotina.ModifyContent,guillotina.AccessContent' -H 'Accept: application/json' --user root:root

httpie

http 'http://nohost/db/container/foobar/@canido?permissions=guillotina.ModifyContent,guillotina.AccessContent' Accept:application/json -a root:root

response

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

{
    "guillotina.ModifyContent": true,
    "guillotina.AccessContent": true
}
Query Parameters:
 
  • permission (string) – (required) (required)
Status Codes:

GET /(db)/(container)/(id)/@sharing

Get sharing settings for this resource

  • Permission: guillotina.SeePermissions
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container/foobar/@sharing HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/foobar/@sharing -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/foobar/@sharing Accept:application/json -a root:root

response

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

{
    "local": {
        "roleperm": {},
        "prinperm": {},
        "prinrole": {
            "root": {
                "guillotina.Owner": "Allow"
            }
        }
    },
    "inherit": [
        {
            "@id": "http://127.0.0.1:60361/db/container",
            "roleperm": {},
            "prinperm": {},
            "prinrole": {
                "root": {
                    "guillotina.ContainerAdmin": "Allow",
                    "guillotina.Owner": "Allow"
                }
            }
        }
    ]
}
Status Codes:

POST /(db)/(container)/(id)/@sharing

Change permissions for a resource

  • Permission: guillotina.ChangePermissions
  • Context: guillotina.interfaces.content.IResource

http

POST /db/container/foobar/@sharing HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json

{
    "prinrole": [
        {
            "principal": "foobar",
            "role": "guillotina.Owner",
            "setting": "Allow"
        }
    ]
}

curl

curl -i -X POST http://nohost/db/container/foobar/@sharing -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"prinrole": [{"principal": "foobar", "role": "guillotina.Owner", "setting": "Allow"}]}' --user root:root

httpie

echo '{
  "prinrole": [
    {
      "principal": "foobar",
      "role": "guillotina.Owner",
      "setting": "Allow"
    }
  ]
}' | http POST http://nohost/db/container/foobar/@sharing Accept:application/json Content-Type:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

PUT /(db)/(container)/(id)/@sharing

Replace permissions for a resource

  • Permission: guillotina.ChangePermissions
  • Context: guillotina.interfaces.content.IResource

http

PUT /db/container/foobar/@sharing HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json

{
    "prinrole": [
        {
            "principal": "foobar",
            "role": "guillotina.Owner",
            "setting": "Allow"
        }
    ]
}

curl

curl -i -X PUT http://nohost/db/container/foobar/@sharing -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"prinrole": [{"principal": "foobar", "role": "guillotina.Owner", "setting": "Allow"}]}' --user root:root

httpie

echo '{
  "prinrole": [
    {
      "principal": "foobar",
      "role": "guillotina.Owner",
      "setting": "Allow"
    }
  ]
}' | http PUT http://nohost/db/container/foobar/@sharing Accept:application/json Content-Type:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

Content

POST /(db)/(container)/(id)/@move

Move resource

  • Permission: guillotina.MoveContent
  • Context: guillotina.interfaces.content.IResource

http

POST /db/container/foobar/@move HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json

{
    "destination": "",
    "new_id": "foobar2"
}

curl

curl -i -X POST http://nohost/db/container/foobar/@move -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"destination": "", "new_id": "foobar2"}' --user root:root

httpie

echo '{
  "destination": "",
  "new_id": "foobar2"
}' | http POST http://nohost/db/container/foobar/@move Accept:application/json Content-Type:application/json -a root:root

response

HTTP/1.1 412 OK
Content-Length: 258
Content-Type: application/json

{
    "reason": "preconditionFailed",
    "details": "",
    "type": "PreconditionFailed",
    "eid": "15d9310aca5a42e59476ff75b0cf4b31",
    "message": "Precondition Failed Destination already has object with the id foobar2 on < Item at /container/foobar2 by 139846608495432 >"
}
Status Codes:

POST /(db)/(container)/(id)/@duplicate

Duplicate resource

  • Permission: guillotina.DuplicateContent
  • Context: guillotina.interfaces.content.IResource

http

POST /db/container/foobar2/@duplicate HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json

{
    "destination": "",
    "new_id": "foobar3"
}

curl

curl -i -X POST http://nohost/db/container/foobar2/@duplicate -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"destination": "", "new_id": "foobar3"}' --user root:root

httpie

echo '{
  "destination": "",
  "new_id": "foobar3"
}' | http POST http://nohost/db/container/foobar2/@duplicate Accept:application/json Content-Type:application/json -a root:root

response

HTTP/1.1 412 OK
Content-Length: 260
Content-Type: application/json

{
    "reason": "preconditionFailed",
    "details": "",
    "type": "PreconditionFailed",
    "eid": "06afebe131d44d13a8758f5dd333cf80",
    "message": "Precondition Failed Destination already has object with the id foobar3 on < Folder at /container/foobar2 by 139846683139912 >"
}
Status Codes:

GET /(db)/(container)/(id)/@invalidate-cache

Invalidate cache of object

  • Permission: guillotina.ModifyContent
  • Context: zope.interface.Interface

http

GET /db/container/foobar2/@invalidate-cache HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/foobar2/@invalidate-cache -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/foobar2/@invalidate-cache Accept:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

Folder

POST /(db)/(container)

Add new resouce inside this container resource

  • Permission: guillotina.AddContent
  • Context: guillotina.interfaces.content.IResource

http

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

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

curl

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

httpie

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

response

HTTP/1.1 201 OK
Content-Length: 186
Content-Type: application/json
Location: http://127.0.0.1:60361/db/container/foobar

{
    "@id": "http://127.0.0.1:60361/db/container/foobar",
    "@name": "foobar",
    "@type": "Folder",
    "@uid": "b2c|ef46b1d50f4c4995a95e2fc66db21f2e",
    "UID": "b2c|ef46b1d50f4c4995a95e2fc66db21f2e"
}
Status Codes:

PATCH /(db)/(container)/(id)

Modify the content of this resource

  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

PATCH /db/container/foobar HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json

{
    "title": "foobar"
}

curl

curl -i -X PATCH http://nohost/db/container/foobar -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"title": "foobar"}' --user root:root

httpie

echo '{
  "title": "foobar"
}' | http PATCH http://nohost/db/container/foobar Accept:application/json Content-Type:application/json -a root:root

response

HTTP/1.1 204 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

GET /(db)/(container)/(id)

Retrieves serialization of resource

  • Permission: guillotina.ViewContent
  • Context: guillotina.interfaces.content.IResource

http

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

curl

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

httpie

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

response

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

{
    "@id": "http://127.0.0.1:60361/db/container/foobar",
    "@type": "Folder",
    "@name": "foobar",
    "@uid": "b2c|ef46b1d50f4c4995a95e2fc66db21f2e",
    "@static_behaviors": [
        "guillotina.behaviors.dublincore.IDublinCore"
    ],
    "parent": {
        "@id": "http://127.0.0.1:60361/db/container",
        "@name": "container",
        "@type": "Container",
        "@uid": "b2c5ad1a66954773879f02c5d41ccf0d",
        "UID": "b2c5ad1a66954773879f02c5d41ccf0d"
    },
    "is_folderish": true,
    "creation_date": "2019-08-24T12:41:34.296948+00:00",
    "modification_date": "2019-08-24T12:41:34.312385+00:00",
    "UID": "b2c|ef46b1d50f4c4995a95e2fc66db21f2e",
    "type_name": "Folder",
    "title": "foobar",
    "uuid": "b2c|ef46b1d50f4c4995a95e2fc66db21f2e",
    "__behaviors__": [],
    "__name__": "foobar",
    "guillotina.behaviors.dublincore.IDublinCore": {
        "title": "foobar",
        "description": null,
        "creation_date": "2019-08-24T12:41:34.296948+00:00",
        "modification_date": "2019-08-24T12:41:34.312385+00:00",
        "effective_date": null,
        "expiration_date": null,
        "creators": [
            "root"
        ],
        "tags": null,
        "publisher": null,
        "contributors": [
            "root"
        ]
    },
    "items": [],
    "length": 0
}
Query Parameters:
 
  • include (string) –
  • omit (string) –
Status Codes:

DELETE /(db)/(container)/(id)

Delete resource

  • Permission: guillotina.DeleteContent
  • Context: guillotina.interfaces.content.IResource

http

DELETE /db/container/foobar HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i -X DELETE http://nohost/db/container/foobar -H 'Accept: application/json' --user root:root

httpie

http DELETE http://nohost/db/container/foobar Accept:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

Behaviors

GET /(db)/(container)/(id)/@behaviors

Get information on behaviors for this resource

  • Permission: guillotina.AccessContent
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container/foobar/@behaviors HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/foobar/@behaviors -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/foobar/@behaviors Accept:application/json -a root:root

response

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

{
    "static": [
        "guillotina.behaviors.dublincore.IDublinCore"
    ],
    "dynamic": [],
    "available": [
        "guillotina.behaviors.attachment.IAttachment",
        "guillotina.behaviors.attachment.IMultiAttachment",
        "guillotina.behaviors.dynamic.IDynamicFields",
        "guillotina.behaviors.dynamic.IDynamicFieldValues"
    ],
    "guillotina.behaviors.attachment.IAttachment": {
        "type": "object",
        "properties": {
            "file": {
                "type": "object",
                "properties": {
                    "type": "object",
                    "properties": {
                        "content_type": {
                            "type": "string",
                            "description": "The content type identifies the type of data.",
                            "title": "Content Type"
                        },
                        "filename": {
                            "type": "string",
                            "title": "Filename"
                        },
                        "extension": {
                            "type": "string",
                            "title": "Extension of the file"
                        },
                        "md5": {
                            "type": "string",
                            "title": "MD5"
                        },
                        "size": {
                            "type": "integer",
                            "title": "Size"
                        }
                    },
                    "required": [
                        "size"
                    ],
                    "invariants": []
                }
            }
        },
        "required": [
            "file"
        ],
        "invariants": []
    },
    "guillotina.behaviors.attachment.IMultiAttachment": {
        "type": "object",
        "properties": {
            "files": {
                "type": "object",
                "additionalProperties": {
                    "type": "object",
                    "properties": {
                        "type": "object",
                        "properties": {
                            "content_type": {
                                "type": "string",
                                "description": "The content type identifies the type of data.",
                                "title": "Content Type"
                            },
                            "filename": {
                                "type": "string",
                                "title": "Filename"
                            },
                            "extension": {
                                "type": "string",
                                "title": "Extension of the file"
                            },
                            "md5": {
                                "type": "string",
                                "title": "MD5"
                            },
                            "size": {
                                "type": "integer",
                                "title": "Size"
                            }
                        },
                        "required": [
                            "size"
                        ],
                        "invariants": []
                    }
                }
            }
        },
        "required": [
            "files"
        ],
        "invariants": []
    },
    "guillotina.behaviors.dublincore.IDublinCore": {
        "type": "object",
        "properties": {
            "title": {
                "type": "string",
                "description": "The first unqualified Dublin Core 'Title' element value.",
                "title": "Title"
            },
            "description": {
                "type": "string",
                "description": "The first unqualified Dublin Core 'Description' element value.",
                "title": "Description"
            },
            "creation_date": {
                "type": "datetime",
                "description": "The date and time that an object is created. \nThis is normally set automatically.",
                "title": "Creation Date"
            },
            "modification_date": {
                "type": "datetime",
                "description": "The date and time that the object was last modified in a\nmeaningful way.",
                "title": "Modification Date"
            },
            "effective_date": {
                "type": "datetime",
                "description": "The date and time that an object should be published. ",
                "title": "Effective Date"
            },
            "expiration_date": {
                "type": "datetime",
                "description": "The date and time that the object should become unpublished.",
                "title": "Expiration Date"
            },
            "creators": {
                "type": "array",
                "description": "The unqualified Dublin Core 'Creator' element values",
                "title": "Creators",
                "items": {
                    "type": "string"
                }
            },
            "tags": {
                "type": "array",
                "description": "The unqualified Dublin Core 'Tags' element values",
                "title": "Tags",
                "items": {
                    "type": "string"
                }
            },
            "publisher": {
                "type": "string",
                "description": "The first unqualified Dublin Core 'Publisher' element value.",
                "title": "Publisher"
            },
            "contributors": {
                "type": "array",
                "description": "The unqualified Dublin Core 'Contributor' element values",
                "title": "Contributors",
                "items": {
                    "type": "string"
                }
            }
        },
        "required": [],
        "invariants": []
    },
    "guillotina.behaviors.dynamic.IDynamicFields": {
        "type": "object",
        "properties": {
            "fields": {
                "type": "object",
                "additionalProperties": {
                    "type": "object",
                    "properties": {
                        "type": "object",
                        "properties": {
                            "title": {
                                "type": "string"
                            },
                            "description": {
                                "type": "string"
                            },
                            "type": {
                                "type": "string",
                                "vocabulary": [
                                    "date",
                                    "integer",
                                    "text",
                                    "float",
                                    "keyword",
                                    "boolean"
                                ]
                            },
                            "required": {
                                "type": "boolean"
                            },
                            "meta": {
                                "type": "object",
                                "title": "Additional information on field",
                                "properties": {}
                            }
                        },
                        "required": [
                            "type"
                        ],
                        "invariants": []
                    }
                }
            }
        },
        "required": [
            "fields"
        ],
        "invariants": []
    },
    "guillotina.behaviors.dynamic.IDynamicFieldValues": {
        "type": "object",
        "properties": {
            "values": {
                "type": "object",
                "additionalProperties": true
            }
        },
        "required": [
            "values"
        ],
        "invariants": []
    }
}
Status Codes:

PATCH /(db)/(container)/(id)/@behaviors

Add behavior to resource

  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

PATCH /db/container/foobar/@behaviors HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json

{
    "behavior": "guillotina.behaviors.attachment.IAttachment"
}

curl

curl -i -X PATCH http://nohost/db/container/foobar/@behaviors -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"behavior": "guillotina.behaviors.attachment.IAttachment"}' --user root:root

httpie

echo '{
  "behavior": "guillotina.behaviors.attachment.IAttachment"
}' | http PATCH http://nohost/db/container/foobar/@behaviors Accept:application/json Content-Type:application/json -a root:root

response

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

{}
Status Codes:

DELETE /(db)/(container)/(id)/@behaviors

Remove behavior from resource

  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

DELETE /db/container/foobar/@behaviors HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json

{
    "behavior": "guillotina.behaviors.attachment.IAttachment"
}

curl

curl -i -X DELETE http://nohost/db/container/foobar/@behaviors -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"behavior": "guillotina.behaviors.attachment.IAttachment"}' --user root:root

httpie

echo '{
  "behavior": "guillotina.behaviors.attachment.IAttachment"
}' | http DELETE http://nohost/db/container/foobar/@behaviors Accept:application/json Content-Type:application/json -a root:root

response

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

{}
Status Codes:

Files

PATCH /(db)/(container)/(id)/@upload/(field_name)
  • Permission: guillotina.ModifyContent
  • Context: guillotina.interfaces.content.IResource

http

PATCH /db/container/foobar/@upload/file HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

foobar data

curl

curl -i -X PATCH http://nohost/db/container/foobar/@upload/file -H 'Accept: application/json' --data-raw 'foobar data' --user root:root

httpie

echo 'foobar data' | http PATCH http://nohost/db/container/foobar/@upload/file Accept:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

GET /(db)/(container)/(id)/@download/(field_name)
  • Permission: guillotina.ViewContent
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container/foobar/@download/file HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/foobar/@download/file -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/foobar/@download/file Accept:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Disposition: attachment; filename="be5a3e66dbb5409985b77c8eb50d6d62"
Content-Length: 11
Content-Type: text/plain

foobar data
Status Codes:

Security

GET /(db)/(container)/(id)/@all_permissions

See all permission settings for this resource

  • Permission: guillotina.SeePermissions
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container/foobar/@all_permissions HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/foobar/@all_permissions -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/foobar/@all_permissions Accept:application/json -a root:root

response

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

[
    {
        "foobar": {
            "prinperm": [],
            "prinrole": [
                {
                    "principal": "root",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                }
            ],
            "roleperm": [],
            "perminhe": []
        }
    },
    {
        "container": {
            "prinperm": [],
            "prinrole": [
                {
                    "principal": "root",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                }
            ],
            "roleperm": [],
            "perminhe": []
        }
    },
    {
        "(no name)": {
            "prinperm": [
                {
                    "principal": "root",
                    "permission": "guillotina.AccessContent",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "permission": "guillotina.AddContainer",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "permission": "guillotina.DeleteContainers",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "permission": "guillotina.GetAPIDefinition",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "permission": "guillotina.GetContainers",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "permission": "guillotina.GetDatabases",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "permission": "guillotina.MountDatabase",
                    "setting": "Allow"
                },
                {
                    "principal": "root",
                    "permission": "guillotina.UmountDatabase",
                    "setting": "Allow"
                }
            ],
            "perminhe": []
        }
    },
    {
        "system": {
            "prinperm": [],
            "prinrole": [],
            "roleperm": [
                {
                    "permission": "guillotina.AccessPreflight",
                    "role": "guillotina.Anonymous",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.AccessPreflight",
                    "role": "guillotina.Authenticated",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.Public",
                    "role": "guillotina.Anonymous",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.Public",
                    "role": "guillotina.Authenticated",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.UseWebSockets",
                    "role": "guillotina.Authenticated",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ViewContent",
                    "role": "guillotina.Reader",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ViewContent",
                    "role": "guillotina.Reviewer",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ViewContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ViewContent",
                    "role": "guillotina.Editor",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.AccessContent",
                    "role": "guillotina.Reader",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.AccessContent",
                    "role": "guillotina.Reviewer",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.AccessContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.AccessContent",
                    "role": "guillotina.Editor",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.AccessContent",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.DuplicateContent",
                    "role": "guillotina.Reader",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.DuplicateContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.DuplicateContent",
                    "role": "guillotina.Editor",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.DeleteContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.AddContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.MoveContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.MoveContent",
                    "role": "guillotina.Editor",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ModifyContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ModifyContent",
                    "role": "guillotina.Editor",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ChangePermissions",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.SeePermissions",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ReindexContent",
                    "role": "guillotina.Owner",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ReindexContent",
                    "role": "guillotina.Editor",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ManageAddons",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ReadConfiguration",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.WriteConfiguration",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.RegisterConfigurations",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.ManageCatalog",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.RawSearchContent",
                    "role": "guillotina.ContainerAdmin",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.Manage",
                    "role": "guillotina.Manager",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.DeleteContainers",
                    "role": "guillotina.ContainerDeleter",
                    "setting": "Allow"
                },
                {
                    "permission": "guillotina.SearchContent",
                    "role": "guillotina.Member",
                    "setting": "Allow"
                }
            ]
        }
    }
]
Status Codes:

GET /(db)/(container)/(id)/@canido

Check if user has permissions on context

  • Permission: guillotina.AccessContent
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container/foobar/@canido?permissions=guillotina.ModifyContent,guillotina.AccessContent HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i 'http://nohost/db/container/foobar/@canido?permissions=guillotina.ModifyContent,guillotina.AccessContent' -H 'Accept: application/json' --user root:root

httpie

http 'http://nohost/db/container/foobar/@canido?permissions=guillotina.ModifyContent,guillotina.AccessContent' Accept:application/json -a root:root

response

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

{
    "guillotina.ModifyContent": true,
    "guillotina.AccessContent": true
}
Query Parameters:
 
  • permission (string) – (required)
Status Codes:

GET /(db)/(container)/(id)/@sharing

Get sharing settings for this resource

  • Permission: guillotina.SeePermissions
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container/foobar/@sharing HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/foobar/@sharing -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/foobar/@sharing Accept:application/json -a root:root

response

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

{
    "local": {
        "roleperm": {},
        "prinperm": {},
        "prinrole": {
            "root": {
                "guillotina.Owner": "Allow"
            }
        }
    },
    "inherit": [
        {
            "@id": "http://127.0.0.1:60361/db/container",
            "roleperm": {},
            "prinperm": {},
            "prinrole": {
                "root": {
                    "guillotina.ContainerAdmin": "Allow",
                    "guillotina.Owner": "Allow"
                }
            }
        }
    ]
}
Status Codes:

POST /(db)/(container)/(id)/@sharing

Change permissions for a resource

  • Permission: guillotina.ChangePermissions
  • Context: guillotina.interfaces.content.IResource

http

POST /db/container/foobar/@sharing HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json

{
    "prinrole": [
        {
            "principal": "foobar",
            "role": "guillotina.Owner",
            "setting": "Allow"
        }
    ]
}

curl

curl -i -X POST http://nohost/db/container/foobar/@sharing -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"prinrole": [{"principal": "foobar", "role": "guillotina.Owner", "setting": "Allow"}]}' --user root:root

httpie

echo '{
  "prinrole": [
    {
      "principal": "foobar",
      "role": "guillotina.Owner",
      "setting": "Allow"
    }
  ]
}' | http POST http://nohost/db/container/foobar/@sharing Accept:application/json Content-Type:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

PUT /(db)/(container)/(id)/@sharing

Replace permissions for a resource

  • Permission: guillotina.ChangePermissions
  • Context: guillotina.interfaces.content.IResource

http

PUT /db/container/foobar/@sharing HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json

{
    "prinrole": [
        {
            "principal": "foobar",
            "role": "guillotina.Owner",
            "setting": "Allow"
        }
    ]
}

curl

curl -i -X PUT http://nohost/db/container/foobar/@sharing -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"prinrole": [{"principal": "foobar", "role": "guillotina.Owner", "setting": "Allow"}]}' --user root:root

httpie

echo '{
  "prinrole": [
    {
      "principal": "foobar",
      "role": "guillotina.Owner",
      "setting": "Allow"
    }
  ]
}' | http PUT http://nohost/db/container/foobar/@sharing Accept:application/json Content-Type:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

Content

POST /(db)/(container)/(id)/@move

Move resource

  • Permission: guillotina.MoveContent
  • Context: guillotina.interfaces.content.IResource

http

POST /db/container/foobar/@move HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json

{
    "destination": "",
    "new_id": "foobar2"
}

curl

curl -i -X POST http://nohost/db/container/foobar/@move -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"destination": "", "new_id": "foobar2"}' --user root:root

httpie

echo '{
  "destination": "",
  "new_id": "foobar2"
}' | http POST http://nohost/db/container/foobar/@move Accept:application/json Content-Type:application/json -a root:root

response

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

{
    "@url": "http://127.0.0.1:60361/db/container/foobar2"
}
Status Codes:

POST /(db)/(container)/(id)/@duplicate

Duplicate resource

  • Permission: guillotina.DuplicateContent
  • Context: guillotina.interfaces.content.IResource

http

POST /db/container/foobar2/@duplicate HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json

{
    "destination": "",
    "new_id": "foobar3"
}

curl

curl -i -X POST http://nohost/db/container/foobar2/@duplicate -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"destination": "", "new_id": "foobar3"}' --user root:root

httpie

echo '{
  "destination": "",
  "new_id": "foobar3"
}' | http POST http://nohost/db/container/foobar2/@duplicate Accept:application/json Content-Type:application/json -a root:root

response

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

{
    "@id": "http://127.0.0.1:60361/db/container/foobar3",
    "@type": "Folder",
    "@name": "foobar3",
    "@uid": "b2c|c6ad1f0f7b4e4e44900934fa0c6dd482",
    "@static_behaviors": [
        "guillotina.behaviors.dublincore.IDublinCore"
    ],
    "parent": {
        "@id": "http://127.0.0.1:60361/db/container",
        "@name": "container",
        "@type": "Container",
        "@uid": "b2c5ad1a66954773879f02c5d41ccf0d",
        "UID": "b2c5ad1a66954773879f02c5d41ccf0d"
    },
    "is_folderish": true,
    "creation_date": "2019-08-24T12:41:34.358307+00:00",
    "modification_date": "2019-08-24T12:41:34.515450+00:00",
    "UID": "b2c|c6ad1f0f7b4e4e44900934fa0c6dd482",
    "type_name": "Folder",
    "title": null,
    "uuid": "b2c|c6ad1f0f7b4e4e44900934fa0c6dd482",
    "__behaviors__": [
        "guillotina.behaviors.attachment.IAttachment"
    ],
    "__name__": "foobar3",
    "guillotina.behaviors.dublincore.IDublinCore": {
        "title": null,
        "description": null,
        "creation_date": "2019-08-24T12:41:34.358307+00:00",
        "modification_date": "2019-08-24T12:41:34.515450+00:00",
        "effective_date": null,
        "expiration_date": null,
        "creators": [
            "root"
        ],
        "tags": null,
        "publisher": null,
        "contributors": [
            "root"
        ]
    },
    "guillotina.behaviors.attachment.IAttachment": {
        "file": {
            "filename": "be5a3e66dbb5409985b77c8eb50d6d62",
            "content_type": "text/plain",
            "size": 11,
            "extension": null,
            "md5": null
        }
    },
    "items": [],
    "length": 0
}
Status Codes:

GET /(db)/(container)/(id)/@addable-types

Return a list of type names that can be added to container

  • Permission: guillotina.AddContent
  • Context: guillotina.interfaces.content.IAsyncContainer

http

GET /db/container/foobar3/@addable-types HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/foobar3/@addable-types -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/foobar3/@addable-types Accept:application/json -a root:root

response

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

[
    "Item",
    "Folder"
]
Status Codes:

GET /(db)/(container)/(id)/@ids

Return a list of ids in the resource

  • Permission: guillotina.Manage
  • Context: guillotina.interfaces.content.IFolder

http

GET /db/container/foobar3/@ids HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/foobar3/@ids -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/foobar3/@ids Accept:application/json -a root:root

response

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

[]
Status Codes:

GET /(db)/(container)/(id)/@items

Paginated list of sub objects

  • Permission: guillotina.Manage
  • Context: guillotina.interfaces.content.IFolder

http

GET /db/container/foobar3/@items HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/foobar3/@items -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/foobar3/@items Accept:application/json -a root:root

response

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

{
    "items": [],
    "total": 0,
    "page": 1,
    "page_size": 20
}
Query Parameters:
 
  • include (string) –
  • omit (string) –
  • page_size (number) – (default: 20)
  • page (number) – (default: 1)
Status Codes:

GET /(db)/(container)/(id)/@invalidate-cache

Invalidate cache of object

  • Permission: guillotina.ModifyContent
  • Context: zope.interface.Interface

http

GET /db/container/foobar3/@invalidate-cache HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290

curl

curl -i http://nohost/db/container/foobar3/@invalidate-cache -H 'Accept: application/json' --user root:root

httpie

http http://nohost/db/container/foobar3/@invalidate-cache Accept:application/json -a root:root

response

HTTP/1.1 200 OK
Content-Length: 0
Content-Type: application/json
Status Codes:

Narrative Developer Documentation

After reading quick tour or training section, Now you can start hands-on style guide to learn how to use it.

Getting started

In these narrative docs, we’ll go through creating a todo application.

Installation

pip install guillotina

Generating the initial application

Guillotina comes with a cookiecutter template for creating a base application.

First, install cookiecutter if it isn’t already installed.

pip install cookiecutter

Then, run the generate command:

guillotina create --template=application

Enter guillotina_todo for package_name.

Then, install your package:

cd guillotina_todo
python setup.py develop

Configuring

The scaffold produces an initial config.yaml configuration file for you.

You can inspect and customize your configuration. Most notable is the database configuration. If you want to run a development postgresql server, I recommend you use docker:

docker run --rm \
  -e POSTGRES_DB=guillotina \
  -e POSTGRES_USER=guillotina \
  -p 127.0.0.1:5432:5432 \
  --name postgres postgres:9.6

Creating to-do type

Types consist of an interface (schema) using the excellent zope.interface package and a class that uses that interface.

Create a guillotina_todo/content.py file with the following:

from guillotina import configure
from guillotina import schema
from guillotina import interfaces
from guillotina import content


class IToDo(interfaces.IItem):
    text = schema.Text()


@configure.contenttype(
    type_name="ToDo",
    schema=IToDo)
class ToDo(content.Item):
    """
    Our ToDo type
    """

Then, we want to make sure our content type configuration is getting loaded, so add this to your __init__.py includeme function:

from guillotina import configure
configure.scan('guillotina_todo.content')

Running

You run you application by using the guillotina command runner again:

guillotina serve -c config.yaml

Creating your todo list

Create container first:

http

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

{
    "@type": "Container",
    "description": "My todo list",
    "id": "todo",
    "title": "ToDo List"
}

curl

curl -i -X POST http://localhost:8080/db/ -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"@type": "Container", "description": "My todo list", "id": "todo", "title": "ToDo List"}' --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": "My todo list", "id": "todo", "title": "ToDo List"}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "@type": "Container",
  "description": "My todo list",
  "id": "todo",
  "title": "ToDo List"
}' | 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': 'My todo list',
    'id': 'todo',
    'title': 'ToDo List',
}, auth=('root', 'root'))

response

HTTP/1.1 200 OK
Content-Type: application/json
Location: /db/todo

{
    "@type": "Container",
    "id": "todo",
    "title": "ToDo List"
}

Install your todo list application:

http

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

{
    "id": "guillotina_todo"
}

curl

curl -i -X POST http://localhost:8080/db/todo/@addons -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"id": "guillotina_todo"}' --user root:root

wget

wget -S -O- http://localhost:8080/db/todo/@addons --header='Accept: application/json' --header='Content-Type: application/json' --post-data='{"id": "guillotina_todo"}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "id": "guillotina_todo"
}' | http POST http://localhost:8080/db/todo/@addons Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/todo/@addons', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'id': 'guillotina_todo',
}, auth=('root', 'root'))

response

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

{
    "available": [
        {
            "id": "guillotina_todo",
            "title": "Guillotina server application python project"
        }
    ],
    "installed": [
        "guillotina_todo"
    ]
}

Add todo items:

http

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

{
    "@type": "ToDo",
    "text": "Get milk"
}

curl

curl -i -X POST http://localhost:8080/db/todo -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"@type": "ToDo", "text": "Get milk"}' --user root:root

wget

wget -S -O- http://localhost:8080/db/todo --header='Accept: application/json' --header='Content-Type: application/json' --post-data='{"@type": "ToDo", "text": "Get milk"}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "@type": "ToDo",
  "text": "Get milk"
}' | http POST http://localhost:8080/db/todo Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/todo', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    '@type': 'ToDo',
    'text': 'Get milk',
}, auth=('root', 'root'))

response

HTTP/1.1 201 Created
Content-Type: application/json
Location: http://localhost:8080/db/todo/385ac34a49bc406f8494600c50b99a85

{
    "@id": "http://localhost:8080/db/todo/385ac34a49bc406f8494600c50b99a85",
    "@name": "385ac34a49bc406f8494600c50b99a85",
    "@type": "ToDo",
    "@uid": "5c9|385ac34a49bc406f8494600c50b99a85",
    "UID": "5c9|385ac34a49bc406f8494600c50b99a85"
}

http

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

{
    "@type": "ToDo",
    "text": "Do laundry"
}

curl

curl -i -X POST http://localhost:8080/db/todo -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"@type": "ToDo", "text": "Do laundry"}' --user root:root

wget

wget -S -O- http://localhost:8080/db/todo --header='Accept: application/json' --header='Content-Type: application/json' --post-data='{"@type": "ToDo", "text": "Do laundry"}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "@type": "ToDo",
  "text": "Do laundry"
}' | http POST http://localhost:8080/db/todo Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/todo', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    '@type': 'ToDo',
    'text': 'Do laundry',
}, auth=('root', 'root'))

response

HTTP/1.1 201 Created
Content-Type: application/json
Location: http://localhost:8080/db/todo/77332e3153a54924b9b36eb263848826

{
    "@id": "http://localhost:8080/db/todo/77332e3153a54924b9b36eb263848826",
    "@name": "77332e3153a54924b9b36eb263848826",
    "@type": "ToDo",
    "@uid": "5c9|77332e3153a54924b9b36eb263848826",
    "UID": "5c9|77332e3153a54924b9b36eb263848826"
}

Get a list of todo items:

http

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

curl

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

wget

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

httpie

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

python-requests

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

response

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

{
    "@id": "http://localhost:8080/db/todo",
    "@name": "todo",
    "@type": "Container",
    "@uid": "5c9932350eaf4ff189d7db934222216b",
    "UID": "5c9932350eaf4ff189d7db934222216b",
    "__behaviors__": [],
    "__name__": "todo",
    "creation_date": "2018-07-21T15:39:11.411162+00:00",
    "is_folderish": true,
    "items": [
        {
            "@id": "http://localhost:8080/db/todo/385ac34a49bc406f8494600c50b99a85",
            "@name": "385ac34a49bc406f8494600c50b99a85",
            "@type": "ToDo",
            "@uid": "5c9|385ac34a49bc406f8494600c50b99a85",
            "UID": "5c9|385ac34a49bc406f8494600c50b99a85"
        },
        {
            "@id": "http://localhost:8080/db/todo/77332e3153a54924b9b36eb263848826",
            "@name": "77332e3153a54924b9b36eb263848826",
            "@type": "ToDo",
            "@uid": "5c9|77332e3153a54924b9b36eb263848826",
            "UID": "5c9|77332e3153a54924b9b36eb263848826"
        }
    ],
    "length": 2,
    "modification_date": "2018-07-21T15:39:11.411162+00:00",
    "parent": {},
    "title": "ToDo List",
    "type_name": "Container",
    "uuid": "5c9932350eaf4ff189d7db934222216b"
}

Security

Guillotina provides an imperative security system. Permissions are computed for a given node in the resource tree using some concept we are going to describe in this document.

Basics

We’ll be explaining the security system by showing examples. Fist, make sure to follow the steps from Getting started.

Now you should have a resource tree that we can represent like the following:

db
└── todo
    ├── <fist_todo_id>
    └── <second_todo_id>

Where db is the database, todo a container with to content inside.

More than that we need some users in order to be able to compute permssion(s) for them, to do so we are going to install guillotina_dbusers, once installed create two users, let’s say “Bob” and “Alice”. You can find more informations about this addon especially how to get Bearer Authorization JWT see training’s users section.

Note that at this moment the resource tree can be represented like this:

db
└── todo
    ├── <fist_todo_id>
    ├── <second_todo_id>
    ├── users
    │   ├── Bob
    │   └── Alice
    └── groups

Now login with “Bob” and try access /db/todo endpoint:

http

GET /db/todo/ HTTP/1.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I
Host: localhost:8080

curl

curl -i http://localhost:8080/db/todo/ -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I'

wget

wget -S -O- http://localhost:8080/db/todo/ --header='Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I'

httpie

http http://localhost:8080/db/todo/ Authorization:'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I'

python-requests

requests.get('http://localhost:8080/db/todo/', headers={
    'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I',
})

response

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
    "auths": [
        "Bob"
    ],
    "content": "< Container at /todo by 140237937521992 >",
    "reason": "You are not authorized to view"
}

Like you can see in the response you are not authorized to view, and that’s great because it means that the security system works like a charm.

Let’s grant “Bob” view permission for this db/todo/ resource tree node:

http

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

{
    "prinperm": [
        {
            "permission": "guillotina.ViewContent",
            "principal": "Bob",
            "setting": "Allow"
        }
    ]
}

curl

curl -i -X POST http://localhost:8080/db/todo/@sharing -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"prinperm": [{"permission": "guillotina.ViewContent", "principal": "Bob", "setting": "Allow"}]}' --user root:root

wget

wget -S -O- http://localhost:8080/db/todo/@sharing --header='Accept: application/json' --header='Content-Type: application/json' --post-data='{"prinperm": [{"permission": "guillotina.ViewContent", "principal": "Bob", "setting": "Allow"}]}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "prinperm": [
    {
      "permission": "guillotina.ViewContent",
      "principal": "Bob",
      "setting": "Allow"
    }
  ]
}' | http POST http://localhost:8080/db/todo/@sharing Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/todo/@sharing', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'prinperm': [{
        'permission': 'guillotina.ViewContent',
        'principal': 'Bob',
        'setting': 'Allow',
    }],
}, auth=('root', 'root'))

response

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

You can now access to /db/todo endpoint using Bob user:

http

GET /db/todo/ HTTP/1.1
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I
Host: localhost:8080

curl

curl -i http://localhost:8080/db/todo/ -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I'

wget

wget -S -O- http://localhost:8080/db/todo/ --header='Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I'

httpie

http http://localhost:8080/db/todo/ Authorization:'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I'

python-requests

requests.get('http://localhost:8080/db/todo/', headers={
    'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIyNTM3NDcsImlkIjoiQm9iIn0.1-JbNe1xNoHJgPEmJ05oULi4I9OMGBsviWFHnFPvm-I',
})

response

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

{
    "@id": "http://localhost:8080/db/todo",
    "@name": "todo",
    "@type": "Container",
    "@uid": "6e63e13b4d1647d5a4ef5ef61ea040f1",
    "UID": "6e63e13b4d1647d5a4ef5ef61ea040f1",
    "__behaviors__": [],
    "__name__": "todo",
    "creation_date": "2018-07-22T07:33:19.098099+00:00",
    "is_folderish": true,
    "items": [
        {
            "@id": "http://localhost:8080/db/todo/9eca9e3e84ce4e79883f19fdbbe694b1",
            "@name": "9eca9e3e84ce4e79883f19fdbbe694b1",
            "@type": "ToDo",
            "@uid": "6e6|9eca9e3e84ce4e79883f19fdbbe694b1",
            "UID": "6e6|9eca9e3e84ce4e79883f19fdbbe694b1"
        },
        {
            "@id": "http://localhost:8080/db/todo/ae45417c8115463aa2d6437de3577d02",
            "@name": "ae45417c8115463aa2d6437de3577d02",
            "@type": "ToDo",
            "@uid": "6e6|ae45417c8115463aa2d6437de3577d02",
            "UID": "6e6|ae45417c8115463aa2d6437de3577d02"
        },
        {
            "@id": "http://localhost:8080/db/todo/groups",
            "@name": "groups",
            "@type": "GroupManager",
            "@uid": "6e6|ff31eda7808044488dc492d2075e4e13",
            "UID": "6e6|ff31eda7808044488dc492d2075e4e13"
        },
        {
            "@id": "http://localhost:8080/db/todo/users",
            "@name": "users",
            "@type": "UserManager",
            "@uid": "6e6|753405ce2dfe4455930c8fc850f38157",
            "UID": "6e6|753405ce2dfe4455930c8fc850f38157"
        }
    ],
    "length": 4,
    "modification_date": "2018-07-22T09:33:13.486834+00:00",
    "parent": {},
    "title": "ToDo List",
    "type_name": "Container",
    "uuid": "6e63e13b4d1647d5a4ef5ef61ea040f1"
}

What we’ve done so far looks like we’ve grant user “Bob” view access to this node, but that’s not totally true.

As you can see in the permission definition we grant permission to a principal. A principal is like a tag and we grant permission to that tag. The user’s id is the “principal” here but more on that later.

Another important thing is the setting attribute, which defined the permission propagation in the resource tree, this attribute can have only three value:

  • Allow: set on resource and children will inherit
  • Deny: set on resource and children will inherit (good way to stop propagation)
  • AllowSingle: set on resource and children will not inherit (also a good way to stop propagation)
  • Unset: you remove the setting

Note that we’ve defined a permission with “Allow” propagation setting at this level of the resource tree:

db
└── todo                   <-- permission was granted here
    ├── <fist_todo_id>
    ├── <second_todo_id>
    ├── users
    │   ├── Bob
    │   └── Alice
    └── groups

Which means that user “Bob” can view todo container, but also all todo, users and groups, but cannot db database. Try it.

The last parameter in our permission definition we’ve talk so far is the permission parameter itself, Guillotina provided a lot of permission by default, you can find an exhaustive like by reading Guillotina permissions definitions from the source code. Most of the permissions your application will need should be defined there, but obviously you can also defined your own, more on that later.

Groups

Groups can be assigned to users, each group names are also principals, this is the way we can add principals to users.

Let’s add a group named todo_viewer:

http

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

{
    "@type": "Group",
    "name": "todo_viewer"
}

curl

curl -i -X POST http://localhost:8080/db/todo/groups -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"@type": "Group", "name": "todo_viewer"}' --user root:root

wget

wget -S -O- http://localhost:8080/db/todo/groups --header='Accept: application/json' --header='Content-Type: application/json' --post-data='{"@type": "Group", "name": "todo_viewer"}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "@type": "Group",
  "name": "todo_viewer"
}' | http POST http://localhost:8080/db/todo/groups Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/todo/groups', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    '@type': 'Group',
    'name': 'todo_viewer',
}, auth=('root', 'root'))

response

HTTP/1.1 201 Created
Content-Type: application/json
Location: http://localhost:8080/db/todo/groups/14f624ef23094362961df0e083cd77e4

{
    "@id": "http://localhost:8080/db/todo/groups/14f624ef23094362961df0e083cd77e4",
    "@name": "14f624ef23094362961df0e083cd77e4",
    "@type": "Group",
    "@uid": "6e6|ff3|14f624ef23094362961df0e083cd77e4",
    "UID": "6e6|ff3|14f624ef23094362961df0e083cd77e4"
}

And add “Bob” and “Alice” to that group.

http

PATCH /db/todo/users/Bob HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json
Host: localhost:8080

{
    "user_groups": [
        "todo_viewer"
    ]
}

curl

curl -i -X PATCH http://localhost:8080/db/todo/users/Bob -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"user_groups": ["todo_viewer"]}' --user root:root

wget

wget -S -O- --method=PATCH http://localhost:8080/db/todo/users/Bob --header='Accept: application/json' --header='Content-Type: application/json' --body-data='{"user_groups": ["todo_viewer"]}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "user_groups": [
    "todo_viewer"
  ]
}' | http PATCH http://localhost:8080/db/todo/users/Bob Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.patch('http://localhost:8080/db/todo/users/Bob', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'user_groups': ['todo_viewer'],
}, auth=('root', 'root'))

response

HTTP/1.1 204 No Content
Content-Type: application/json

http

PATCH /db/todo/users/Alice HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json
Host: localhost:8080

{
    "user_groups": [
        "todo_viewer"
    ]
}

curl

curl -i -X PATCH http://localhost:8080/db/todo/users/Alice -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"user_groups": ["todo_viewer"]}' --user root:root

wget

wget -S -O- --method=PATCH http://localhost:8080/db/todo/users/Alice --header='Accept: application/json' --header='Content-Type: application/json' --body-data='{"user_groups": ["todo_viewer"]}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "user_groups": [
    "todo_viewer"
  ]
}' | http PATCH http://localhost:8080/db/todo/users/Alice Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.patch('http://localhost:8080/db/todo/users/Alice', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'user_groups': ['todo_viewer'],
}, auth=('root', 'root'))

response

HTTP/1.1 204 No Content
Content-Type: application/json

Let’s grant todo_viewer view permission for this db/todo/ resource tree node:

http

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

{
    "prinperm": [
        {
            "permission": "guillotina.ViewContent",
            "principal": "todo_viewer",
            "setting": "Allow"
        }
    ]
}

curl

curl -i -X POST http://localhost:8080/db/todo/@sharing -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"prinperm": [{"permission": "guillotina.ViewContent", "principal": "todo_viewer", "setting": "Allow"}]}' --user root:root

wget

wget -S -O- http://localhost:8080/db/todo/@sharing --header='Accept: application/json' --header='Content-Type: application/json' --post-data='{"prinperm": [{"permission": "guillotina.ViewContent", "principal": "todo_viewer", "setting": "Allow"}]}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "prinperm": [
    {
      "permission": "guillotina.ViewContent",
      "principal": "todo_viewer",
      "setting": "Allow"
    }
  ]
}' | http POST http://localhost:8080/db/todo/@sharing Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/todo/@sharing', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'prinperm': [{
        'permission': 'guillotina.ViewContent',
        'principal': 'todo_viewer',
        'setting': 'Allow',
    }],
}, auth=('root', 'root'))

response

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

Now alice should be able to view todo container and all it’s children.

At the moment alice can view users and groups which is not convenient for a todo_viewer group, let’s deny that.

http

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

{
    "prinperm": [
        {
            "permission": "guillotina.ViewContent",
            "principal": "todo_viewer",
            "setting": "Deny"
        }
    ]
}

curl

curl -i -X POST http://localhost:8080/db/todo/users/@sharing -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"prinperm": [{"permission": "guillotina.ViewContent", "principal": "todo_viewer", "setting": "Deny"}]}' --user root:root

wget

wget -S -O- http://localhost:8080/db/todo/users/@sharing --header='Accept: application/json' --header='Content-Type: application/json' --post-data='{"prinperm": [{"permission": "guillotina.ViewContent", "principal": "todo_viewer", "setting": "Deny"}]}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "prinperm": [
    {
      "permission": "guillotina.ViewContent",
      "principal": "todo_viewer",
      "setting": "Deny"
    }
  ]
}' | http POST http://localhost:8080/db/todo/users/@sharing Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/todo/users/@sharing', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'prinperm': [{
        'permission': 'guillotina.ViewContent',
        'principal': 'todo_viewer',
        'setting': 'Deny',
    }],
}, auth=('root', 'root'))

response

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

http

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

{
    "prinperm": [
        {
            "permission": "guillotina.ViewContent",
            "principal": "todo_viewer",
            "setting": "Deny"
        }
    ]
}

curl

curl -i -X POST http://localhost:8080/db/todo/groups/@sharing -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"prinperm": [{"permission": "guillotina.ViewContent", "principal": "todo_viewer", "setting": "Deny"}]}' --user root:root

wget

wget -S -O- http://localhost:8080/db/todo/groups/@sharing --header='Accept: application/json' --header='Content-Type: application/json' --post-data='{"prinperm": [{"permission": "guillotina.ViewContent", "principal": "todo_viewer", "setting": "Deny"}]}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "prinperm": [
    {
      "permission": "guillotina.ViewContent",
      "principal": "todo_viewer",
      "setting": "Deny"
    }
  ]
}' | http POST http://localhost:8080/db/todo/groups/@sharing Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/todo/groups/@sharing', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'prinperm': [{
        'permission': 'guillotina.ViewContent',
        'principal': 'todo_viewer',
        'setting': 'Deny',
    }],
}, auth=('root', 'root'))

response

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

Now both Alice and Bob can’t access to users and groups, if you want Bob to be able to access those endpoints you should explicitly set permission for principal “Bob” on those ones.

Roles

Roles are granted permissions, which means that a principal with one role will inherit all that role permissions.

Guillotina defined serval default roles, see developer roles section. But remember that you can defined your own ones(more on that later).

For example let’s give to principal “Alice” the guillotina.Editor role on /db/todo/<first_todo_id> resource tree node, which grants the following permissions:

  • guillotina.AccessContent
  • guillotina.ViewContent
  • guillotina.ModifyContent
  • guillotina.ReindexContent

To do use run (don’t forget to replace <first_todo_id_> with your first todo id):

http

POST /db/todo/<first_todo_id>/@sharing HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json
Host: localhost:8080

{
    "prinrole": [
        {
            "principal": "Alice",
            "role": "guillotina.Editor",
            "setting": "Allow"
        }
    ]
}

curl

curl -i -X POST 'http://localhost:8080/db/todo/<first_todo_id>/@sharing' -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"prinrole": [{"principal": "Alice", "role": "guillotina.Editor", "setting": "Allow"}]}' --user root:root

wget

wget -S -O- 'http://localhost:8080/db/todo/<first_todo_id>/@sharing' --header='Accept: application/json' --header='Content-Type: application/json' --post-data='{"prinrole": [{"principal": "Alice", "role": "guillotina.Editor", "setting": "Allow"}]}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "prinrole": [
    {
      "principal": "Alice",
      "role": "guillotina.Editor",
      "setting": "Allow"
    }
  ]
}' | http POST 'http://localhost:8080/db/todo/<first_todo_id>/@sharing' Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/todo/<first_todo_id>/@sharing', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'prinrole': [{
        'principal': 'Alice',
        'role': 'guillotina.Editor',
        'setting': 'Allow',
    }],
}, auth=('root', 'root'))

response

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

Now Alice can access, view, modify and reindex the first todo but Bob still only view to it.

You can also add permission for a role at/from a given resource tree node for a given role, for example at the moment principal “Alice” which have “guillotina.Editor” role on /db/todo/<first_todo_id> cannot delete it.

Let’s fix that by giving “guillotina.DeleteContent” permission to “guillotina.Editor” role at this specific resource tree node:

http

POST /db/todo/<first_todo_id>/@sharing HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
Content-Type: application/json
Host: localhost:8080

{
    "roleperm": [
        {
            "permission": "guillotina.DeleteContent",
            "role": "guillotina.Editor",
            "setting": "Allow"
        }
    ]
}

curl

curl -i -X POST 'http://localhost:8080/db/todo/<first_todo_id>/@sharing' -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"roleperm": [{"permission": "guillotina.DeleteContent", "role": "guillotina.Editor", "setting": "Allow"}]}' --user root:root

wget

wget -S -O- 'http://localhost:8080/db/todo/<first_todo_id>/@sharing' --header='Accept: application/json' --header='Content-Type: application/json' --post-data='{"roleperm": [{"permission": "guillotina.DeleteContent", "role": "guillotina.Editor", "setting": "Allow"}]}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "roleperm": [
    {
      "permission": "guillotina.DeleteContent",
      "role": "guillotina.Editor",
      "setting": "Allow"
    }
  ]
}' | http POST 'http://localhost:8080/db/todo/<first_todo_id>/@sharing' Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/todo/<first_todo_id>/@sharing', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    'roleperm': [{
        'permission': 'guillotina.DeleteContent',
        'role': 'guillotina.Editor',
        'setting': 'Allow',
    }],
}, auth=('root', 'root'))

response

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

Now anyone who have “guillotina.Editor” role on that node, including only Alice at the moment, will be able to delete it.

Security Levels

Security for every operation is managed against three definitions (in order of priority):

  • Local
  • Global
  • Code

“Local” means for a given resource tree node.

“Global” stand for application level.

And finally “Code” for code level, like services, containers or whatever code exposed to the API.

This means that principal or role could be mandatory to acces them.

Locals can defined:

  • Permission for principal with propagation definition
  • Role for principal with propagation definition
  • Permission for role with propagation definition

Globals:

  • Role for principal
  • Permission for principal

Code:

  • Role for principal
  • Permission for principal
  • Permission for role

Roles

There are two kind of roles: Global and Local. The ones that are defined to be local can’t be used globally and vice-versa. On indexing, the global roles are the ones that are indexed for security in addition to the flat user/group information from each resource.

Python helper functions


# Code to get the global roles that have access_content to an object
from guillotina.security.utils import get_roles_with_access_content
get_roles_with_access_content(obj)

# Code to get the user list that have access content to an object
from guillotina.security.utils import get_principals_with_access_content
get_principals_with_access_content(obj)


# Code to get all the security info
from guillotina.security.utils import settings_for_object
settings_for_object(obj)

# Code to get the Interaction object ( security object )
from guillotina.interfaces import IInteraction

interaction = IInteraction(request)

# Get the list of global roles for a user and some groups
interaction.global_principal_roles(principal, groups)

# Get if the authenticated user has permission on a object
interaction.check_permission(permission, obj)

REST APIs

Get all the endpoints and their security

[GET] APPLICATION_URL/@apidefinition (you need guillotina.GetContainers permission)

Get the security info for a resource (with inherited info)

[GET] RESOURCE/@sharing (you need guillotina.SeePermissions permission)

Modify the local roles/permission for a resource

[POST] RESOURCE/@sharing (you need guillotina.ChangePermissions permission)

{
"prinperm": [
  {
    "principal": "foobar",
    "permission": "guillotina.ModifyContent",
    "setting": "Allow"
  }
],
"prinrole": [
  {
    "principal": "foobar",
    "role": "guillotina.Owner",
    "setting": "Allow"
  }
],
"roleperm": [
  {
    "permission": "guillotina.ModifyContent",
    "role": "guillotina.Member",
    "setting": "Allow"
  }
]
}

Propagation setting

The different types are:

  • Allow: set on resource and children will inherit
  • Deny: set on resource and children will inherit (good way to stop propagation)
  • AllowSingle: set on resource and children will not inherit (also a good way to stop propagation)
  • Unset: you remove the setting

Roles

guillotina implements robust ACL security.

An overview of our security features are:

  • Users are given roles and groups
  • Roles are granted permissions
  • Groups are granted roles
  • Roles can be granted to users on specific objects

Requests security

By default request has participation of anonymous user plus the ones added by auth plugins

Databases, Application and static files objects

Databases and static files have a specific permission system. They don’t have roles by default and the permissions are specified to root user

  • guillotina.AddContainer
  • guillotina.GetContainers
  • guillotina.DeleteContainers
  • guillotina.AccessContent
  • guillotina.GetDatabases

Anonymous user has on DB/StaticFiles/StaticDirectories/Application object :

  • guillotina.AccessContent

Roles in guillotina container objects

Defined at:

  • guillotina/permissions.py

Container/App Roles

guillotina.ContainerAdmin

  • guillotina.AccessContent
  • guillotina.ManageAddons
  • guillotina.RegisterConfigurations
  • guillotina.WriteConfiguration
  • guillotina.ReadConfiguration
  • guillotina.ManageCatalog

guillotina.ContainerDeleter

  • guillotina.DeletePortal

Default roles on Guillotina Container

They are stored in annotations using IRolePermissionMap.

Created objects set the guillotina.Owner role to the user who created it.

Default groups on Guillotina Container

Managers

RootParticipation

There is a root user who has permissions to all containers:

DB/APP permissions are defined on factory/content.py

The definition of the root user can be found on auth/users.py. Notice how it is assigned to the "Managers" group by default, which in turn has the following hardcoded roles:

  • guillotina.ContainerAdmin
  • guillotina.ContainerDeleter
  • guillotina.Owner
  • guillotina.Member
  • guillotina.Manager

Thus, these are the default roles for the root user.

Applications

Applications are used to provide additional functionality to guillotina.

Community Addons

Some useful addons to use in your own development:

Creating

An application is a Python package that implements an entry point to tell guillotina to load it.

If you’re not familiar with how to build Python applications, please read documentation on building packages before you continue.

In this example, guillotina_myaddon is your package module.

Initialization

Your config.yaml file will need to provide the application name in the applications array for it to be initialized.

applications:
    - guillotina_myaddon

Configuration

Once you create a guillotina application, there are two primary ways for it to hook into guillotina.

Call the includeme function

Your application can provide an includeme function at the root of the module and guillotina will call it with the instance of the root object.


def includeme(root):
  # do initialization here...
  pass

Load app_settings

If an app_settings dict is provided at the module root, it will automatically merge the global guillotina app_settings with the module’s. This allows you to provide custom configuration.

Add-ons

Addons are integrations that can be installed or uninstalled against a Guillotina container. guillotina applications can potentially provide many addons. If you have not read the section on applications, please read that before you come here. The only way to provide addons is to first implement a guillotina application.

Creating an add-on

Create an addon installer class in an install.py file in your guillotina application:


from guillotina.addons import Addon
from guillotina import configure

@configure.addon(
    name="myaddon",
    title="My addon",
    dependencies=['cms'])
class MyAddon(Addon):

    @classmethod
    def install(cls, container, request):
        # install code
        pass

    @classmethod
    def uninstall(cls, container, request):
        # uninstall code
        pass

Note

Scanning

If your service modules are not imported at run-time, you may need to provide an additional scan call to get your services noticed by guillotina.

In your application __init__.py file, you can simply provide a scan call like:

from guillotina import configure
def includeme(root):
    configure.scan('my.package')

Layers

A Layer is a marker you install with your add-on, this allows your application to lookup views and adapters (override core functionality) only for the container you installed the add-on.


from guillotina.addons import Addon
from guillotina import configure
from guillotina.interfaces import ILayers

LAYER = 'guillotina_myaddon.interfaces.ILayer'

@configure.addon(
    name="myaddon",
    title="My addon")
class MyAddon(Addon):

    @classmethod
    def install(cls, container, request):
        registry = request.container_settings
        registry.for_interface(ILayers).active_layers |= {
            LAYER
        }

    @classmethod
    def uninstall(cls, container, request):
        registry = request.container_settings
        registry.for_interface(ILayers).active_layers -= {
            LAYER
        }

Installing an addon into a container

Addons can be installed into a container using @addons endpoint by providing addon name as id For example:

http

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

{
    "id": "myaddon"
}

curl

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

wget

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

httpie

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

python-requests

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

response

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

{
    "available": [
        {
            "id": "myaddon",
            "title": "Guillotina DB Users"
        },
        {
            "id": "application_name",
            "title": "Your application title"
        }
    ],
    "installed": [
        "dbusers",
        "application_name"
    ]
}

Services

Services provide responses to API endpoint requests. A service is the same as a “view” that you might see in many web frameworks.

The reason we’re using the convention “service” is because we’re focusing on creating API endpoints.

Defining a service

A service can be as simple as a function in your application:

from guillotina import configure
from guillotina.interfaces import IContainer

@configure.service(context=IContainer, name='@myservice', method='GET',
                   permission='guillotina.AccessContent')
async def my_service(context, request):
    return {
        'foo': 'bar'
    }

The most simple way to define a service is to use the decorator method shown here.

As long as your application imports the module where your service is defined, your service will be loaded for you.

In this example, the service will apply to a GET request against a container, /zodb/guillotina/@myservice.

Note

Scanning

If your service modules are not imported at run-time, you may need to provide an additional scan call to get your services noticed by guillotina.

In your application __init__.py file, you can simply provide a scan call like:

from guillotina import configure
def includeme(root):
    configure.scan('my.package')

Class-based services

For more complex services, you might want to use class-based services.

With the class-based approach, the example above will look like this:

from guillotina import configure
from guillotina.interfaces import IContainer
from guillotina.api.service import Service

@configure.service(context=IContainer, name='@myservice', method='GET',
                   permission='guillotina.AccessContent')
class MyService(Service):
    async def __call__(self):
        return {
            'foo': 'bar'
        }

Special cases

I want that my service is accessible no matter the content

You can define in the service configuration with allow_access=True which tells the security checker to ignore checking for the default permission guillotina.AccessContent.

from guillotina import configure
from guillotina.interfaces import IResource
@configure.service(
    context=IResource, name='@download',
    method='GET', permission='guillotina.Public',
    allow_access=True)
async def my_service(context, request):
    pass

Response rendering

Guillotina has a rendering framework to be able to dynamically handle multiple request Accept headers.

Out of the box, Guillotina only supports application/json, text/html and text/plain dynamic response types. Streaming and web socket type responses are also supported but are not handled by the dyanamic rendering framework.

Customizing responses

Services can provide simple type values for responses. Ideally anything that can be serialized as json(default renderer).

Additionally, services can provide custom response objects to customize status and header.

from guillotina import configure
from guillotina.interfaces import IResource
from guillotina.response import Response

@configure.service(
    context=IResource, name='@custom-status',
    method='GET', permission='guillotina.Public',
    allow_access=True)
async def custom_status(context, request):
    return {'foo': 'bar'}, 201


@configure.service(
    context=IResource, name='@custom-headers',
    method='GET', permission='guillotina.Public',
    allow_access=True)
async def custom_headers(context, request):
    return Response(content={'foo': 'bar'}, status=200, headers={'X-Foobar', 'foobar'})

Response types

Guillotina will automatically transform any response types in the guillotina.response library.

These response objects should have simple dict values for their content if provided.

Bypassing reponses rendering

If you return any aiohttp based response objects, they will be ignored by the rendering framework.

This is useful when streaming data for example and it should not be transformed.

Custom rendering

It’s also very easy to provide your own renderer. All you need to do is provide your own renderer class and configure it with the configuration object.

Here is a yaml example:

from guillotina import configure
from guillotina.renderer import Renderer
import yaml

# yaml is known to have a lot of different content types, it's okay!
@configure.renderer(name='text/vnd.yaml')
@configure.renderer(name='application/yaml')
@configure.renderer(name='application/x-yaml')
@configure.renderer(name='text/x-yaml')
@configure.renderer(name='text/yaml')
class RendererYaml(Renderer):
    content_type = 'application/yaml'

    def get_body(self, value) -> bytes:
        if value is not None:
            value = yaml.dump(value)
            return value.encode('utf-8')

Content types

Content types allow you to provide custom schemas and content to your services.

Out-of-the-box, guillotina ships with simple Container, Folder and Item content types. The Container content type is the main content type to hold your data in. It is the starting point for applications and other things to operate within.

The Folder type allows someone to add items inside of it. Both types only have simple Dublin Core fields by default.

Defining content types

A content type consists of a class and optionally, a schema to define the custom fields you want your class to use.

A simple type will look like this::

from guillotina import configure
from guillotina.content import Folder
from guillotina.interfaces import IItem
from guillotina import schema

class IMySchema(IItem):
    foo = schema.Text()

@configure.contenttype(
    type_name="MyType",
    schema=IMySchema,
    behaviors=["guillotina.behaviors.dublincore.IDublinCore"])
class MyType(Folder):
    pass

This example creates a simple schema and assigns it to the MyType content type.

Note

Scanning

If your service modules are not imported at run-time, you may need to provide an additional scan call to get your services noticed by guillotina.

In your application __init__.py file, you can simply provide a scan call like:

from guillotina import configure
def includeme(root):
    configure.scan('my.package')

Behaviors

Besides having static content types definitions with their schema, there is the concept of behaviors. This allows us to provide functionality across content types, using specific marker interfaces to create adapters and subscribers based on that behavior and not the content type.

Definition of a behavior

If you want to have a shared behavior based on some fields and operations that needs to be shared across different content types, you can define them on a guillotina.schema interface:

from zope.interface import Interface
from guillotina.schema import Textline

class IMyLovedBehavior(Interface):
    text = Textline(
        title=u'Text line field',
        required=False
    )

    text2 = Textline(
        title=u'Text line field',
        required=False
    )

Once you define the schema you can define a specific marker interface that will be applied to the objects that has this behavior:


class IMarkerBehavior(Interface):
    """Marker interface for content with attachment."""

Finally the instance class that implements the schema can be defined in case you want to enable specific operations. Or you can use guillotina.behaviors.instance.AnnotationBehavior as the default annotation storage.

For example, in case you want to have a class that stores the field as content and not as annotations:

from guillotina.behaviors.properties import ContextProperty
from guillotina.behaviors.instance import AnnotationBehavior
from guillotina.interfaces import IResource
from guillotina import configure, schema
from zope.interface import Interface

class IMarkerBehavior(Interface):
    """Marker interface for content with attachment."""


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

@configure.behavior(
    title="Attachment",
    provides=IMyBehavior,
    marker=IMarkerBehavior,
    for_=IResource)
class MyBehavior(AnnotationBehavior):
    """If attributes
    """
    text = ContextProperty(u'attribute', ())

In this example text will be stored on the context object and text2 as a annotation.

Static behaviors

With behaviors you can define them as static for specific content types:


from guillotina import configure
from guillotina.interfaces import IItem
from guillotina.content import Item

@configure.contenttype(
    type_name="MyItem",
    schema=IItem,
    behaviors=["guillotina.behaviors.dublincore.IDublinCore"])
class MyItem(Item):
    pass

Note

Scanning

If your service modules are not imported at run-time, you may need to provide an additional scan call to get your services noticed by guillotina.

In your application __init__.py file, you can simply provide a scan call like:

from guillotina import configure
def includeme(root):
    configure.scan('my.package')

Create and modify content with behaviors

For the deserialization of the content you will need to pass on the POST/PATCH operation the behavior as a object on the JSON.

CREATE an ITEM with the expires : POST on parent:

    {
        "@type": "Item",
        "guillotina.behaviors.dublincore.IDublinCore": {
            "expires": "1/10/2017"
        }
    }

MODIFY an ITEM with the expires : PATCH on the object:

    {
        "guillotina.behaviors.dublincore.IDublinCore": {
            "expires": "1/10/2017"
        }
    }

Get content with behaviors

On the serialization of the content you will get the behaviors as objects on the content.

GET an ITEM : GET on the object:

    {
        "@id": "http://localhost:8080/zodb/guillotina/item1",
        "guillotina.behaviors.dublincore.IDublinCore": {
            "expires": "2017-10-01T00:00:00.000000+00:00",
            "modified": "2016-12-02T14:14:49.859953+00:00",
        }
    }

Dynamic Behaviors

guillotina offers the option to have content that has dynamic behaviors applied to them.

Which behaviors are available on a context

We can know which behaviors can be applied to a specific content.

GET CONTENT_URI/@behaviors:

    {
        "available": ["guillotina.behaviors.attachment.IAttachment"],
        "static": ["guillotina.behaviors.dublincore.IDublinCore"],
        "dynamic": [],
        "guillotina.behaviors.attachment.IAttachment": { },
        "guillotina.behaviors.dublincore.IDublinCore": { }
    }

This list of behaviors is based on the for statement on the configure of the behavior. The list of static ones are the ones defined on the content type definition on the configure. The list of dynamic ones are the ones that have been assigned.

Add a new behavior to a content

We can add a new dynamic behavior to a content using a PATCH operation on the object with the @behavior attribute, or in a small PATCH operation to the @behavior entry point with the value to add.

MODIFY an ITEM with the expires : PATCH on the object:

    {
        "guillotina.behaviors.dublincore.IDublinCore": {
            "expires": "1/10/2017"
        }
    }

MODIFY behaviors : PATCH on the object/@behaviors:

    {
        "behavior": "guillotina.behaviors.dublincore.IDublinCore"
    }

Delete a behavior to a content

We can add a new dynamic behavior to a content by a DELETE operation to the @behavior entry point with the value to remove.

DELETE behaviors : DELETE on the object/@behaviors:

    {
        "behavior": "guillotina.behaviors.dublincore.IDublinCore"
    }

Out-of-the-box Behaviors

Guillotina comes with a couple behaviors:

  • guillotina.behaviors.dublincore.IDublinCore: Dublin core field
  • guillotina.behaviors.attachment.IAttachment: Provide file field

Interfaces

guillotina uses interfaces to abstract and define various things including content. Interfaces are useful when defining API contracts, using inheritance, defining schema/behaviors and being able to define which content your services are used for.

In the services example, you’ll notice the use of context=IContainer for the service decorator configuration. In that case, it is used to tell guillotina that the service is only defined for a container object.

Read the zope.interface docs for more details about the power of designing around around interfaces and to learn more about how to use it.

Events

Guillotina provides an event/subscriber pattern for dispatching events out to subscribers when “things” happen in Guillotina.

Subscribing

Subscribing to events is done with the configure module.

All subscribers are configured with:

  1. the object type to match subcribe to the event against
  2. the type of event you want to subscribe to

For example, to subscribe when an object is modified:

from guillotina import configure
from guillotina.interfaces import IResource
from guillotina.interfaces import IObjectModifiedEvent

@configure.subscriber(for_=(IResource, IObjectModifiedEvent))
async def modified_object(obj, event):
    pass

Creating events

You are also able to create your own events to notify on:

from guillotina.interfaces import IObjectEvent
from zope.interface import implementer
from guillotina.event import notify

class ICustomEvent(IObjectEvent):
    pass

@implementer(IObjectEvent)
class CustomEvent:

    def __init__(self, object):
        self.object = object


await notify(CustomEvent(ob))

Events

  • guillotina.interfaces.IObjectEvent: every time anything happens to an object
  • guillotina.interfaces.IFileStartedUpload
  • guillotina.interfaces.IFileFinishUploaded
  • guillotina.interfaces.IFileBeforeFinishUploaded
  • guillotina.interfaces.IObjectLocationEvent: Base event for remove/rename
  • guillotina.interfaces.IObjectMovedEvent
  • guillotina.interfaces.IBeforeObjectMovedEvent
  • guillotina.interfaces.IObjectAddedEvent: when content added to folder
  • guillotina.interfaces.IObjectDuplicatedEvent
  • guillotina.interfaces.IBeforeObjectAddedEvent
  • guillotina.interfaces.IObjectRemovedEvent
  • guillotina.interfaces.IBeforeObjectRemovedEvent
  • guillotina.interfaces.IObjectModifiedEvent
  • guillotina.interfaces.IObjectPermissionsModifiedEvent
  • guillotina.interfaces.INewUserAdded

Commands

You can provide your own CLI commands for guillotina through a simple interface.

Available commands

  • serve: run the HTTP REST API server (this is the default command if none given)
  • shell: drop into a shell with root object to manually work with
  • create: use cookiecutter to generate guillotina applications
  • initialize-db: databases are automatically initialized; however, you can use this command to manually do it
  • testdata: populate the database with test data from wikipedia
  • run: run a python script. The file must have a function async def run(container):
  • gen-key: generate cryptographic key for jwt, jwk
  • dbvacuum: run optimized vacuuming process to clean up deleted objects

Command Options

    • --config: path to configuration file. defaults to config.(yaml|json)
    • --profile: profile Guillotina while it’s running
    • --profile-output: where to save profiling output
    • --monitor: run with aiomonitor requires aiomonitor
    • --line-profiler: use line_profiler requires line_profiler
    • --line-profiler-matcher: fnmatch of module/function to profile requires line_profiler
    • --line-profiler-output: to store output in a file requires line_profiler
    • --override: Override configuration values, Example: --override="root_user.password=foobar"
  • serve:

    • --host: host to bind to
    • --port: port to bind to
    • --reload: auto reload on code changes. requires aiohttp_autoreload
  • shell

  • create

    • --template: name of template to use
    • --overwrite: overwrite existing file
    • --output: where to save the file
  • initialize-db

  • testdata

    • --per-node: How many items to import per node
    • --depth: How deep to make the nodes
  • run

    • --script: path to script to run with run async function

Running commands

Guillotina provides two binaries to run commands through, bin/guillotina and a shortcut, bin/g.

To run a command, it’s just a positional argument on the running command::

bin/g shell

Creating commands

guillotina provides a simple API to write your own CLI commands.

Here is a minimalistic example:


from guillotina.commands import Command
class MyCommand(Command):

    def get_parser(self):
        parser = super(MyCommand, self).get_parser()
        # add command arguments here...
        return parser

    def run(self, arguments, settings, app):
        pass

Then, just add your command to your application’s app_settings in the __init__.py:


app_settings = {
    "commands": {
        "mycommand": "my.package.commands.MyCommand"
    }
}

Overridding configuration

First off, you can use the --override setting for all commands to provide setting overrides in files. For example, --override="root_user.password=foobar".

Additionally, you can override configuration with environment variables. To override the root password like above, you would do: G_root_user__password=foobar.

You can also override json data structures as well:

export G_cors__allow_origin='["http://localhost:8080"]'

Use the double underscore(__) to designate nested json structure to assign values.

Application Configuration

guillotina handles application configuration mostly with decorators.

For example, registering a new service uses our configuration decorator syntax:

from guillotina import configure
from guillotina.interfaces import IContainer

@configure.service(context=IContainer, name='@myservice', method='GET',
                   permission='guillotina.AccessContent')
async def my_service(context, request):
    return {
        'foo': 'bar'
    }

guillotina applications can override default guillotina configuration.

If multiple guillotina applications configure conflicting configurations, guillotina chooses the configuration according to the order the guillotina applications that are included.

A full reference of the available configure decorators can be found in the programming api reference section.

Design

This section is meant to explain and defend the design of guillotina.

JavaScript application development focus

One of the main driving factors behind the development of guillotina is to streamline the development of custom web applications.

Some of the technologies we support in order to be a great web application development platform are:

  • Everything is an API endpoint
  • JWT
  • Web sockets
  • Configuration is done with JSON
  • URL to object-tree data model

Speed

A primary focus of guillotina is speed. We take shortcuts and may use some ugly or less-well conceptually architected solutions in some areas in order to gain speed improvements.

Some of the decisions we made affect how applications and addons are designed. Mainly, we try to stay light on the amount of data we’re loading from the database where possible and we try to lower the number of lookups we do in certain scenarios.

That being said, guillotina is not a barebones framework. It provides a lot of functionality so it will never be as fast as say Pyramid.

“There are no solutions. There are only trade-offs.” - Thomas Sowell

Asynchronous

guillotina is asynchronous from the ground up, built on top of aiohttp using Python 3.6’s asyncio features.

Practically speaking, being built completely on asyncio compatible technologies, guillotina does not block for network IO to the database, index catalog, redis, etc or whatever you’ve integrated.

Additionally, we have support for async utilities that run in the same async loop and async content events.

Finally, we also support web sockets OOTB.

Security

Guillotina uses the same great security infrastructure that Plone has been using for the last 15 years which allows you to define permissions, roles, groups, users and customize all of them contextually based on where the content is located in your container.

Style

Stylistically, guillotina pulls ideas from the best web frameworks:

  • YAML/JSON configuration
  • Pyramid-like idioms and syntax where it makes sense
  • Functions + decorators over classes

Persistence

There are three kinds of objects that are considered on the system:

  • Tree objects: objects are resources that implement guillotina.interfaces.IResource. This object has a __name__ and a __parent__ property that indicate the id on the tree and the link to the parent. By themselves they don’t have access to their children, they need to interact with the transaction object to get them.
  • Annotations: objects that are associated with tree objects. These can be any type of data. In Guillotina, the main source of annotation objects are behaviors.

Saving objects

If you’re manually modifying objects in services(or views) without using the serialization adapters, you need to register the object to be saved to the database. To do this, just use the _p_register() method.

from guillotina import configure
@configure.service(
    method='PATCH', name='@dosomething')
async def matching_service(context, request):
    context.foobar = 'foobar'
    context._p_register()

Transactions

Guillotina automatically manages transactions for you in services; however, if you have long running services and need to flush data to the database, you can manually manage transactions as well.

from guillotina.transactions import get_tm

tm = get_tm()
await tm.commit()  # commit current transaction
await tm.begin()  # start new one

There is also an async context manager:

from guillotina.transactions import managed_transaction

async with managed_transaction() as txn:
    # modify objects

Blobs

guillotina provides basic blob file persistency support. These blobs are still stored in the database.

Registering blobs

Blobs must be registered with and stored on a resource object. This is so we can do garbage collection on the blobs that were created for resources.


from guillotina.blob import Blob

blob = Blob(resource)
resource.blob = blob
blobfi = blob.open('w')

await blobfi.async_write(b'foobar')
assert await blobfi.async_read() == b'foobar'

Guillotina automatically reads and writes chunks of blob data from the database.

Router

Guillotina uses aiohttp for it’s webserver. In order to route requests against Guillotina’s traversal url structure, Guillotina provides it’s own router that does traversal: guillotina.traversal.router.

How URLs are routed

Guillotina’s content is structured like a file system. Objects are routed to URL paths. HTTP verbs are provided against those objects on those paths. Additional services(or views depending on terminology) are provided with URL path parts that start with @, for example, the @move endpoint.

Route matching

With Guillotina, you can also route custom sub paths off a registered service. Guillotina is primarily for routing objects to urls; however, this feature is used to provide additional parameters to the service.

An example of where this is used is for file services: /db/container/item/@upload/file.

Registering custom route parts

from guillotina import configure
@configure.service(
    method='GET', permission='guillotina.AccessContent',
    name='@match/{foo}/{bar}')
async def matching_service(context, request):
    return request.matchdict  # will return {'foo': 'foo', 'bar': 'bar'}

Some caveats need to be considered when mixing in routing:

  • matches are only done when traversal misses
  • there are limits to the variability of the route scheme you use. For example @foobar/{one}/{two} and @foobar/one/two will be converted into the same service registration; however, the former will match against variable paths and the later will only match @foobar/one/two. So you might run into restrictions quickly if you’re trying to do complex routing.

Providing your own router

Guillotina allows you to provide your own customized router using the router settings.

Here is an example router that provides /v1 and /v2 type url structure:

from guillotina import configure
from guillotina.content import Resource
from guillotina.interfaces import IContainer
from guillotina.interfaces import IDefaultLayer
from guillotina.interfaces import IRequest
from guillotina.interfaces import IResource
from guillotina.traversal import TraversalRouter
from guillotina.traversal import traverse
from zope.interface import alsoProvides


class IV1Layer(IDefaultLayer):
    pass


class IV2Layer(IDefaultLayer):
    pass


@configure.service(method='GET', name='@foobar',
                   permission='guillotina.AccessContent',
                   layer=IV1Layer)
async def v1_service(context, request):
    return {
        'version': '1'
    }


@configure.service(method='GET', name='@foobar',
                   permission='guillotina.AccessContent',
                   layer=IV2Layer)
async def v2_service(context, request):
    return {
        'version': '2'
    }


@configure.contenttype(type_name="VersionRouteSegment")
class VersionRouteSegment(Resource):

    type_name = 'VersionRouteSegment'

    def __init__(self, name, parent):
        super().__init__()
        self.__name__ = self.id = name
        self.__parent__ = parent


class MyRouter(TraversalRouter):
    async def traverse(self, request: IRequest) -> IResource:
        resource, tail = await super().traverse(request)
        if len(tail) > 0 and tail[0] in ('v1', 'v2') and IContainer.providedBy(resource):
            segment = VersionRouteSegment(tail[0], resource)
            if tail[0] == 'v1':
                alsoProvides(request, IV1Layer)
            elif tail[0] == 'v2':
                alsoProvides(request, IV2Layer)

            if len(tail) > 1:
                # finish traversal from here
                return await traverse(request, segment, tail[1:])
            else:
                resource = segment
                tail = tail[1:]
        return resource, tail


app_settings = {
    # provide custom application settings here...
    'router': MyRouter
}

Exceptions

Exceptions during the rendering of API calls are wrapped, logged and provided generic http status codes by default.

Guillotina provides a mechanism for customizing the status codes and type of responses given depending on the exception type.

Custom exception response

from aiohttp.web_exceptions import HTTPPreconditionFailed
from guillotina import configure
from guillotina.interfaces import IErrorResponseException

import json


@configure.adapter(
    for_=json.decoder.JSONDecodeError,
    provides=IErrorResponseException)
def json_decode_error_response(exc, error='', eid=None):
    return HTTPPreconditionFailed(
        reason=f'JSONDecodeError: {eid}')

Fields

Guillotina uses schemas to define content types and behaviors. These schemas consist of field definitions.

Available fields

  • guillotina.schema.Bool
  • guillotina.schema.Bytes
  • guillotina.schema.Choice: validates against vocabulary of values
  • guillotina.schema.Date
  • guillotina.schema.Datetime
  • guillotina.schema.Decimal
  • guillotina.schema.Dict
  • guillotina.schema.Float
  • guillotina.schema.Int
  • guillotina.schema.JSONField
  • guillotina.schema.List
  • guillotina.schema.Set
  • guillotina.schema.Text
  • guillotina.schema.TextLine
  • guillotina.schema.Time
  • guillotina.fields.PatchField: allow updating value without patching entire value
  • guillotina.fields.BucketListField: optimized storage for very large lists of data
  • guillotina.fields.BucketDictField: optimized storage for very large dictionaries of data
  • guillotina.files.CloudFileField: file field for storing in db or cloud storage

Patch field

Guillotina provides a PatchField which allows you to patch values of List, Dict and Int fields without having the original value.

Patch field list

from zope.interface import Interface
from guillotina.fields import PatchField
from guillotina import schema

class IMySchema(Interface):
    values = PatchField(schema.List(
        value_type=schema.Text()
    ))

Then, payload for patching to append to this list would look like:

{
    "values": {
        "op": "append",
        "value": "foobar"
    }
}

Append if unique value only:

{
    "values": {
        "op": "appendunique",
        "value": "foobar"
    }
}

Extend:

{
    "values": {
        "op": "extend",
        "value": ["foo", "bar"]
    }
}

Extend if unique values:

{
    "values": {
        "op": "extendunique",
        "value": ["foo", "bar"]
    }
}

Delete:

{
    "values": {
        "op": "del",
        "value": 0
    }
}

Remove:

{
    "values": {
        "op": "remove",
        "value": "foobar"
    }
}

Update:

{
    "values": {
        "op": "update",
        "value": {
            "index": 0,
            "value": "Something new"
        }
    }
}

Patch dict field

from zope.interface import Interface
from guillotina.fields import PatchField
from guillotina import schema

class IMySchema(Interface):
    values = PatchField(schema.Dict(
        key_type=schema.Text(),
        value_type=schema.Text()
    ))

Then, payload for patching to add to this dict would look like:

{
    "values": {
        "op": "assign",
        "value": {
            "key": "foo",
            "value": "bar"
        }
    }
}

Delete:

{
    "values": {
        "op": "del",
        "value": "foo"
    }
}

Update:

{
    "values": {
        "op": "update",
        "value": [{
            "key": "foo",
            "value": "bar"
        }, {
            "key": "foo2",
            "value": "bar2"
        }]
    }
}

Patch int field

PatchField can also be used on Int fields to increment, decrement or reset their original value.

from zope.interface import Interface
from guillotina.fields import PatchField
from guillotina import schema

class IMySchema(Interface):
    counter = PatchField(schema.Int(
        title='My Counter',
        default=1,
    ))

The payload to increment counter by 3 units would look like:

{
    "counter": {
        "op": "inc",
        "value": 3
    }
}

Notice that, at this point, counter will be set to 4 because its default value is 1. If the default would not be set, the increment operation assumes a 0, and thus counter would be 3.

Likewise, to decrement the field, the following payload would work:

{
    "counter": {
        "op": "dec",
        "value": 4
    }
}

To reset counter to its default value, you can send the following payload without value:

{
    "counter": {
        "op": "reset"
    }
}

and counter will be set to its default value 1. Otherwise, you can also send the target reset value:

{
    "counter": {
        "op": "reset",
        "value": 0
    }
}

Notice that a reset operation on a integer without a default value is equivalent to sending a value of 0.

Bucket list field

from zope.interface import Interface
from guillotina.fields import BucketListField
from guillotina import schema

class IMySchema(Interface):
    values = BucketListField(
        value_type=schema.Text(),
        bucket_len=5000
    )

Then, payload for patching to append to this list would look like:

{
    "values": {
        "op": "append",
        "value": "foobar"
    }
}

Extend:

{
    "values": {
        "op": "extend",
        "value": ["foo", "bar"]
    }
}

Delete:

{
    "values": {
        "op": "del",
        "value": {
            "bucket_index": 0,
            "item_index": 0
        }
    }
}

Bucket dict field

from zope.interface import Interface
from guillotina.fields import BucketDictField
from guillotina import schema

class IMySchema(Interface):
    values = BucketDictField(
        key_type=schema.Text(),
        value_type=schema.Text(),
        bucket_len=5000
    )

Then, payload for patching would be…:

{
    "values": {
        "op": "assign",
        "value": {
            "key": "foo",
            "value": "bar"
        }
    }
}

Update:

{
    "values": {
        "op": "update",
        "value": [{
            "key": "foo",
            "value": "barupdated"
        }, {
            "key": "other",
            "value": "othervalue"
        }]
    }
}

Delete:

{
    "values": {
        "op": "del",
        "value": "foo"
    }
}

Serializing content

Guillotina provides ways to hook into the content serialization that is done when you retreive content from the API.

All serializers are:

  • adapters of the context and current request
  • async callable objects that return a dictionary of json compatible data

The training doc has a great introduction on customizing the serialization for content.

Serialization interfaces:

  • guillotina.interfaces.IResourceSerializeToJson: Provides full content
  • guillotina.interfaces.IResourceSerializeToJsonSummary: Summary of content
  • guillotina.interfaces.IFactorySerializeToJson: Information on conten type
  • guillotina.interfaces.ISchemaSerializeToJson: Serialize full schema
  • guillotina.interfaces.ISchemaFieldSerializeToJson: Feild of a schema
  • guillotina.interfaces.ICatalogDataAdapter: Searchable data(for adapters like elasticsearch)
  • guillotina.interfaces.ISecurityInfo: minimal data used for changes to security of object required by catalog adapters.
  • guillotina.db.interfaces.IJSONDBSerializer: Content serialized to db in addition to pickled data. Used with the JSONB field in postgresql. By default this is the same as ICatalogDataAdapter.

Type serialization

You can customize the any serialization by providing override adapters.

For example, to customize the default summary serialization of your custom type:

from guillotina import configure
from guillotina.interfaces import IResourceSerializeToJsonSummary
from guillotina.json.serialize_content import DefaultJSONSummarySerializer
from zope.interface import Interface


@configure.adapter(
    for_=(IMyContext, Interface),
    provides=IResourceSerializeToJsonSummary)
class ConversationJSONSummarySerializer(DefaultJSONSummarySerializer):
    async def __call__(self):
        data = await super().__call__()
        data.update({
            'creation_date': self.context.creation_date,
            'title': self.context.title,
            'users': self.context.users
        })
        return data

JSON DB

By default, store_json is false in the application settings. To activate this feature, make sure to set store_json: true in your yaml configuration.

To customize the json serialized to the database for your application or type:

from guillotina.db.interfaces import IJSONDBSerializer
from guillotina import configure

@configure.adapter(
    for_=IMyType,
    provides=IJSONDBSerializer)
class JSONDBSerializer(DefaultCatalogDataAdapter):
    async def __call__(self):
        return {
            'foo': 'bar'
        }

AsyncIO Utilities

Guillotina comes with some amazing utilities to make working with AsyncIO a bit easier.

Of those, Guillotina provides functions to run asychronous functions in a queue or a pool.

For example, sending an email:


from guillotina import configure
from guillotina.utils import execute


async def send_email(to_, from_, subject, body):
    pass


@configure.service(name='@myservice', method='GET',
                   permission='guillotina.AccessContent')
async def my_service(context, request):
    execute.in_pool(
        send_email, 'foo@bar', 'foo@bar', 'Hello!', 'Some body!').after_request()
    return {
        'foo': 'bar'
    }

This will execute the function send_email in an asynchronous pool after the request is finished.

The functions execute.in_queue, execute.in_queue_with_func, execute.after_commit and execute.before_commit are also available.

See the full specification.

Component Architecture

Guillotina is built on a component architecture. The component architecture uses adapter and singleton software design patterns to help manage complexity. It allows users to register and lookup adapters and utility defined against interfaces.

Why

program to an interface, not an implementation
favor object composition over class inheritance
keep objects stupid

The component architecture is a powerful tool to help you build complex software that needs to be extensible. In software engineering, adapters and singletons are used often so it is a natural pattern to build on.

Almost any component/functionality in Guillotina can be overridden in an add-on application by overriding Guillotina’s components.

Basics

To query an adapter on content:

from guillotina.component import get_adapter
from guillotina.interfaces import IResource
from zope.interface import Interface
from guillotina import configure

class IMyAdapter(Interface):
    pass

@configure.adapter(for_=IResource, provides=IMyAdapter)
class MyAdapter:
    def __init__(self, ob):
        self.ob = ob

    def do_something(self):
        pass

adapter = get_adapter(ob, IMyAdapter)
adapter.do_something()

To query for a utility(which is what we call singletons), it’s similiar:

from guillotina.component import get_utility
from guillotina.interfaces import IPermission
permission = get_utility(IPermission, name='guillotina.AccessContent')

Details

To describe power of this approach, we’ll go through using adapters without the registration and lookup and then with the component architecture.

Adapters without CA

class Automobile:
    wheels = 4

    def __init__(self):
        pass


class Motorcycle(Automobile):
    wheels = 2


class SemiTruck(Automobile):
    wheels = 18


class Operate:

    def __init__(self, automobile: Automobile):
        self.automobile = automobile

    def drive(self):
        pass


class OperateMotocycle:

    def drive(self):
        pass


class OperateSemi:

    def drive(self):
        pass

Then, to use these adapters, you might do something like:

if isinstance(auto, SemiTruck):
    operate = OperateSemi(auto)
elif isinstance(auto, Motorcycle):
    operate = OperateMotocycle(auto)
else:
    operate = Operate(auto)
operate.drive()

Adapters with CA

from guillotina import configure
from zope.interface import Attribute, Interface, implementer


class IAutomobile(Interface):
    wheels = Attribute('number of wheels')


class IMotocycle(IAutomobile):
    pass


class ISemiTruck(IAutomobile):
    pass


@implementer(IAutomobile)
class Automobile:
    wheels = 4


@implementer(IMotocycle)
class Motorcycle(Automobile):
    wheels = 2


@implementer(ISemiTruck)
class SemiTruck(Automobile):
    wheels = 18


class IOperate(Interface):
    def drive():
        pass


@configure.adapter(for_=IAutomobile, provides=IOperate)
class Operate:

    def __init__(self, automobile: Automobile):
        self.automobile = automobile

    def drive(self):
        return 'driving automobile'


@configure.adapter(for_=IMotocycle, provides=IOperate)
class OperateMotocycle(Operate):

    def drive(self):
        return 'driving motocycle'


@configure.adapter(for_=ISemiTruck, provides=IOperate)
class OperateSemi(Operate):

    def drive(self):
        return 'driving semi'

Then, to use it:

from guillotina.component import get_adapter
semi = SemiTruck()
operate = get_adapter(semi, IOperate)
operate.drive()

Debugging

Debugging Guillotina will be slightly different from other web application that are not asyncio but Guillotina provides some nice integration to help you out.

X-Debug header

Any any request, you can provide the header X-Debug:1 and Guillotina will output debugging headers in the response about timing, number of queries and cache hit/miss stats.

GET /(db)/(container)

Retrieves serialization of resource

  • Permission: guillotina.ViewContent
  • Context: guillotina.interfaces.content.IResource

http

GET /db/container HTTP/1.1
Accept: application/json
Authorization: Basic cm9vdDpyb290
X-Debug: 1

curl

curl -i http://nohost/db/container -H 'Accept: application/json' -H 'X-Debug: 1' --user root:root

httpie

http http://nohost/db/container Accept:application/json X-Debug:1 -a root:root

response

HTTP/1.1 200 OK
Content-Length: 512
Content-Type: application/json
XG-Num-Queries: 0
XG-Request-Cache-hits: 0
XG-Request-Cache-misses: 3
XG-Request-Cache-stored: 3
XG-Timing-0-start: 0.24748
XG-Timing-1-traversed: 0.34022
XG-Timing-2-beforeauthentication: 0.06580
XG-Timing-3-authentication: 0.11826
XG-Timing-4-viewfound: 0.13995
XG-Timing-5-authorization: 0.54264
XG-Timing-6-viewrender: 0.04911
XG-Timing-7-viewrendered: 0.81587
XG-Timing-8-renderer: 0.16475
XG-Timing-9-headers: 0.02027
XG-Timing-Total: 2.50435
XG-Total-Cache-hits: 0
XG-Total-Cache-misses: 6
XG-Total-Cache-stored: 6

{
    "@id": "http://127.0.0.1:60361/db/container",
    "@type": "Container",
    "@name": "container",
    "@uid": "380cfe6d1baf4d1a99f7d29a9440fb51",
    "@static_behaviors": [],
    "parent": {},
    "is_folderish": true,
    "creation_date": "2019-08-24T12:41:32.767383+00:00",
    "modification_date": "2019-08-24T12:41:32.767383+00:00",
    "UID": "380cfe6d1baf4d1a99f7d29a9440fb51",
    "type_name": "Container",
    "title": "container",
    "uuid": "380cfe6d1baf4d1a99f7d29a9440fb51",
    "__behaviors__": [],
    "__name__": "container",
    "items": [],
    "length": 0
}
Query Parameters:
 
  • include (string) –
  • omit (string) –
Status Codes:

GDEBUG

On startup, you can also provide the environment variable GDEBUG=true. This will provide detailed query statistics with the X-Debug:1.

aiomonitor

Guillotina also provides integration with the aiomonitor python module. This module allows you to attach to a running python with asyncio to inspect the active tasks running.

First, install aiomonitor:

pip install aiomonitor

Then, run guillotina with --monitor:

g server --monitor

Finally, connect to it:

python -m aiomonitor.cli

Jupyter

Guillotina also works with Jupyter notebooks. Load the example notebook in the guillotina repository to see an example of how to get started.

Deploying

References

Programming API Reference

This is not a complete reference but a reference of modules which seem most useful.

guillotina.configure

guillotina.configure.scan(path)

Load a module dotted name.

Parameters:path (str) – dotted name
guillotina.configure.json_schema_definition(name, schema)

Register a json schema definition

Parameters:
  • name (str) – Name of schema
  • schema (dict) – schema definition, must be json compatible
Return type:

None

guillotina.configure.service(**kwargs)

Configure a service

>>> from guillotina import configure
>>> @configure.service(name='@foobar')
    async def foobar_service(context, request): return {'foo': 'bar'}
Parameters:
  • context (Interface) – Content type interface this service is registered against
  • method (str) – HTTP method this service works against. Defaults to GET.
  • permission (str) – Permission this service requires
  • layer (str) – Layer this service is registered for. Default is IDefaultLayer
  • name – This is used as part of the uri. Example @foobar -> /mycontent/@foobar.
  • summary – Used for documentation and swagger.
  • description – Used for documentation and swagger.
  • responses – Used for documentation and swagger.
  • parameters – Used for documentation and swagger.
guillotina.configure.contenttype(**kwargs)

Configure content type

>>> from guillotina import configure
>>> from guillotina.content import Item
>>> @configure.contenttype(type_name="Foobar")
    class Foobar(Item): pass
Parameters:
  • type_name (str) – Name of the content type
  • schema (str) – Schema to use for content type
  • add_permission (str) – Permission required to add content. Defaults to guillotina.AddContent
  • allowed_types (list) – List of types allowed to be added inside this content assuming it is a Folder type. Defaults to allowing all types.
  • behaviors (str) – List of behaviors to enable for this type.
  • factory – Dotted name to custom factory to use. See guillotina.content.ResourceFactory for default implementation
guillotina.configure.behavior(**kwargs)

Configure behavior

>>> from guillotina import configure
>>> from guillotina.behaviors.instance import ContextBehavior
>>> class IMyBehavior(Interface): pass
>>> @configure.behavior(
      title="Dublin Core fields",
      provides=IMyBehavior,
      for_="guillotina.interfaces.IResource")
    class MyBehavior(ContextBehavior): pass
Parameters:
  • title – Title of behavior
  • provides – Schema to use for behavior
  • behavior – Marker interface to apply to utilized instance’s behavior
  • for_ – Content type this behavior is available for
guillotina.configure.vocabulary(**kwargs)

Configure vocabulary

>>> from guillotina import configure
>>> @configure.vocabulary(name="myvocab")
    class MyVocab:
      def __init__(self, context):
        self.context = context
        self.values = range(10)
      def __iter__(self):
        return iter([])
      def __contains__(self, value):
        return value in self.values
      def __len__(self):
        return len(self.values)
      def getTerm(self, value):
        return 'value'
Parameters:name – Reference of the vocabulary to get it
guillotina.configure.addon(**kwargs)

Configure addon

>>> from guillotina import configure
>>> @configure.addon(
      name="docaddon",
      title="Doc addon",
      dependencies=["cms"])
    class TestAddon(Addon): pass
Parameters:
  • name – Unique name of addon
  • title – Title of addon
  • dependencies – List of names of dependency addons
guillotina.configure.adapter(**kwargs)

Configure adapter

Parameters:
  • for_ – Type or list of types this subscriber is for: required
  • provides – Interface this adapter provides
guillotina.configure.utility(**kwargs)

Configure utility

Parameters:
  • provides – Interface this utility provides
  • name – Optional to name the utility
guillotina.configure.permission(**kwargs)

Configure permission

Parameters:
  • id
  • title
  • description
guillotina.configure.role(**kwargs)

Configure role

Parameters:
  • id
  • title
  • description
  • local (bool) – defaults to True
guillotina.configure.grant(**kwargs)

Configure granting permission to role

Parameters:
  • role
  • principal
  • permission
  • permissions
guillotina.configure.value_serializer(type)

Configure a value serializer

>>> @configure.value_serializer(bytes)
>>> def bytes_converter(value): return b64encode(value)
Parameters:type – type to serialize
guillotina.configure.value_deserializer(field_type)

Configure a value deserializer

>>> @configure.value_deserializer(IText)
>>> def field_converter(field, value, context): return value
Parameters:field_type – type of field to deserialize
guillotina.configure.renderer(**kwargs)

Configure a renderer

>>> @configure.renderer(name='text/plain')
>>> class RendererPlain(StringRenderer):
      content_type = 'text/plain'
Parameters:name – content type the renderer can be used for
guillotina.configure.language(name)

Configure a language

>>> @configure.language(name='en')
>>> class EN(DefaultLanguage): pass
Parameters:name – Name of language

guillotina.content

class guillotina.content.Resource(id=None)

Base resource object class

acl

Access control list stores security information on the object

Return type:dict
add_behavior(iface)

We need to apply the marker interface.

value: Interface to add

Return type:None
remove_behavior(iface)

We need to apply the marker interface.

value: Interface to add

Return type:None
uuid

The unique id of the content object

class guillotina.content.Item(id=None)

Basic item content type. Inherits from Resource

class guillotina.content.Folder(id=None)

Basic folder content type. Inherits from Resource but provides interface to work with contained objects asynchronously.

async_contains(key)

Asynchronously check if key exists inside this folder

Parameters:key (str) – key of child object to check
Return type:bool
async_del(key)

Asynchronously delete object in the folder

Parameters:key (str) – key of child objec to delete
Return type:None
async_get(key, default=None, suppress_events=False)

Asynchronously get an object inside this folder

Parameters:key (str) – key of child object to get
Return type:Resource
async_items(suppress_events=False)

Asynchronously iterate through contents of folder

Return type:Asynciterator[Tuple[str, Resource]]
async_keys()

Asynchronously get the sub object keys in this folder

Return type:List[str]
async_len()

Asynchronously calculate the len of the folder

Return type:int
async_multi_get(keys, default=None, suppress_events=False)

Asynchronously get an multiple objects inside this folder

Parameters:keys (List[str]) – keys of child objects to get
Return type:Asynciterator[Tuple[Resource]]
async_set(key, value)

Asynchronously set an object in this folder

Parameters:
  • key (str) – key of child object to set
  • value (Resource) – object to set as child
Return type:

None

class guillotina.content.Container(id=None)
guillotina.content.create_content_in_container(parent, type_, id_, request=None, check_security=True, **kw)

Utility to create a content.

This method is the one to use to create content. id_ can be None

Parameters:
  • parent (Folder) – where to create content inside of
  • type – content type to create
  • id – id to give content in parent object
  • request (Optional[<InterfaceClass guillotina.interfaces.IRequest>]) – <optional>
  • check_security – be able to disable security checks
Return type:

Resource

guillotina.content.get_all_possible_schemas_for_type(type_name)
Return type:List[<InterfaceClass zope.interface.Interface>]
guillotina.content.iter_schemata(obj)
Return type:Iterator[<InterfaceClass zope.interface.Interface>]
guillotina.content.move(context, destination=None, new_id=None, check_permission=True)
Return type:None
guillotina.content.duplicate(context, destination=None, new_id=None, check_permission=True)
Return type:<InterfaceClass guillotina.interfaces.content.IResource>

guillotina.contentapi

class guillotina.contentapi.ContentAPI(db, user=<guillotina.auth.users.RootUser object>)

guillotina.request

class guillotina.request.Request(*args, **kwargs)

Bases: aiohttp.web_request.Request

Guillotina specific request type. We store potentially a lot of state onto the request object as it is essential our poor man’s thread local model

add_future(name, fut, scope='', args=None, kwargs=None)

Register a future to be executed after the request has finished.

Parameters:
  • name (str) – name of future
  • fut (Callable[…, Coroutine[Any, Any, Any]]) – future to execute after request
  • scope (str) – group the futures to execute different groupings together
  • args – arguments to execute future with
  • kwargs – kwargs to execute future with
execute_futures(scope='')

Execute all the registered futures in a new task

Parameters:scope (str) – scoped futures to execute. Leave default for normal behavior
get_future(name, scope='')

Get a registered future

Parameters:
  • name (str) – scoped futures to execute. Leave default for normal behavior
  • scope (str) – scope name the future was registered for
matchdict = None

Dictionary of matched path parameters on request

record(event_name)

Record event on the request

Parameters:event_name (str) – name of event

guillotina.response

class guillotina.response.Response(*, content=None, headers=None, status=None)

Bases: Exception

__init__(*, content=None, headers=None, status=None)
Parameters:
  • content (Optional[dict]) – content to serialize
  • headers (Optional[dict]) – headers to set on response
  • status (Optional[int]) – customize the response status
Return type:

None

class guillotina.response.ErrorResponse(type, message, *, reason=None, content=None, headers=None, status=500)

Bases: guillotina.response.Response

__init__(type, message, *, reason=None, content=None, headers=None, status=500)
Parameters:
  • type (str) – type of error
  • message (str) – error message
  • content (Optional[dict]) – provide additional content
  • headers (Optional[dict]) – headers to set on response
  • status (int) – customize the response status
Return type:

None

class guillotina.response.HTTPError(*, content=None, headers=None, status=None)

Bases: guillotina.response.Response

Base class for exceptions with status codes in the 400s and 500s.

class guillotina.response.HTTPRedirection(*, content=None, headers=None, status=None)

Bases: guillotina.response.Response

Base class for exceptions with status codes in the 300s.

class guillotina.response.HTTPSuccessful(*, content=None, headers=None, status=None)

Bases: guillotina.response.Response

Base class for exceptions with status codes in the 200s.

class guillotina.response.HTTPOk(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPSuccessful

class guillotina.response.HTTPCreated(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPSuccessful

class guillotina.response.HTTPAccepted(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPSuccessful

class guillotina.response.HTTPNonAuthoritativeInformation(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPSuccessful

class guillotina.response.HTTPNoContent(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPSuccessful

class guillotina.response.HTTPResetContent(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPSuccessful

class guillotina.response.HTTPPartialContent(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPSuccessful

class guillotina.response.HTTPMultipleChoices(location, *, content=None, headers=None)

Bases: guillotina.response._HTTPMove

Parameters:
  • location (str) – where to redirect
  • headers (Optional[dict]) – additional headers to set
__init__(location, *, content=None, headers=None)
Parameters:
  • content (Optional[dict]) – content to serialize
  • headers (Optional[dict]) – headers to set on response
  • status – customize the response status
Return type:

None

class guillotina.response.HTTPMovedPermanently(location, *, content=None, headers=None)

Bases: guillotina.response._HTTPMove

Parameters:
  • location (str) – where to redirect
  • headers (Optional[dict]) – additional headers to set
__init__(location, *, content=None, headers=None)
Parameters:
  • content (Optional[dict]) – content to serialize
  • headers (Optional[dict]) – headers to set on response
  • status – customize the response status
Return type:

None

class guillotina.response.HTTPFound(location, *, content=None, headers=None)

Bases: guillotina.response._HTTPMove

Parameters:
  • location (str) – where to redirect
  • headers (Optional[dict]) – additional headers to set
__init__(location, *, content=None, headers=None)
Parameters:
  • content (Optional[dict]) – content to serialize
  • headers (Optional[dict]) – headers to set on response
  • status – customize the response status
Return type:

None

class guillotina.response.HTTPSeeOther(location, *, content=None, headers=None)

Bases: guillotina.response._HTTPMove

Parameters:
  • location (str) – where to redirect
  • headers (Optional[dict]) – additional headers to set
__init__(location, *, content=None, headers=None)
Parameters:
  • content (Optional[dict]) – content to serialize
  • headers (Optional[dict]) – headers to set on response
  • status – customize the response status
Return type:

None

class guillotina.response.HTTPNotModified(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPRedirection

class guillotina.response.HTTPUseProxy(location, *, content=None, headers=None)

Bases: guillotina.response._HTTPMove

__init__(location, *, content=None, headers=None)
Parameters:
  • content (Optional[dict]) – content to serialize
  • headers (Optional[dict]) – headers to set on response
  • status – customize the response status
Return type:

None

class guillotina.response.HTTPTemporaryRedirect(location, *, content=None, headers=None)

Bases: guillotina.response._HTTPMove

__init__(location, *, content=None, headers=None)
Parameters:
  • content (Optional[dict]) – content to serialize
  • headers (Optional[dict]) – headers to set on response
  • status – customize the response status
Return type:

None

class guillotina.response.HTTPPermanentRedirect(location, *, content=None, headers=None)

Bases: guillotina.response._HTTPMove

__init__(location, *, content=None, headers=None)
Parameters:
  • content (Optional[dict]) – content to serialize
  • headers (Optional[dict]) – headers to set on response
  • status – customize the response status
Return type:

None

class guillotina.response.HTTPClientError(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPError

class guillotina.response.HTTPBadRequest(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPUnauthorized(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPPaymentRequired(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPForbidden(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPNotFound(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.InvalidRoute(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPNotFound

The defined route is invalid

class guillotina.response.HTTPMethodNotAllowed(method, allowed_methods, *, content=None, headers=None)

Bases: guillotina.response.HTTPClientError

__init__(method, allowed_methods, *, content=None, headers=None)
Parameters:
  • method (str) – method not allowed
  • allowed_methods (list) – list of allowed methods
  • content (Optional[dict]) – content to serialize
  • headers (Optional[dict]) – headers to set on response
Return type:

None

class guillotina.response.HTTPNotAcceptable(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPProxyAuthenticationRequired(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPRequestTimeout(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPConflict(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPGone(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPLengthRequired(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPPreconditionFailed(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPRequestEntityTooLarge(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPRequestURITooLong(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPUnsupportedMediaType(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPRequestRangeNotSatisfiable(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPExpectationFailed(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPMisdirectedRequest(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPUnprocessableEntity(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPFailedDependency(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPUpgradeRequired(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPPreconditionRequired(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPTooManyRequests(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPRequestHeaderFieldsTooLarge(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPUnavailableForLegalReasons(link, *, content=None, headers=None)

Bases: guillotina.response.HTTPClientError

class guillotina.response.HTTPServerError(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPError

class guillotina.response.HTTPInternalServerError(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPServerError

class guillotina.response.HTTPNotImplemented(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPServerError

class guillotina.response.HTTPBadGateway(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPServerError

class guillotina.response.HTTPServiceUnavailable(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPServerError

class guillotina.response.HTTPGatewayTimeout(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPServerError

class guillotina.response.HTTPVersionNotSupported(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPServerError

class guillotina.response.HTTPVariantAlsoNegotiates(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPServerError

class guillotina.response.HTTPInsufficientStorage(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPServerError

class guillotina.response.HTTPNotExtended(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPServerError

class guillotina.response.HTTPNetworkAuthenticationRequired(*, content=None, headers=None, status=None)

Bases: guillotina.response.HTTPServerError

guillotina.schema

class guillotina.schema.Bool(title='', description='', __name__='', required=True, readonly=False, constraint=None, default=None, defaultFactory=None, missing_value=<object object>, **kw)

A field representing a Bool.

from_unicode(str)
>>> b = Bool()
>>> IFromUnicode.providedBy(b)
True
>>> b.from_unicode('True')
True
>>> b.from_unicode('')
False
>>> b.from_unicode('true')
True
>>> b.from_unicode('false') or b.from_unicode('False')
False
class guillotina.schema.Bytes(min_length=0, max_length=None, **kw)

Field containing a byte string (like the python str).

The value might be constrained to be with length limits.

from_unicode(uc)

See IFromUnicode.

class guillotina.schema.Choice(values=None, vocabulary=None, source=None, **kw)

Choice fields can have a value found in a constant or dynamic set of values given by the field definition.

bind(object)

See guillotina.schema._bootstrapinterfaces.IField.

from_unicode(str)

See IFromUnicode.

class guillotina.schema.Date(min=None, max=None, default=None, **kw)

Field containing a date.

class guillotina.schema.Datetime(*args, **kw)

Field containing a DateTime.

class guillotina.schema.Decimal(*args, **kw)

Field containing a Decimal.

from_unicode(uc)

See IFromUnicode.

class guillotina.schema.Dict(key_type=None, value_type=None, **kw)

A field representing a Dict.

bind(object)

See guillotina.schema._bootstrapinterfaces.IField.

class guillotina.schema.Float(*args, **kw)

Field containing a Float.

from_unicode(uc)

See IFromUnicode.

class guillotina.schema.Int(*args, **kw)

A field representing an Integer.

from_unicode(str)
>>> f = Int()
>>> f.from_unicode("125")
125
>>> f.from_unicode("125.6") #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
ValueError: invalid literal for int(): 125.6
class guillotina.schema.JSONField(schema='{"type": "object", "properties": {}}', **kw)
class guillotina.schema.List(value_type=None, unique=False, **kw)

A field representing a List.

class guillotina.schema.Set(**kw)

A field representing a set.

class guillotina.schema.Text(*args, **kw)

A field containing text used for human discourse.

from_unicode(str)
>>> t = Text(constraint=lambda v: 'x' in v)
>>> t.from_unicode(b"foo x spam")
Traceback (most recent call last):
...
WrongType: ('foo x spam', <type 'unicode'>, '')
>>> t.from_unicode("foo x spam")
u'foo x spam'
>>> t.from_unicode("foo spam")
Traceback (most recent call last):
...
ConstraintNotSatisfied: ('foo spam', '')
class guillotina.schema.TextLine(*args, **kw)

A text field with no newlines.

class guillotina.schema.Time(min=None, max=None, default=None, **kw)

Field containing a time.

class guillotina.fields.PatchField(field, *args, **kwargs)
class guillotina.fields.BucketListField(*args, value_type=None, bucket_len=5000, annotation_prefix='bucketlist-', **kwargs)
class guillotina.fields.BucketDictField(*args, key_type=None, value_type=None, bucket_len=1000, annotation_prefix='bucketdict-', **kwargs)
class guillotina.fields.CloudFileField(**kw)

A cloud file hosted file.

Its configured on config.json with :

“cloud_storage”: “guillotina.interfaces.IS3FileField”

or

“cloud_storage”: “guillotina_gcloudstorage.interfaces.IGCloudFileField”

guillotina.component

guillotina.component.get_adapter(object, interface=<InterfaceClass zope.interface.Interface>, name='', context=None, args=[], kwargs={})

Get a registered adapter

Parameters:
  • object – Object to get adapter for
  • interface – What interface should the adapter provide
  • name – if it is a named adapter
  • args – args to provide the adapter constructor
  • kwargs – kwargs to provide the adapter constructor
Raises:

ComponentLookupError

guillotina.component.get_adapters(objects, provided, context=None)

Get a registered adapter

Parameters:
  • objects – Tuple of objects
  • provided – What interface should the adapter provide
guillotina.component.get_multi_adapter(objects, interface=<InterfaceClass zope.interface.Interface>, name='', context=None, args=[], kwargs={})

Get a registered multi adapter

Parameters:
  • objects – Objects to get adapter for
  • interface – What interface should the adapter provide
  • name – if it is a named adapter
  • args – args to provide the adapter constructor
  • kwargs – kwargs to provide the adapter constructor
Raises:

ComponentLookupError

guillotina.component.query_adapter(object, interface=<InterfaceClass zope.interface.Interface>, name='', default=None, context=None, args=[], kwargs={})

Get a registered adapter

Parameters:
  • object – Object to get adapter for
  • interface – What interface should the adapter provide
  • name – if it is a named adapter
  • args – args to provide the adapter constructor
  • kwargs – kwargs to provide the adapter constructor
guillotina.component.query_multi_adapter(objects, interface=<InterfaceClass zope.interface.Interface>, name='', default=None, context=None, args=[], kwargs={})

Get a registered multi adapter

Parameters:
  • objects – Objects to get adapter for
  • interface – What interface should the adapter provide
  • name – if it is a named adapter
  • args – args to provide the adapter constructor
  • kwargs – kwargs to provide the adapter constructor
guillotina.component.get_utility(interface, name='', context=None)

Get a registered utility

Parameters:
  • interface – What interface should the utility provide
  • name – if it is a named adapter
Raises:

ComponentLookupError

guillotina.component.query_utility(interface, name='', default=None, context=None)

Get a registered utility

Parameters:
  • interface – What interface should the utility provide
  • name – if it is a named adapter
guillotina.component.get_all_utilities_registered_for(interface, context=None)

Get all utilities registered for interface

Parameters:interface – What interface should the utility provide
guillotina.component.get_utilities_for(interface, context=None)

Get utilities registered for interface

Parameters:interface – What interface should the utility provide

guillotina.utils

guillotina.utils.get_current_request()

Return the current request by heuristically looking it up from stack

Return type:<InterfaceClass guillotina.interfaces.IRequest>
guillotina.utils.get_content_path(content)

Generate full path of resource object

Parameters:content (<InterfaceClass guillotina.interfaces.content.IResource>) – object to get path from
Return type:str
guillotina.utils.iter_parents(content)

Iterate through all the parents of a content object

Parameters:content (<InterfaceClass guillotina.interfaces.content.IResource>) – object to get path from
Return type:Iterator[<InterfaceClass guillotina.interfaces.content.IResource>]
guillotina.utils.navigate_to(obj, path)

Get a sub-object.

Parameters:
  • obj (<InterfaceClass guillotina.interfaces.content.IAsyncContainer>) – object to get path from
  • path (str) – relative path to object you want to retrieve
guillotina.utils.get_owners(obj)

Return owners of an object

Parameters:obj (<InterfaceClass guillotina.interfaces.content.IResource>) – object to get path from
Return type:list
guillotina.utils.get_object_url(ob, request=None, **kwargs)

Generate full url of object.

Parameters:
  • ob (<InterfaceClass guillotina.interfaces.content.IResource>) – object to get url for
  • request (Optional[<InterfaceClass guillotina.interfaces.IRequest>]) – relative path to object you want to retrieve
Return type:

Optional[str]

guillotina.utils.get_object_by_oid(oid, txn=None)

Get an object from an oid

Parameters:
  • oid (str) – Object id of object you need to retreive
  • txn – Database transaction object. Will get current transaction is not provided
Return type:

Optional[<InterfaceClass guillotina.interfaces.content.IResource>]

guillotina.utils.get_behavior(ob, iface, create=False)

Generate behavior of object.

Parameters:
  • ob – object to get behavior for
  • interface – interface registered for behavior
  • create – if behavior data empty, should we create it?
guillotina.utils.get_authenticated_user(request=None)

Get the currently authenticated user

Parameters:request (Optional[<InterfaceClass guillotina.interfaces.IRequest>]) – request the user is authenticated against
Return type:Optional[<InterfaceClass guillotina.interfaces.security.IPrincipal>]
guillotina.utils.get_authenticated_user_id(request=None)

Get the currently authenticated user id

Parameters:request (Optional[<InterfaceClass guillotina.interfaces.IRequest>]) – request the user is authenticated against
Return type:Optional[str]
guillotina.utils.strings_differ(string1, string2)

Check whether two strings differ while avoiding timing attacks.

This function returns True if the given strings differ and False if they are equal. It’s careful not to leak information about where they differ as a result of its running time, which can be very important to avoid certain timing-related crypto attacks:

>>> strings_differ('one', 'one')
False
>>> strings_differ('one', 'two')
True
Parameters:
  • string1 (str) –
  • string2 (str) –
Return type:

bool

guillotina.utils.get_random_string(length=30, allowed_chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')

Heavily inspired by Plone/Django Returns a securely generated random string.

>>> get_random_string(length=10)
Parameters:
  • length (int) –
  • allowed_chars (str) –
Return type:

str

guillotina.utils.resolve_dotted_name(name)

import the provided dotted name

>>> resolve_dotted_name('guillotina.interfaces.IRequest')
<InterfaceClass guillotina.interfaces.IRequest>
Parameters:name (str) – dotted name
Return type:~ResolvableType
guillotina.utils.get_caller_module(level=2, sys=<module 'sys' (built-in)>)

Pulled out of pyramid

Return type:module
guillotina.utils.resolve_module_path(path)
Return type:str
guillotina.utils.get_module_dotted_name(ob)
Return type:str
guillotina.utils.get_dotted_name(ob)

Convert a module/class/function to dotted path string

Parameters:ob (~ResolvableType) – the object you’d like to convert
Return type:str
guillotina.utils.import_class(import_string)

Import class from string

Parameters:import_string (str) – dotted class name
Return type:module
guillotina.utils.resolve_path(file_path)

Resolve path to file inside python module

>>> resolve_path('guillotina:__init__.py')
PosixPath('/Users/vangheem/onna/onna-canonical/libsrc/guillotina/guillotina/__init__.py')
Parameters:file_path (str) – module:path string
Return type:Path
guillotina.utils.merge_dicts(d1, d2)

Update two dicts of dicts recursively, if either mapping has leaves that are non-dicts, the second’s leaf overwrites the first’s.

Return type:dict
guillotina.utils.apply_coroutine(func, *args, **kwargs)

Call a function with the supplied arguments. If the result is a coroutine, await it.

>>> async def foobar(): return 'hi'
>>> async def async_foobar(): return 'hi'
>>> await apply_coroutine(foobar)
'hi'
>>> await apply_coroutine(async_foobar)
'hi'
Parameters:
  • func (function) – function to run as coroutiune if one
  • *args – args to call function with
  • **kwargs – kwargs to call function with
Return type:

object

guillotina.utils.lazy_apply(func, *call_args, **call_kwargs)

apply arguments in the order that they come in the function signature and do not apply if argument not provided

call_args will be applied in order if func signature has args. otherwise, call_kwargs is the magic here…

guillotina.utils.safe_unidecode(val)

Convert bytes to a string in a safe way

>>> safe_unidecode(b'foobar')
'foobar'
Parameters:val (bytes) – bytes to convert
Return type:str
class guillotina.utils.Navigator(txn, container)

Provides a consistent view of the loaded objects, ensuring that only a single instance of each resource path is loaded.

It is particularly useful when objects that may have been modified or added have to be found by path (something that the content api cannot do).

To keep the Navigator index consistent, all object loadings must be done through its API, meaning the content api should not be used anymore. In case it cannot be avoided, make sure to use the sync() function before re-using the Navigator.

Parameters:
  • txn (Transaction) – A Transaction instance
  • container (Container) – A Container
delete(obj)

Delete an object from the tree

When using Navigator, this method should be used so that the Navigator can update its index and not return it anymore.

get(path)

Returns an object from its path.

If this path was already modified or loaded by Navigator, the same object instance will be returned.

sync()

Resync the index with the transaction

If some operations may have added, modified or deleted objects that the Navigator does not know, sync() should be called so that the index is up-to-date with the transaction.

guillotina.utils.execute

guillotina.utils.execute.after_request(func, *args, _name=None, _request=None, _scope='', **kwargs)

Execute after the request has successfully finished.

Parameters:
  • func (Callable[…, Coroutine[Any, Any, Any]]) – function to be queued
  • _name – unique identifier to give in case you want to prevent duplicates
  • _scope – customize scope of after commit to run for instead of default(successful request)
  • _request – provide request object to prevent request lookup
  • *args – arguments to call the func with
  • **kwargs – keyword arguments to call the func with
guillotina.utils.execute.after_request_failed(func, *args, _name=None, _request=None, **kwargs)

Execute after the request has failed or errored.

Parameters:
  • func (Callable[…, Coroutine[Any, Any, Any]]) – function to be queued
  • _request – provide request object to prevent request lookup
  • *args – arguments to call the func with
  • **kwargs – keyword arguments to call the func with
guillotina.utils.execute.after_commit(func, *args, _request=None, **kwargs)

Execute a commit to the database.

Parameters:
  • func (Callable) – function to be queued
  • _request – provide request object to prevent request lookup
  • *args – arguments to call the func with
  • **kwargs – keyword arguments to call the func with
guillotina.utils.execute.before_commit(func, *args, _request=None, **kwargs)

Execute before a commit to the database.

Parameters:
  • func (Callable[…, Coroutine[Any, Any, Any]]) – function to be queued
  • _request – provide request object to prevent request lookup
  • *args – arguments to call the func with
  • **kwargs – keyword arguments to call the func with
guillotina.utils.execute.in_pool(func, *args, request=None, **kwargs)

Execute function in the async pool.

Parameters:
  • func (Callable[…, Coroutine[Any, Any, Any]]) – function to be queued
  • _request – provide request object to prevent request lookup. Provide if function be wrapped in database transaction.
  • *args – arguments to call the func with
  • **kwargs – keyword arguments to call the func with
Return type:

ExecuteContext

guillotina.utils.execute.in_queue(view)

Execute view-type object(context, request) in the async queue.

Parameters:view (Union[<InterfaceClass guillotina.interfaces.views.IView>, GenerateQueueView]) – view to be queued
Return type:ExecuteContext
guillotina.utils.execute.in_queue_with_func(func, *args, _request=None, **kwargs)

Execute function in the async queue.

Parameters:
  • func (Callable[…, Coroutine[Any, Any, Any]]) – function to be queued
  • _request – provide request object to prevent request lookup
  • *args – arguments to call the func with
  • **kwargs – keyword arguments to call the func with
Return type:

ExecuteContext

class guillotina.utils.execute.ExecuteContext(func, *args, **kwargs)

Execution context object to allow you to run the function in different contexts.

after_commit(_request=None)

Execute after we commit to the database.

Parameters:_request – provide request object to prevent request lookup
after_request(_name=None, _request=None)

Execute after the request has successfully finished.

Parameters:
  • _name – unique identifier to give in case you want to prevent duplicates
  • _request – provide request object to prevent request lookup
after_request_failed(_name=None, _request=None)

Execute after the request has failed or errored.

Parameters:
  • _name – unique identifier to give in case you want to prevent duplicates
  • _request – provide request object to prevent request lookup
before_commit(_request=None)

Execute just before we commit to the database.

Parameters:_request – provide request object to prevent request lookup

guillotina.factory

guillotina.factory.app.make_app(config_file=None, settings=None, loop=None, server_app=None)

Make application from configuration

Parameters:
  • config_file – path to configuration file to load
  • settings – dictionary of settings
  • loop – if not using with default event loop
  • settings – provide your own aiohttp application

guillotina.transactions

guillotina.transactions.commit(request=None, warn=True)

Commit the current active transaction.

Parameters:request – request object transaction is connected to
guillotina.transactions.abort(request=None)

Abort the current active transaction.

Parameters:request – request object transaction is connected to
guillotina.transactions.get_tm(request=None)

Return shared transaction manager (from request)

This is used together with “with” syntax for wrapping mutating code into a request owned transaction.

Parameters:request – request owning the transaction

Example:

with get_tm(request).transaction() as txn:  # begin transaction txn

    # do something

# transaction txn commits or raises ConflictError
guillotina.transactions.get_transaction(request=None)

Return the current active transaction.

Parameters:request – request object transaction is connected to
class guillotina.transactions.managed_transaction(request=None, tm=None, write=False, abort_when_done=False, adopt_parent_txn=False, execute_futures=True)

Execute a transaction as async context manager and automatically close connection after done.

>>> async with managed_transaction() as txn:
>>>   pass
Parameters:
  • request – request object to connect transaction with
  • tm – transaction manager to retrieve transaction from
  • write – Does this write to database? (defaults to false)
  • abort_when_done – Abort transaction when done (defaults to false)
  • adopt_parent_txn – If this is a sub-transaction, use parent’s registered objects
  • execute_futures – Execute registered futures with transaction after done (defaults to true)

Migration Reference

Contents:

< 4.3 to 4.3

Moving async utilities to be loaded explicit:

  • Check which async utilities you want to use on your addon by default

  • Move them to __init__.py settings like:

    ...
    app_settings = {
        "load_utilities": {
            "catalog": {
                "provides": "guillotina_elasticsearch.utility.IElasticSearchUtility",  # noqa
                "factory": "guillotina_elasticsearch.utility.ElasticSearchUtility",
                "settings": {}
            }
        },
    ...
    
  • Remove any reference to app_settings[‘utilities’] (that was a list) to app_settings[‘load_utilities’]

Installation/Configuration/Deployment

production

Contents:

Installation

Guillotina is an installable python package which can be installed with pip, easy_install or buildout.

Additionally, Guillotina provides docker images.

Install with pip

pip install guillotina

Running

Installing Guillotina provide the g executable. To run the server, simply:

g serve

Read command options for details on commands.

Configuration

guillotina and its addons define a global configuration that is used. All of these settings are configurable by providing a JSON configuration file or yaml to the start script.

By default, the startup script looks for a config.yaml file. You can use a different file by using the -c option for the script like this: ./bin/guillotina -c myconfig.yaml.

Applications

To load guillotina applications into your application, use the applications setting:

applications:
- guillotina_elasticsearch
- guillotina_swagger

Databases

Guillotina can use PostgreSQL out-of-the-box.

To configure available databases, use the databases option. Configuration options map 1-to-1 to database setup:

---
databases:
  db:
    storage: postgresql
    dsn: postgresql://postgres@localhost:5432/guillotina
    read_only: false
    pool_size: 40
    read_only: false
    statement_cache_size: 100
    max_cached_statement_lifetime: 300
    autovacuum: true

In this configuration, the db key referenced in configuration here will be mapped to the url http://localhost:8080/db.

Currently supported database drivers are:

  • postgresql
  • cockroach
  • DUMMY: in-memory database, useful in testing
  • DUMMY_FILE: simple file storage, useful in testing
Database configuration options
  • pool_size: Size of connection pool. (defaults to 13)
  • transaction_strategy: Connection strategy to use. See Transaction strategy_ for details. (defaults to resolve_readcommitted)
  • conn_acquire_timeout: How long to wait for connection to be freed up from pool. (defaults to 20)
  • cache_strategy: If you have something like guillotina_rediscache installed, you can configure here. (defaults to dummy)
  • objects_table_name: Table name to store object data. (defaults to objects)
  • blobs_table_name: Table name to store blob data. (defaults to blobs)
  • autovacuum: Default vacuum relies on pg referential integrity to delete all objects. If you have extremely large databases, this can be very heavy on pg. Set this to false and run the dbvacuum command in a cronjob. (defaults to true)
Storages

Guillotina also provides the ability to dynamically create databases with the @storages endpoint. But in order to utilitize this feature, you need to configure the databases connection settings.

These are configured in much of the same way as databases.

storages:
  bucket:
    storage: postgresql
    dsn: postgresql://postgres@localhost:5432

Notice how it’s missing the database part of the dsn.

Static files

static:
  favicon.ico: static/favicon.ico
  static_files: module_name:static

These files will then be available on urls /favicon.ico and /static_files.

JavaScript Applications

We can also serve JS apps from guillotina. These will allow routing on your JS application without any extra configuration by returning the base directory index.html for every sub directory in the url.

Once there is SSR support in Python, guillotina will integrate with it through this as well.

jsapps:
  app: path/to/app

Root user password

root_user:
  password: root

CORS

cors:
  allow_origin:
    - "*"
  allow_methods:
    - GET
    - POST
    - DELETE
    - HEAD
    - PATCH
    - PUT
  allow_headers:
    - "*"
  expose_headers:
    - "*"
  allow_credentials: true
  max_age: 3660

Applications

To extend/override Guillotina, the applications configuration allows you to specify which to enable.

applications:
  - guillotina_elasticsearch

Async utilities

Guillotina has support for injecting dependencies from configuration with asynchronous utility.

An async utility is a class that implements initialize and finalize method. The initialize method is run at application start as a task. This gives you the power to hook async tasks into guillotina.

load_utilities:
  catalog:
    provides: guillotina.interfaces.ICatalogUtility
    factory: guillotina_elasticsearch.utility.ElasticSearchUtility
    settings: {}

Middleware

guillotina is built on aiohttp which provides support for middleware. You can provide an array of dotted names to use for your application.

middlewares:
  - guillotina_myaddon.Middleware

aiohttp settings

You can pass aiohttp_settings to configure the aiohttp server.

aiohttp_settings:
  client_max_size: 20971520

JWT Settings

If you want to enable JWT authentication, you’ll need to configure the JWT secret in Guillotina.

jwt:
  secret: foobar
  algorithm: HS256

Additionally, to work with websockets, you’ll need to configure the jwk setting:

jwk:
  k: QqzzWH1tYqQO48IDvW7VH7gvJz89Ita7G6APhV-uLMo
  kty: oct

Miscellaneous settings

  • port (number): Port to bind to. defaults to 8080
  • access_log_format (string): Customize access log format for aiohttp. defaults to None
  • store_json (boolean): Serialize object into json field in database. defaults to false
  • host (string): Where to host the server. defaults to "0.0.0.0"
  • port (number): Port to bind to. defaults to 8080
  • conflict_retry_attempts (number): Number of times to retry database conflict errors. defaults to 3
  • cloud_storage (string): Dotted path to cloud storage field type. defaults to "guillotina.interfaces.IDBFileField"
  • loop_policy: (string): Be able to customize the event loop policy used. For example, to use uvloop, set this value to uvloop.EventLoopPolicy.
  • router: be able to customize the main aiohttp Router class
  • oid_generator: be able to customize the function used to generate oids on the system. defaults to guillotina.db.oid.generate_oid
  • cors_renderer: customize the cors renderer, defaults to guillotina.cors.DefaultCorsRenderer
  • request_indexer: customize the class used to index content, defaults to guillotina.catalog.index.RequestIndexer

Transaction strategy

Guillotina provides a few different modes to operate in to customize the level of performance versus consistency. The setting used for this is transaction_strategy which defaults to resolve.

Even though we have different transaction strategies that provide different voting algorithms to decide if it’s a safe write, all write operations STILL make sure that the object committed matches the transaction it was retrieved with. If not, a conflict error is detected and the request is retried. So even if you choose the transaction strategy with no database transactions, there is still a level of consistency so that you know you will only modify an object that is consistent with the one retrieved from the database.

Example configuration:

databases:
  - db:
      storage: postgresql
      transaction_strategy: resolve
      dsn: postgresql://postgres@localhost:5432/guillotina

Available options:

  • none: No db transaction, no conflict resolution. Fastest but most dangerous mode. Use for importing data or if you need high performance and do not have multiple writers.
  • tidonly: The same as none with no database transaction; however, we still use the database to issue us transaction ids for the data committed. Since no transaction is used, this is potentially just as safe as any of the other strategies just as long as you are not writing to multiple objects at the same time — in those cases, you might be in an inconsistent state on tid conflicts.
  • dbresolve: Use db transaction but do not perform any voting when writing(no conflict resolution). (likely only use with cockroach db + serialized transactions)
  • dbresolve_readcommitted: Same as no vote; however, db transaction only started at commit phase. This should provide better performance; however, you’ll need to consider the side affects of this for reading data. (likely only use with cockroach db + serialized transactions)
  • simple: Detect concurrent transactions and error if another transaction id is committed to the db ahead of the current transaction id. This is the safest mode to operate in but you might see conflict errors.
  • resolve: Same as simple; however, it allows commits when conflicting transactions are writing to different objects.
  • resolve_readcommitted: Same as resolve however, db transaction only started at commit phase. This should provide better performance; however, you’ll need to consider the side affects of this for reading data.

Warning: not all storages are compatible with all transaction strategies.

Connection class

The default asyncpg connection class has some overhead. Guillotina provides a way to override it with a custom class or a provided lighter one:

pg_connection_class: guillotina.db.storages.pg.LightweightConnection

Authentication and Authorization

Extractors

auth_extractors are what you can configure to decide how we extract potential credential information from a request.

The default value is:

auth_extractors
- guillotina.auth.extractors.BearerAuthPolicy
- guillotina.auth.extractors.BasicAuthPolicy
- guillotina.auth.extractors.WSTokenAuthPolicy

However, there is also a guillotina.auth.extractors.CookiePolicy available if you want to extract a credential from an auth_token cookie as well.

Available:

  • guillotina.auth.extractors.BearerAuthPolicy: Looks for Bearer Authorization header
  • guillotina.auth.extractors.BasicAuthPolicy: Looks for Basic Authorization header
  • guillotina.auth.extractors.WSTokenAuthPolicy: Looks for ws_token query param
Identifiers

auth_user_identifiers is a mechanism to customize how we lookup and validate users against an extracted credential.

For example, this is the main part of what guillotina_dbusers does and the only configuration setting it needs/provides.

By default, guillotina does not provide a user identifier plugin and only authenticates the root user/password.

Validators

auth_token_validators allows you to customize what kind of values we’ll athorize extracted credentials and identified users against.

  • guillotina.auth.validators.JWTValidator: Validate extract jwt token
  • guillotina.auth.validators.SaltedHashPasswordValidator: Validate extracted password against stored salted and hashed password

Configuration life cycle

Guillotina and applications you define might provide their own configuration that you can override but they might also override the default configuration of guillotina as well.

Guillotina configuration is not considered fully loaded until the entire application has started up and finished loading all it’s dependencies.

Because of this, you can not reference global app_settings at module scope or in any application startup code.

The order of application configuration and override loading in order of more precedence to lower is:

  • startup yaml/json file
  • applications defined in the applications configuration, in order
  • guillotina base configuration

Production

Nginx front

It’s very common to run the API using nginx with a proxy_pass in front, so there is an option to define the URL for the generated URLs inside the api:

Adding the header:

X-VirtualHost-Monster https://example.com/api/

will do a rewrite of the URLs.

Sample configuration on nginx:

    location /api/ {
        proxy_set_header X-VirtualHost-Monster $scheme://$http_host/api/
        proxy_pass http://api.guillotina.svc.cluster.local:80/;
    }

Servicing Guillotina with Ambassador/Envoy

Working with ambassador/envoy works the same as with any other api service gateway; however, there are a few things you can do to improve your experience.

First off, if you want to use internal urls to access guillotina defined services, you will need to utilize dynamically adding a header to the request in order for Guillotina to understand how it’s being served and generate urls correctly.

Example with ambassador:

getambassador.io/config: |
  ...
  add_request_headers:
    x-virtualhost-path: /path-served-at/
  ...

Additionally, it is recommended to use a resolver with a load balancer that will hash requests to backends based on the Authorization header. This encourages requests from a single user to be directed at the same backend so you will get more cache hits.

getambassador.io/config: |
  ...
  resolver: <my endpoint resolver>
  load_balancer:
    policy: ring_hash
    header: Authorization

Postgresql

With very large databases, Postgresql can get into a state where particular prepared statements perform very poorly and they’ll start pegging your CPU.

The origin of this problem is related to how asyncpg caches prepared statements.

If you start seeing this problem, it is recommdned to tune the following connection configuration options:

  • statement_cache_size: Setting to 0 is an option
  • max_cached_statement_lifetime: Set to extremely low value(2)

(Make sure to tune postgresql for your server and dataset size.)

Logging

Logging configuration is built into guillotina’s configuration syntax.

If the logging setting is provided, it is simply passed to Python’s dict config method: https://docs.python.org/3.6/library/logging.config.html#logging-config-dictschema

Example guillotina configuration

To log errors for guillotina for example:

{
  "logging": {
    "version": 1,
    "formatters": {
      "brief": {
        "format": "%(message)s"
      },
      "default": {
        "format": "%(asctime)s %(levelname)-8s %(name)-15s %(message)s",
        "datefmt": "%Y-%m-%d %H:%M:%S"
      }
    },
    "handlers": {
      "file": {
        "class": "logging.handlers.RotatingFileHandler",
        "formatter": "default",
        "filename": "logconfig.log",
        "maxBytes": 1024,
        "backupCount": 3
      }
    },
    "loggers": {
      "guillotina": {
        "level": "DEBUG",
        "handlers": ["file"],
        "propagate": 0
      }
    }
  }
}

Request logging example

{
  "logging": {
    "version": 1,
    "formatters": {
      "default": {
        "format": "%(message)s"
      }
    },
    "handlers": {
      "file": {
        "class": "logging.handlers.RotatingFileHandler",
        "formatter": "default",
        "filename": "access.log",
        "maxBytes": 1024,
        "backupCount": 3
      }
    },
    "loggers": {
      "aiohttp.access": {
        "level": "INFO",
        "handlers": ["file"],
        "propagate": 0
      }
    }
  }
}

Available Loggers

  • guillotina
  • aiohttp.access
  • aiohttp.client
  • aiohttp.internal
  • aiohttp.server
  • aiohttp.web
  • aiohttp.websocket

About

Note

Scanning

If your service modules are not imported at run-time, you may need to provide an additional scan call to get your services noticed by guillotina.

In your application __init__.py file, you can simply provide a scan call like:

from guillotina import configure
def includeme(root):
    configure.scan('my.package')

Developer documentation

Contents:

Testing

Customizing app settings on tests

You can use pytest markers to overwrite specific application settings on tests, for instance:

@pytest.mark.app_settings({
    'root_user: {'password': 'supersecret!'}
})
async def test_the_code(container_requester):
    pass

The above test would run a guillotina server with supersecret! root password.

About

As the Web evolves, so do the frameworks that we use to work with the Web. Guillotina is part of that evolution, providing an asynchronous web server with a rich, REST-ful API to build your web applications around.

It is designed for building JavaScript applications. It is an API framework, not a typical template-based rendering framework like most web frameworks (Django/Pyramid/Plone). What we mean by this is that Guillotina will not generate HTML out-of-the-box for you. It is designed to be consumed by JavaScript applications that do the HTML rendering, or to act as a middleware layer on a microservice architecture.

Features:
  • REST JSON API
  • Built-in authentication/authorization, built-in JWT support
  • Hierarchical data/url structure
  • Permissions/roles/groups
  • Fully customizable permission/roles/groups based on hierarchical data structure
  • Robust customizable component architecture and configuration syntax
  • Content types, dynamic behaviors
  • Built-in CORS support
  • JSON schema support
  • PostgreSQL and CockroachDB drivers
  • Blobs

Guillotina is built on the lessons learned from great technologies of the open source projects Plone, Zope, Pyramid and Django.

Inspirations:
  • Plone/Zope’s hierarchical data model
  • Pyramid’s decorator-based auto-discovery application configuration syntax
  • Django’s global application settings style syntax
  • Zope’s component architecture for building robustly customizable applications
  • Plone/Zope’s security model
  • JSON Schema
Lessons Learned (from said inspired frameworks):
  • Trade-offs for the sake of performance is okay
  • Too many complex dependencies causes difficulties in management and upgrades
  • It’s okay to fork dependency packages

History lesson

In the beginning, there was bobo.

bobo was what Jim Fulton called his initial idea of mapping objects to web urls. It’s an old idea. A beautiful idea. The developers of Guillotina think it’s the best possible way to conceptualize most content-centric APIs and organization of how your web applications think about data or their APIs.

Think about this simple example. Assuming you have the following dictionary:

{
  "foo": {
    "bar": {}
  }
}
The corresponding urls for a site based off this dictionary would be:

And so on… It’s a simple way to build APIs from data around a hierarchy (or tree).

Extrapolating this concept, Jim Fulton also built the ZODB package. This was a complete database built on serializing Python objects using the pickle library. Then, frameworks like Zope (and eventually Plone), used this database and the bobo style of publishing objects to URLs to build a framework and CMS around.

An Object Graph: The guillotina datastore.

At the beginning there is the notion of Content-Type. A content type, it’s just a python interface (A class) that describes an object. Every object could be stored on the db. And every object, could have child objects related to them. Something like:

/user@account/ /user@account/preferences /user@account/todos /user@account/todos/todos_list1 /user@account/todos/todos_list1/todo_item1 /user@account/todos/todos_list1/todo_item2 /user@account/todos/todos_list1/todo_item3

Allows us to better express content relations, and this is where guillotina shines, because it offers an automatic REST API over them.

For example you can do a PATCH request over /user@account/preferences, to update, them or you can POST an item over the /user@account/todos with the necessary payload to create new todo posts lists, or you can just do a DELETE request to /user@account/todos/todos_list1/todo_item3 to remove a todo list item.

That’s the main foundation of guillotina, and also one of the most powerful concepts, the permission system, is based on this. As an example, at /user@account path, only the user is allowed to access it. All child objects inherit this permission, anyone else than the owner could access them, but if at some point, we add new readers to an item (a todo list) will give access to other users.

Security is accessed throught /object_path/@sharing

Forked dependency packages

Guillotina has eaten a few packages that would have otherwise been dependencies.

The reasons for forking are:
  • Required to support asyncio
  • Provide tighter fit for framework
  • Make installations less painful and error-prone
  • Groking framework is easier when there is one package to import from
Forks:
  • parts of the ZODB data model: we’re on a relational storage model now
  • plone.behavior
  • zope.security
  • zope.schema
  • zope.component/zope.configuration
  • zope.dublincore
  • zope.i18n
  • zope.lifecycleevent
  • zope.location
  • zope.event

What it isn’t

  • Guillotina is not a replacement for Plone
  • Guillotina is not a re-implementation of Plone
  • Guillotina does not implement all the features and APIs of Plone

It could come some day with the guillotina_cms package but replacement of Plone is not the goal of Guillotina.

Awesome Guillotina

Some useful applications to get you started:

Where to find more packages:

Quick tour of Guillotina

Guillotina is powerful datastore, capable of storing and indexing milions 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
  • Serialitzation/Validiation library integrated.
  • Elastic search integration throught guillotina_elasticsearch, or fallback to postgres json indexing.
  • Declarative configuration using decorators.
  • Integrated cloud storage file uploads.
  • py.test fixtues 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

In order to play with those examples you should install guillotina and cookiecutter, let’s do that in a python virtualenv:

$ virtualenv .
$ source ./bin/activate
$ pip install guillotina cookiecutter

Then use guillotina templates to create an application:

$ ./bin/g create --template=application
Could not find the configuration file config.json. Using default settings.
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.

Quickstart

How to quickly get started using guillotina.

This tutorial will assume usage of virtualenv. You can use your own preferred tool for managing your python environment.

This tutorial assumes you have postgresql running

Setup the environment:

virtualenv .

Install guillotina:

./bin/pip install guillotina

Generate configuration file (requires cookiecutter):

./bin/pip install cookiecutter
./bin/g create --template=configuration

Finally, run the server:

./bin/g

The server should now be running on http://0.0.0.0:8080

Then, use Postman, curl or whatever tool you prefer to interact with the REST API.

You can also navigate in your Guillotina server with its built-in web admin interface by visiting http://localhost:8080/+admin/.

Modify the configuration in config.yaml to customize server setttings.

Postgresql installation instructions

If you do not have a postgresql database server installed, you can use docker to get one running quickly.

Example docker run command:

docker run -e POSTGRES_DB=guillotina -e POSTGRES_USER=guillotina -p 127.0.0.1:5432:5432 postgres:9.6

Creating a container

Guillotina containers are the building block of all other content. A container is where you place all other content for your application. Only containers can be created inside databases.

Let’s create one:

http

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

{
    "@type": "Container",
    "title": "Guillotina 1",
    "id": "guillotina",
    "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": "guillotina", "title": "Guillotina 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": "guillotina", "title": "Guillotina 1"}' --auth-no-challenge --user=root --password=root

httpie

echo '{
  "@type": "Container",
  "description": "Description",
  "id": "guillotina",
  "title": "Guillotina 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': 'guillotina',
    'title': 'Guillotina 1',
}, auth=('root', 'root'))

response

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

and create content inside the container:

http

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

{
    "@type": "Item",
    "title": "News",
    "id": "news"
}

curl

curl -i -X POST http://localhost:8080/db/guillotina/ -H 'Accept: application/json' -H 'Content-Type: application/json' --data-raw '{"@type": "Item", "id": "news", "title": "News"}' --user root:root

wget

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

httpie

echo '{
  "@type": "Item",
  "id": "news",
  "title": "News"
}' | http POST http://localhost:8080/db/guillotina/ Accept:application/json Content-Type:application/json -a root:root

python-requests

requests.post('http://localhost:8080/db/guillotina/', headers={
    'Accept': 'application/json',
    'Content-Type': 'application/json',
}, json={
    '@type': 'Item',
    'id': 'news',
    'title': 'News',
}, auth=('root', 'root'))

response

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

Retrieving your data

Let’s navigating throught your newly created data.

First you can see all your containers using the following, notice that at the moment there’s only one named guillotina:

http

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

curl

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

wget

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

httpie

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

python-requests

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

response

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

{
    "@type": "Database",
    "containers": [
        "guillotina"
    ]
}

Then you could explore container data using:

http

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

curl

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

wget

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

httpie

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

python-requests

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

response

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

{
    "@id": "http://localhost:8080/db/guillotina",
    "@name": "guillotina",
    "@type": "Container",
    "@uid": "7d9ebe1b2e1044688c83985e9e0a7ef3",
    "UID": "7d9ebe1b2e1044688c83985e9e0a7ef3",
    "__behaviors__": [],
    "__name__": "guillotina",
    "creation_date": "2018-07-21T09:37:28.125034+00:00",
    "is_folderish": true,
    "items": [
        {
            "@id": "http://localhost:8080/db/guillotina/news",
            "@name": "news",
            "@type": "Item",
            "@uid": "7d9|11729830722c4e43924df18d21d14bdf",
            "UID": "7d9|11729830722c4e43924df18d21d14bdf"
        }
    ],
    "length": 1,
    "modification_date": "2018-07-21T09:37:28.125034+00:00",
    "parent": {},
    "title": "Guillotina 1",
    "type_name": "Container",
    "uuid": "7d9ebe1b2e1044688c83985e9e0a7ef3"
}

And finally query a specific content inside the container using:

http

GET /db/guillotina/news HTTP/1.1
Accept: application/json
Host: localhost:8080
Authorization: Basic cm9vdDpyb290

curl

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

wget

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

httpie

http http://localhost:8080/db/guillotina/news Accept:application/json -a root:root

python-requests

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

response

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

{
    "@id": "http://localhost:8080/db/guillotina/news",
    "@name": "news",
    "@type": "Item",
    "@uid": "7d9|11729830722c4e43924df18d21d14bdf",
    "UID": "7d9|11729830722c4e43924df18d21d14bdf",
    "__behaviors__": [],
    "__name__": "news",
    "creation_date": "2018-07-21T09:37:41.863014+00:00",
    "guillotina.behaviors.dublincore.IDublinCore": {
        "contributors": [
            "root"
        ],
        "creation_date": "2018-07-21T09:37:41.863014+00:00",
        "creators": [
            "root"
        ],
        "description": null,
        "effective_date": null,
        "expiration_date": null,
        "modification_date": "2018-07-21T09:37:41.863014+00:00",
        "publisher": null,
        "tags": null,
        "title": "News"
    },
    "is_folderish": false,
    "modification_date": "2018-07-21T09:37:41.863014+00:00",
    "parent": {
        "@id": "http://localhost:8080/db/guillotina",
        "@name": "guillotina",
        "@type": "Container",
        "@uid": "7d9ebe1b2e1044688c83985e9e0a7ef3",
        "UID": "7d9ebe1b2e1044688c83985e9e0a7ef3"
    },
    "title": "News",
    "type_name": "Item",
    "uuid": "7d9|11729830722c4e43924df18d21d14bdf"
}