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 )