Package: lorator
The Orator ORM provides a simple yet beautiful ActiveRecord implementation.It is inspired by the database part of theLaravel framework,
but largely modified to be more pythonic.The full documentation is available here:http://orator-orm.com/docsInstallationYou can install Orator in 2 different ways:The easier and more straightforward is to use pippipinstalloratorInstall from source using the official repository (https://github.com/sdispater/orator)The different dbapi packages are not part of the package dependencies,
so you must install them in order to connect to corresponding databases:Postgres:psycopg2MySQL:PyMySQLormysqlclientSqlite: Thesqlite3module is bundled with Python by defaultBasic UsageConfigurationAll you need to get you started is the configuration describing your database connections
and passing it to aDatabaseManagerinstance.fromoratorimportDatabaseManager,Modelconfig={'mysql':{'driver':'mysql','host':'localhost','database':'database','user':'root','password':'','prefix':''}}db=DatabaseManager(config)Model.set_connection_resolver(db)Defining a modelclassUser(Model):passNote that we did not tell the ORM which table to use for theUsermodel. The plural “snake case” name of the
class name will be used as the table name unless another name is explicitly specified.
In this case, the ORM will assume theUsermodel stores records in theuserstable.
You can specify a custom table by defining a__table__property on your model:classUser(Model):__table__='my_users'The ORM will also assume that each table has a primary key column namedid.
You can define a__primary_key__property to override this convention.
Likewise, you can define a__connection__property to override the name of the database
connection that should be used when using the model.Once a model is defined, you are ready to start retrieving and creating records in your table.
Note that you will need to placeupdated_atandcreated_atcolumns on your table by default.
If you do not wish to have these columns automatically maintained,
set the__timestamps__property on your model toFalse.Retrieving all modelsusers=User.all()Retrieving a record by primary keyuser=User.find(1)print(user.name)Querying using modelsusers=User.where('votes','>',100).take(10).get()foruserinusers:print(user.name)AggregatesYou can also use the query builder aggregate functions:count=User.where('votes','>',100).count()If you feel limited by the builder’s fluent interface, you can use thewhere_rawmethod:users=User.where_raw('age > ? and votes = 100',[25]).get()Chunking ResultsIf you need to process a lot of records, you can use thechunkmethod to avoid
consuming a lot of RAM:forusersinUser.chunk(100):foruserinusers:# ...Specifying the query connectionYou can specify which database connection to use when querying a model by using theonmethod:user=User.on('connection-name').find(1)If you are using read / write connections, you can force the query to use the “write” connection
with the following method:user=User.on_write_connection().find(1)Mass assignmentWhen creating a new model, you pass attributes to the model constructor.
These attributes are then assigned to the model via mass-assignment.
Though convenient, this can be a serious security concern when passing user input into a model,
since the user is then free to modifyanyandallof the model’s attributes.
For this reason, all models protect against mass-assignment by default.To get started, set the__fillable__or__guarded__properties on your model.Defining fillable attributes on a modelThe__fillable__property specifies which attributes can be mass-assigned.classUser(Model):__fillable__=['first_name','last_name','email']Defining guarded attributes on a modelThe__guarded__is the inverse and acts as “blacklist”.classUser(Model):__guarded__=['id','password']You can also blockallattributes from mass-assignment:__guarded__=['*']Insert, update and deleteSaving a new modelTo create a new record in the database, simply create a new model instance and call thesavemethod.user=User()user.name='John'user.save()You can also use thecreatemethod to save a model in a single line, but you will need to specify
either the__fillable__or__guarded__property on the model since all models are protected against
mass-assignment by default.After saving or creating a new model with auto-incrementing IDs, you can retrieve the ID by accessing
the object’sidattribute:inserted_id=user.idUsing the create method# Create a new user in the databaseuser=User.create(name='John')# Retrieve the user by attributes, or create it if it does not existuser=User.first_or_create(name='John')# Retrieve the user by attributes, or instantiate it if it does not existuser=User.first_or_new(name='John')Updating a retrieved modeluser=User.find(1)user.name='Foo'user.save()You can also run updates as queries against a set of models:affected_rows=User.where('votes','>',100).update(status=2)Deleting an existing modelTo delete a model, simply call thedeletemodel:user=User.find(1)user.delete()Deleting an existing model by keyUser.destroy(1)User.destroy(1,2,3)You can also run a delete query on a set of models:affected_rows=User.where('votes','>'100).delete()Updating only the model’s timestampsIf you want to only update the timestamps on a model, you can use thetouchmethod:user.touch()TimestampsBy default, the ORM will maintain thecreated_atandupdated_atcolumns on your database table
automatically. Simply add thesetimestampcolumns to your table. If you do not wish for the ORM to maintain
these columns, just add the__timestamps__property:classUser(Model):__timestamps__=FalseProviding a custom timestamp formatIf you wish to customize the format of your timestamps (the default is the ISO Format) that will be returned when using theto_dictor theto_jsonmethods, you can override theget_date_formatmethod:classUser(Model):defget_date_format():return'DD-MM-YY'Converting to dictionaries / JSONConverting a model to a dictionaryWhen building JSON APIs, you may often need to convert your models and relationships to dictionaries or JSON.
So, Orator includes methods for doing so. To convert a model and its loaded relationship to a dictionary,
you may use theto_dictmethod:user=User.with_('roles').first()returnuser.to_dict()Note that entire collections of models can also be converted to dictionaries:returnUser.all().serailize()Converting a model to JSONTo convert a model to JSON, you can use theto_jsonmethod!returnUser.find(1).to_json()Query BuilderIntroductionThe database query builder provides a fluent interface to create and run database queries.
It can be used to perform most database operations in your application, and works on all supported database systems.SelectsRetrieving all row from a tableusers=db.table('users').get()foruserinusers:print(user['name'])Chunking results from a tableforusersindb.table('users').chunk(100):foruserinusers:# ...Retrieving a single row from a tableuser=db.table('users').where('name','John').first()print(user['name'])Retrieving a single column from a rowuser=db.table('users').where('name','John').pluck('name')Retrieving a list of column valuesroles=db.table('roles').lists('title')This method will return a list of role titles. It can return a dictionary
if you pass an extra key parameter.roles=db.table('roles').lists('title','name')Specifying a select clauseusers=db.table('users').select('name','email').get()users=db.table('users').distinct().get()users=db.table('users').select('name as user_name').get()Adding a select clause to an existing queryquery=db.table('users').select('name')users=query.add_select('age').get()Using where operatorsusers=db.table('users').where('age','>',25).get()Or statementsusers=db.table('users').where('age','>',25).or_where('name','John').get()Using Where Betweenusers=db.table('users').where_between('age',[25,35]).get()Using Where Not Betweenusers=db.table('users').where_not_between('age',[25,35]).get()Using Where Inusers=db.table('users').where_in('id',[1,2,3]).get()users=db.table('users').where_not_in('id',[1,2,3]).get()Using Where Null to find records with null valuesusers=db.table('users').where_null('updated_at').get()Order by, group by and havingquery=db.table('users').order_by('name','desc')query=query.group_by('count')query=query.having('count','>',100)users=query.get()Offset and limitusers=db.table('users').skip(10).take(5).get()users=db.table('users').offset(10).limit(5).get()JoinsThe query builder can also be used to write join statements.Basic join statementdb.table('users')\.join('contacts','users.id','=','contacts.user_id')\.join('orders','users.id','=','orders.user_id')\.select('users.id','contacts.phone','orders.price')\.get()Left join statementdb.table('users').left_join('posts','users.id','=','posts.user_id').get()You can also specify more advance join clauses:clause=JoinClause('contacts').on('users.id','=','contacts.user_id').or_on(...)db.table('users').join(clause).get()If you would like to use a “where” style clause on your joins,
you may use thewhereandor_wheremethods on a join.
Instead of comparing two columns, these methods will compare the column against a value:clause=JoinClause('contacts').on('users.id','=','contacts.user_id').where('contacts.user_id','>',5)db.table('users').join(clause).get()Advanced whereSometimes you may need to create more advanced where clauses such as “where exists” or nested parameter groupings.
It is pretty easy to do with the Orator query builderParameter groupingdb.table('users')\.where('name','=','John')\.or_where(db.query().where('votes','>',100).where('title','!=','admin')).get()The query above will produce the following SQL:SELECT*FROMusersWHEREname='John'OR(votes>100ANDtitle!='Admin')Exists statementdb.table('users').where_exists(db.table('orders').select(db.raw(1)).where_raw('order.user_id = users.id'))The query above will produce the following SQL:SELECT*FROMusersWHEREEXISTS(SELECT1FROMordersWHEREorders.user_id=users.id)AggregatesThe query builder also provides a variety of aggregate methods, `
such ascount,max,min,avg, andsum.users=db.table('users').count()price=db.table('orders').max('price')price=db.table('orders').min('price')price=db.table('orders').avg('price')total=db.table('users').sum('votes')Raw expressionsSometimes you may need to use a raw expression in a query.
These expressions will be injected into the query as strings, so be careful not to create any SQL injection points!
To create a raw expression, you may use theraw()method:db.table('users')\.select(db.raw('count(*) as user_count, status'))\.where('status','!=',1)\.group_by('status')\.get()InsertsInsert records into a tabledb.table('users').insert(email='foo@bar.com',votes=0)db.table('users').insert({'email':'foo@bar.com','votes':0})It is important to note that there is two notations available.
The reason is quite simple: the dictionary notation, though a little less practical, is here to handle
columns names which cannot be passed as keywords arguments.Inserting records into a table with an auto-incrementing IDIf the table has an auto-incrementing id, useinsert_get_idto insert a record and retrieve the id:id=db.table('users').insert_get_id({'email':'foo@bar.com','votes':0})Inserting multiple record into a tabledb.table('users').insert([{'email':'foo@bar.com','votes':0},{'email':'bar@baz.com','votes':0}])UpdatesUpdating recordsdb.table('users').where('id',1).update(votes=1)db.table('users').where('id',1).update({'votes':1})Like theinsertstatement, there is two notations available.
The reason is quite simple: the dictionary notation, though a little less practical, is here to handle
columns names which cannot be passed as keywords arguments.Incrementing or decrementing the value of a columndb.table('users').increment('votes')# Increment the value by 1db.table('users').increment('votes',5)# Increment the value by 5db.table('users').decrement('votes')# Decrement the value by 1db.table('users').decrement('votes',5)# Decrement the value by 5You can also specify additional columns to update:db.table('users').increment('votes',1,name='John')DeletesDeleting recordsdb.table('users').where('age','<',25).delete()Delete all recordsdb.table('users').delete()Truncatedb.table('users').truncate()UnionsThe query builder provides a quick and easy way to “union” two queries:first=db.table('users').where_null('first_name')users=db.table('users').where_null('last_name').union(first).get()Theunion_allmethod is also available.Read / Write connectionsSometimes you may wish to use one database connection for SELECT statements,
and another for INSERT, UPDATE, and DELETE statements. Orator makes this easy,
and the proper connections will always be used whether you use raw queries, the query
builder or the actual ORMHere is an example of how read / write connections should be configured:config={'mysql':{'read':{'host':'192.168.1.1'},'write':{'host':'192.168.1.2'},'driver':'mysql','database':'database','user':'root','password':'','prefix':''}}Note that two keys have been added to the configuration dictionary:readandwrite.
Both of these keys have dictionary values containing a single key:host.
The rest of the database options for thereadandwriteconnections
will be merged from the mainmysqldictionary. So, you only need to place items
in thereadandwritedictionaries if you wish to override the values in the main dictionary.
So, in this case,192.168.1.1will be used as the “read” connection, while192.168.1.2will be used as the “write” connection. The database credentials, prefix, character set,
and all other options in the mainmysqldictionary will be shared across both connections.Database transactionsTo run a set of operations within a database transaction, you can use thetransactionmethod
which is a context manager:withdb.transaction():db.table('users').update({votes:1})db.table('posts').delete()NoteAny exception thrown within a transaction block will cause the transaction to be rolled back
automatically.Sometimes you may need to start a transaction yourself:db.begin_transaction()You can rollback a transaction with therollbackmethod:db.rollback()You can also commit a transaction via thecommitmethod:db.commit()By default, all underlying DBAPI connections are set to be in autocommit mode
meaning that you don’t need to explicitly commit after each operation.Accessing connectionsWhen using multiple connections, you can access them via theconnection()method:users=db.connection('foo').table('users').get()You also can access the raw, underlying dbapi connection instance:db.connection().get_connection()Sometimes, you may need to reconnect to a given database:db.reconnect('foo')If you need to disconnect from the given database, use thedisconnectmethod:db.disconnect('foo')