Quickstart

Eager to get started? This page gives a good introduction to Protean. Follow Installation to set up a project and install Protean first.

In this quickstart, we will create a simple Protean application with SQLITE as the database and Flask as the API framework.

Initialize a Project

Let us initialize a new directory for your project. We will call it authentication.

From the command line, cd into a directory where you’d like to store your code, then run the following command:

$ protean new authentication

This will create a authentication directory in your current directory.

Here is a quicklook at the directory structure:

A Simple Domain

A simple Protean domain looks something like this:

from protean import Domain

domain = Domain(__name__)

Here’s what we did:

  1. First we imported the protean.Domain class. An instance of this class will be our domain root to which all elements are attached.

  2. Next, we create an instance of this class. The optional argument is the name of the domain’s module or package. __name__ is a convenient shortcut for this that is appropriate for most cases, so it is the default if no name is specified explicitly.

Define an Aggregate

Aggregates are the basic building blocks of the domain. Use the protean.Domain.aggregate() decorator to bind an Aggregate to the domain.

from protean.field import String

@domain.aggregate
class User:
    name = String(max_length=50)
    email = String(max_length=255, unique=True)

Define an Application Service

Application services expose the domain to the external world. You can create an Application Service with the help of protean.Domain.application_service() decorator.

@domain.application_service
class SignupService:
    @classmethod
    def signup(cls, name, email):
        user = User(name=name, email=email)
        domain.repository_for(User).add(user)

        return user

Configure a database

By default, a Protean domain is configured with an protean.adapters.repository.MemoryProvider that manages a dictionary database in memory. This database is handy when you get started with your domain, especially for testing purposes. You can also specify an alternate implementation by overriding the database config. Let’s do that and specify an SQLITE database.

Note that Protean uses SQLAlchemy to access the SQLITE database internally.

domain.config["DATABASES"]["default"] = {
    "PROVIDER": "protean.adapters.repository.sqlalchemy.SAProvider",
    "DATABASE": "SQLITE",
    "DATABASE_URI": "sqlite:///quickstart.db",
}

A database file quickstart.db will be created in the location you will be running your application from.

Initialize the domain

Before you can use the domain, you need to initialize it. Initialize the domain by calling protean.Domain.init() on the domain instance.

domain.init(traverse=False)

Since all our code is in the same module, we can use traverse=False. If you have your code spread across multiple modules, you can set traverse=True to traverse the entire module tree and load all the elements.

Configure Flask

Let’s next expose the domain to the external world via APIs with Flask. We accomplish this by activating <TO-LINK> the domain in a function that runs before every request.

We also register a function to run before Flask processes the very first request, in which we set up the database with a table whose structure is auto-generated from the Aggregate definition.

from flask import Flask

app = Flask(__name__)

@app.before_request
def set_context():
    context = domain.domain_context()
    context.push()

If you want to create the database tables automatically from the structure defined in the domain, you can:

@app.before_first_request
def setup_db():
    with domain.domain_context():
        for provider in domain.providers_list():
            for _, aggregate in domain.registry.aggregates.items():
                domain.repository_for(aggregate.cls)._dao

            provider._metadata.create_all()

Define a route

We are now ready to define API routes for the domain. Let’s create a route that helps us create new users as well as returns a list of all existing users.

@app.route("/users", methods=["GET", "POST"])
def users():
    if request.method == "POST":
        user = SignupService.signup(request.form['name'], request.form['email'])
        return json.dumps(user.to_dict()), 201
    else:
        users = current_domain.repository_for(User).all()
        return json.dumps([user.to_dict() for user in users]), 200

Start the Flask server

To run the Flask application, use the flask command or python -m flask. The snippet below assumes that your code is saved in a file named quickstart.py. If it is not, adjust the command accordingly.

$ export FLASK_APP=quickstart
$ flask run

If all is well, you should see a success message at the console along with the URL to access the Flask server.

Access the domain over APIs

You can access the APIs once the server is running. We can use HTTPie to fire requests from the console. Let’s first fire a POST request to create a user.

http -f POST http://localhost:5000/users name=John email=john.doe@example.com

You should see a success message with the user record that was just created.

HTTP/1.0 201 CREATED
Content-Length: 95
Content-Type: text/html; charset=utf-8
Date: Mon, 09 Aug 2021 16:19:31 GMT
Server: Werkzeug/1.0.1 Python/3.9.4

{
    "email": "john.doe@example.com",
    "id": "41de0f44-9dd0-4ac9-98e3-5e2eca498511",
    "name": "John"
}

We can now fire a GET request to retrieve all users from the database.

http http://127.0.0.1:5000/users

HTTP/1.0 200 OK
Content-Length: 97
Content-Type: text/html; charset=utf-8
Date: Mon, 09 Aug 2021 16:19:36 GMT
Server: Werkzeug/1.0.1 Python/3.9.4

[
    {
        "email": "john.doe@example.com",
        "id": "41de0f44-9dd0-4ac9-98e3-5e2eca498511",
        "name": "John"
    }
]

That’s it! You have now created a simple Protean domain with SQLITE and Flask and accessed it over the web.