Navigation

Orator 0.5 is now out. This version introduces database migrations and attributes accessors and mutators.

Why the change of name?

This ORM project was inspired by the Eloquent ORM of the Laravel PHP framework. So when I started it I took the same name for the project. However, as I have been pointed out, it would likely lead to confusion and conflict when searching information about it. That's why I decided to change the name to Orator.

What it means is that the eloquent package is now deprecated and that the 0.5 version will be the last. It is therefore highly encouraged to use the orator package instead.

Database migrations

Migrations are a type of version control for your database. They allow a team to modify the database schema and stay up to date on the current schema state. Migrations are typically paired with the Schema Builder to easily manage your database’s schema.

For the migrations to actually work, you need a configuration file describing your databases in a DATABASES dict, like so:

DATABASES = {
    'mysql': {
        'driver': 'mysql',
        'host': 'localhost',
        'database': 'database',
        'username': 'root',
        'password': '',
        'prefix': ''
    }
}

This file needs to be specified when using migrations commands.

Creating migrations

To create a migration, you can use the migrations:make command on the Orator CLI:

orator migrations:make create_users_table -c databases.py

This will create a migration file that looks like this:

from orator.migrations import Migration


class CreateTableUsers(Migration):

    def up(self):
        """
        Run the migrations.
        """
        pass

    def down(self):
        """
        Revert the migrations.
        """
        pass

By default, the migration will be placed in a migrations folder relative to where the command has been executed, and will contain a timestamp which allows the framework to determine the order of the migrations.

If you want the migrations to be stored in another folder, use the --path/-p option:

orator migrations:make create_users_table -c databases.py -p my/path/to/migrations

The --table and --create options can also be used to indicate the name of the table, and whether the migration will be creating a new table:

orator migrations:make add_votes_to_users_table -c databases.py --table=users

orator migrations:make create_users_table -c databases.py --table=users --create

These commands would respectively create the following migrations:

from orator.migrations import Migration


class AddVotesToUsersTable(Migration):

    def up(self):
        """
        Run the migrations.
        """
        with self.schema.table('users') as table:
            pass

    def down(self):
        """
        Revert the migrations.
        """
        with self.schema.table('users') as table:
            pass
from orator.migrations import Migration


class CreateTableUsers(Migration):

    def up(self):
        """
        Run the migrations.
        """
        with self.schema.create('users') as table:
            table.increments('id')
            table.timestamps()

    def down(self):
        """
        Revert the migrations.
        """
        self.schema.drop('users')

Running migrations

To run all outstanding migrations, just use the migrations:run command:

orator migrations:run -c databases.py

Rolling back migrations

Rollback the last migration operation

orator migrations:rollback -c databases.py

Rollback all migrations

eloquent migrations:reset -c databases.py

Getting migrations status

To see the status of the migrations, just use the migrations:status command:

orator migrations:status -c databases.py

This would output something like this:

+----------------------------------------------------+------+
| Migration                                          | Ran? |
+----------------------------------------------------+------+
| 2015_05_02_04371430559457_create_users_table       | Yes  |
| 2015_05_04_02361430725012_add_votes_to_users_table | No   |
+----------------------------------------------------+------+

Accessors & mutators

Orator provides a convenient way to transform your model attributes when getting or setting them.

Defining an accessor

Simply use the accessor decorator on your model to declare an accessor:

from orator.orm import Model, accessor


class User(Model):

    @accessor
    def first_name(self):
        first_name = self.get_raw_attribute('first_name')

        return first_name[0].upper() + first_name[1:]

In the example above, the first_name column has an accessor.

The name of the decorated function must match the name of the column being accessed.

Defining a mutator

Mutators are declared in a similar fashion:

from orator.orm import Model, mutator


class User(Model):

    @mutator
    def first_name(self, value):
        self.set_raw_attribute('first_name', value)

If the column being mutated already has an accessor, you can use it has a mutator:

from orator.orm import Model, accessor


    class User(Model):

        @accessor
        def first_name(self):
            first_name = self.get_raw_attribute('first_name')

            return first_name[0].upper() + first_name[1:]

        @first_name.mutator
        def set_first_name(self, value):
            self.set_raw_attribute(value.lower())

The inverse is also possible:

from orator.orm import Model, mutator


    class User(Model):

        @mutator
        def first_name(self, value):
            self.set_raw_attribute(value.lower())

        @first_name.accessor
        def get_first_name(self):
            first_name = self.get_raw_attribute('first_name')

            return first_name[0].upper() + first_name[1:]

Appendable attributes

When converting a model to a dictionary or a JSON string, you may occasionally need to add dictionary attributes that do not have a corresponding column in your database. To do so, simply define an accessor for the value:

class User(Model):

    @accessor
    def is_admin(self):
        return self.get_raw_attribute('admin') == 'yes'

Once you have created the accessor, just add the value to the __appends__ property on the model:

class User(Model):

    __append__ = ['is_admin']

    @accessor
    def is_admin(self):
        return self.get_raw_attribute('admin') == 'yes'

Once the attribute has been added to the __appends__ list, it will be included in both the model's dictionary and JSON forms. Attributes in the __appends__ list respect the __visible__ and __hidden__ configuration on the model.

There is a lot more you can do with Orator, just give a look at the documentation to see all available features.

What's next?

Finally, here are some features targeted for future versions (actual roadmap to be determined):

  • Model events:
from models import User


def check_user(user):
    if not user.is_valid():
        return False

User.creating(check_user)
  • Extra functionalities, like caching.
Sébastien Eustace

Sébastien Eustace

sebastien.eustace.io

Born & raised in France, and currently living in the beautiful city of Quito, Ecuador, I'm a software engineer, proud pythonista (but knowledgeable in other languages and technologies as well) but overall an open source lover.

View Comments