Quickstart

In this section, we’re going to build together a small application showcasing Procrastinate and its everyday use.

Prerequisites & installation

If you already have a PostgreSQL database around, make sure to note the connection parameters. Otherwise, we’ll create one together with Docker:

$ docker run --name pg-procrastinate --detach --rm -p 5432:5432 -e POSTGRES_PASSWORD=password postgres

Note

If you need to stop the docker at some point, use docker stop pg-procrastinate.

Within a virtualenv, install Procrastinate with:

(venv) $ pip install procrastinate

Create a Procrastinate application object

We’ll do this in a single file. Start an empty file named tutorial.py:

from procrastinate import App, PsycopgConnector

app = App(
    connector=PsycopgConnector(
        kwargs={
            "host": "localhost",
            "user": "postgres",
            "password": "password",
        }
    )
)

The application will be the entry point for both:

  • Declaring tasks (a.k.a job templates) to be launched by Procrastinate,

  • Launching the worker that will consume the jobs created from those tasks.

  • Applying Database schema

Prepare the database

Install the PostgreSQL structure procrastinate needs in your database with:

(venv) $ export PYTHONPATH=.  # required for procrastinate to find "tutorial.app"
(venv) $ procrastinate --app=tutorial.app schema --apply

Note

The export PYTHONPATH=. above is required here for the procrastinate command to be able to find your tutorial module, and the app object inside it. You wouldn’t need to export PYTHONPATH if the tutorial module was installed in the (virtual) Python environment.

Are we good to go?

(venv) $ procrastinate --app=tutorial.app healthchecks
App configuration: OK
DB connection: OK
Found procrastinate_jobs table: OK

Declare a task

In your file, add the following

# at the top of the file
import random
import time

...

# at the bottom of the file
@app.task(name="sum")
def sum(a, b):
    time.sleep(random.random() * 5)  # Sleep up to 5 seconds
    return a + b

We’ve defined a task named “sum” that will wait a bit and compute the sum of two things. (We could have added type annotations if we wanted).

At this point, nothing is running yet. We’ve just created a task, which is the template (or blueprint) for a job.

Our task doesn’t really have an impact on the world (a side effect). It doesn’t write a file, or update a database, it doesn’t make an API call. In real life, this is a problem, because at this point, all the job is doing is wasting CPU cycle. In our case, though, we’ll just monitor the standard output to see if our task executed successfully. The return value of a task serves no other purpose than logging.

Launch a job

We’ll use the defer method of our task

import sys

...

def main():
    with app.open():
        a = int(sys.argv[1])
        b = int(sys.argv[2])
        print(f"Scheduling computation of {a} + {b}")
        sum.defer(a=a, b=b)  # This is the line that launches a job

if __name__ == "__main__":
    main()

You can launch your script now with:

(venv) $ python tutorial.py 2 3

App is instantiated in the main Python module (tutorial.py). See https://procrastinate.readthedocs.io/en/stable/discussions.html#top-level-app
Scheduling computation of 2 + 3

Note

We can see that Procrastinate is complaining about the fact that we’re instantiating the app in the main module (tutorial.py, the module on which we called Python on). This is not a problem for this tutorial, but it’s defintely something you should avoid when building your real application. Follow the link in the warning to learn more, but you don’t need to worry about it for now.

We’ve deferred a job, hurrah! But nothing happened yet. We need to launch a worker to consume the job. Before that, let’s defer a handful of jobs, so that we can see the worker in action:

(venv) $ python tutorial.py 5 7
(venv) $ python tutorial.py 3 9
(venv) $ python tutorial.py 1 2

Time to launch a worker and see what happens.

Run a worker

(venv) $ procrastinate --verbose --app=tutorial.app worker
Launching a worker on all queues
INFO:procrastinate.worker.worker:Starting worker on all queues
INFO:procrastinate.worker.worker:Starting job sum[1](a=2, b=3)
INFO:procrastinate.worker.worker:Job sum[1](a=2, b=3) ended with status: Success, lasted 1.822 s - Result: 5

Stop the worker with ctrl+c.

In the logs, you can see that the result is 5, and the task took 1.8 seconds (remember that we added a random sleep in the task).

Congratulations, you’ve successfully executed your first jobs with Procrastinate!

Checking your jobs

Procrastinate comes with a simple interactive shell to check the status of your jobs:

(venv) $ procrastinate --app=tutorial.app shell
Welcome to the procrastinate shell.   Type help or ? to list commands.

procrastinate> help

Documented commands (type help <topic>):
========================================
EOF  cancel  exit  help  list_jobs  list_queues  list_tasks  retry

procrastinate> list_jobs
#1 sum on default - [succeeded]
procrastinate> exit

Your final file

import random
import sys
import time

from procrastinate import App, PsycopgConnector

app = App(
    connector=PsycopgConnector(
        kwargs={
            "host": "localhost",
            "user": "postgres",
            "password": "password",
        }
    )
)


@app.task(name="sum")
def sum(a, b):
    time.sleep(random.random() * 5)  # Sleep up to 5 seconds
    return a + b


def main():
    with app.open():
        a = int(sys.argv[1])
        b = int(sys.argv[2])
        print(f"Scheduling computation of {a} + {b}")
        sum.defer(a=a, b=b)  # This is the line that launches a job


if __name__ == "__main__":
    main()

Going further

To continue with practical steps, head to the “How to…” section. For example, have a look at the locks feature: Ensure jobs run sequentially and in order.

If you want to better understand some design decisions, head to the Discussions sections.