Mother Manual

(DocUtils Powered)

Manual for Mother versions >= 0.6.0

Index:

Install Mother

Pre-install requirements, one of the 2 libraries:

Grab the DBMother tarball at:

Untar it and change directory to the created directory:

$ tar xzf mother-x.y.z.tgz
$ cd mother-x.y.z

To install Mother, just use distutils:

# python setup.py install

Be sure to run the previous command as root on Unix systems, or to use the absolute python path on Windows if python won't be recognized as valid command.

Besides the addition of the python module, a command line tool called "mothermapper" will also get installed (normally in "/usr/bin/" on Unix systems).

The Database example used on this guide

Example code has been included with DBMother. We use the following schema for the sample code and our tutorial. Please pay close attention to the following description because we will use throughout our tutorial.

Here's a graphic representation of the database:

   Stars
       \
        \
        Planets       Lifeforms
         /    \          /
        /      \        /
       /        \      /
Moons_info     Civiliztions

We have a need to keep track of star systems. For each star system, we have a set of planets. For each planet we could have some information about it's moons. Lifeforms is a table that store information about the different forms of life present in our universe. Civilizations is a relation between Lifeforms and Planets: we will use it to store information like, "humans live on earth".

Here's the sql script (shipped with the tarball):

--
-- --
-- For Posgtgres version >= 8.1 and < 8.2.3: you need to create
-- tables WITH OIDS. Remove `WITH OIDS' otherwise.
--
-- Note that Mother is scalable and changing postgres version is
-- safe. If you upgrade your postgres to 8.2.3, for example, you
-- don't have to create tables WITH OIDS and your work environment
-- will be working at all.
-- --
--

create table stars (
    star_id         serial,
    star_name       text,
    star_age        int,
    star_mass       int,

    primary key(star_id)
) WHIT OIDS;

create table planets (
    star_id         int,
    planet_id       serial,
    planet_name     text,
    planet_mass     int,

    primary key(planet_id),
    foreign key(star_id) references stars(star_id)
) WITH OIDS;

create table moons_info (
    planet_id       int,
    moon_info_id    serial,
    num_moons       int,

    primary key(moon_info_id),
    foreign key(planet_id) references planets(planet_id)
) WITH OIDS;

create table lifeforms (
    life_id         serial,
    life_name       text,
    life_age        int,

    primary key(life_id)
) WITH OIDS;

create table civilizations (
    life_id         int,
    planet_id       int,
    age             int,

    foreign key(life_id) references lifeforms(life_id),
    foreign key(planet_id) references planets(planet_id)
) WITH OIDS;

Creating a Mother Environment

To handle a database with DBMother we need to create a DBMother environment, using the mothermapper tool. If you are ever in doubt what parameters you could use with mothermapper, try:

$ mothermapper -h

Thanks to DBMother's introspection, there is no need to write XML and models files: DBMother is able to automatically obtain the database structure.

First of all, we need to create a configuration file which allows one to specify:

  • database parameters
  • logging features
  • and the Connection Pool

To create a configuration file with default values (using Postgres as the backend), just do:

$ mothermapper -P /usr/myhome/first/dbmother.conf

otherwise use -S for SQLite:

$ mothermapper -S /usr/myhome/first/dbmother.conf

At this point you need to edit the newly created map file and set the parameters for your particular environment. Don't worry: the map file is heavy commented and most options contain suitable default values. One value you should pay attention to is "MOTHER_MAP". Here you should place a reasonable directory and file name value like "/usr/myhome/first/dbmother.map".

When you are done with your edits, test your configuration like so:

$ mothermapper -c /usr/myhome/first/dbmother.conf -t

Now the database needs tables. Altough this is not mandatory, We can use "mothermapper" to run a sql script (the sql script example is shipped with the tarball, we assume that it was saved on /usr/myhome/first/db1.sql):

$ mothermapper -c /usr/myhome/first/dbmother.conf -e /usr/myhome/first/db1.sql

Finally we need to create the DBMother map file. The map is created automatically by "mothermapper" (assuming you remembered to set the "MOTHER_MAP" value in the DBMother config file) try the following:

$ mothermapper -c /usr/myhome/first/dbmother.conf -s

Done. If all went well you can do a final test by importing your configuration file like so:

>>> from mother.mothers import init_mother
>>> init_mother('/usr/myhome/first/dbmother.conf')

Success!!

Simple Database Actions

In this section we will handle simple and single records. We use the persitent connection, so make sure that the mother configuration file enables it.

The primary concept of Mother is: declare a short but extendible class for a table: each instance of this class is a table record. To explain this concept let's start handling records on the table 'stars'.

Put the following class declaration inside the file sample.py:

from mother.mothers import *

class ClsStars(DbMother):
    table_name= 'stars'
    def __init__(self, store= {}, flag= MO_NOA, session= None):
        DbMother.__init__(self, store, flag, session)

The argument session is used when dealing with the connection pool, useful for threaded environments, as web applications. For now, forgive it.

Suppose we want to insert the Sun star on the table stars. The Sun name is 'sun', the Sun age is 10 and the Sun mass is 20:

>>> from sample import *
>>> init_mother('/my/conf/file')
>>> sun_dict= {'star_name': 'sun', 'star_mass': 20, 'star_age': 10}
>>> Sun= ClsStars(sun_dict)
>>> Sun.insert()
>>> print Sun
>>> sun_id = Sun.getField('star_id')

Note that Mother has retrieved for us the primary key of Sun automatically. Note also that Mother knows the primary key of the table stars. Moreover, Mother knows the fields of that table; try to do:

>>> wrong_dict= {'foo': 1}
>>> Sun= ClsStars(wrong_dict)
>>> Sun.insert()

A question arises: what about the initialization argument flag? This argument is useful to perform db actions during the initialization. In other words the Sun insertion could be made without the insert() call:

>>> Sun= ClsStars(sun_dict, MO_SAVE)

MO_NOA means "No Action" and is the default value of this argument.

Now we want to modigy the Sun record, setting the star_mass to 15:

>>> Sun.setField('star_mass', 15)
>>> # or Sun.setFields({'star_mass': 15}) to change more fields
>>> Sun.update()

If we want to update a record without having his mother instance we have to use his primary key:

>>> Sun= ClsStars({'star_id': sun_id, 'star_mass': 15}, MO_UP)

Note that setField() and setFields() are not allowed to change the primary key of the table.

Now, we want to remove the sun record:

>>> Sun.delete()

of, if we don't have a mother instance for this record, we can do:

>>> ClsStars({'star_id': sun_id}, MO_DEL)

To delete a record there is no need to specify his primary key:

>>> ClsStars({'star_name': 'sun'}, MO_DEL)

But note that this call will delete all records on the table stars with star_name= 'sun'. If you want to avoid this type of dangerous call, define your mother class to be "paranoid":

class ClsStars(DbMother):
    table_name= 'stars'
    paranoid = True
    def __init__(....):
        ...

Now Mother will refuse to perform db actions without a primary key.

Finally, we could want to retrieve a record.

>>> Sun= ClsStars({'star_id': sun_id})
>>> Sun.load()
>>> # or Sun.load(fields= 'star_name') if we want to load only the name
>>> # or Sun= ClsStars({'star_id': sun_id}, MO_LOAD)

There is no need to use the primary key, but remember that this call will raise an exception if there isn't a unique record with the specified fields.

If we want to give some sql specific values to some fields, as DEFAULT, NULL, True or False, just use SQL_DEFAULT, SQL_NULL, SQL_FALSE, SQL_TRUE:

>>> from mother.mothers import SQL_NULL
>>> Sun= ClsStars({'star_mass': SQL_NULL, 'star_name': 'sun'}, MO_SAVE)
>>> print Sun
>>> print Sun.getFields()

Note that the methods getFields() and getField() accept an optional argument: autoload. If autoload is True, the requested fields will be retrieved from the database if necessary.

All the described methods have a good inline doc:

>>> print Sun.getFields.__doc__

Handling Multiple Records

In the previous section we learned how to handle one record at time. Now we begin to deal with a set of records. To do that a specific class is used: MotherBox.

If an instance of a Mother class represents a record on a table, an instance of a MotherBox class is a set of record of one table. Differently by DbMother, we don't need to create a class for each table.

Let's start retrieving all records on the table stars:

>>> from sample import *
>>> init_mother('/my/conf/file')
>>> star_box= MotherBox(ClsStars, filter= None, flag= MO_LOAD)
>>> print len(star_box)

Now the star_box instance contains the records. len(star_box) gives us the number of retrieved records. We can choose to take them as dictionnaries or as Mother instances:

>>> mommas= star_box.getRecords(flag_obj= True)
>>> dicts= star_box.getRecords()
>>> for momma in mommas:
...    print momma
...
>>> for d in dicts:
...    print d
...

The MotherBox technology is more complex: a lot of args could be specified during the initialization. First of all, the filters. Just take all records with star_mass equal to 10:

>>> star_box= MotherBox(ClsStars, filter= {'star_mass': 10}, flag= MO_LOAD)
>>> star_box= MotherBox(ClsStars, filter= 'star_mass = 10', flag= MO_LOAD)

As you can see, fiilters could be dicts or strings. Filter could be also MoFilter instances: this allows to escape strings, adding security. This type of filters is explained after: for now, know that all the Mother internal operations are safe: SQL is escaped so that SQL injection is not allowed.

We could be interested to retrieve only the star_name:

>>> star_box= MotherBox(ClsStars, fields= ['star_name'], flag= MO_LOAD)

We could be interested to retrieve records oredered by star_id:

>>> star_box= MotherBox(ClsStars, order= ['star_id'], flag= MO_LOAD)

Now that we know how to load records, it's time to see how to delete and update them. To delete all records on stars with star_mass > 15, we can do:

>>> MotherBox(ClsStars, filter= 'star_mass > 15', flag= MO_DEL)

To update all records on stars with star_mass = 15, setting star_age = 2, just do:

>>> fup= {'star_age': 2}
>>> filter= {'star_mass': 15}
>>> MotherBox(ClsStars, filter= filter, fields= fup, flag= MO_UP)

Handling Children

The table planets is a child of the table stars: a planet could be in a particular star system. Now we learn how to handle children.

First of all we need to create a Mother class for the table planets: after that we have to enable the children Manager for the ClsStars class, specifiyng a list of children that we want to handle. Working on sample.py:

from mother.mothers import *

class ClsPlanets(DbMother):
    table_name= 'planets'
    def __init__(self, store= {}, flag= MO_NOA, session= None):
        DbMother.__init__(self, store, flag, session)

class ClsStars(DbMother, MotherManager):
    table_name= 'stars'
    def __init__(self, store= {}, flag= MO_NOA, session= None):
        self.initChildManager([ClsPlanets])
        DbMother.__init__(self, store, flag, session)

Note that we have subclassed the ClsStars class with MotherManager and called the initChildManager() method to specify that we want to handle planets children.

Now, let's create the Sun star and insert the planet Earth:

>>> from sample import *
>>> init_mother('/my/conf/file')
>>> Sun= ClsStars({'star_name': 'sun'}, MO_SAVE)
>>> Earth= Sun.insertPlanets({'planet_name': 'earth'})
>>> print Earth

Note that:

  • the method insertPlanets() is created automatically
  • the foreign key 'star_id' was not specified and it's assigned automatically

Now we want all the planets living on the solar system:

>>> planet_box= Sun.getMultiplePlanets()

Now all planets on the solar system with planet_mass > 12, ordered by planet_id:

>>> ftr= 'planet_mass > 12'
>>> order= ['planet_id']
>>> planet_box= Sun.getMultiplePlanets(filter= ftr, order= order)

If we are interested only on planet names, we can specify to load only this field:

>>> planet_box= Sun.getMultiplePlanets(fields= ['planet_name'])

If we want to retrieve a unique planet we can do:

>>> Earth= Sun.getPlanets({'planet_name': 'earth'})

Note that this call will raise an exception if there isn't a unique record on the table planets with planet_name = 'earth' and star_id = Sun.getField('star_id').

We can use this fact to test planet existence:

>>> try:
...   planet= Sun.getPlanets({'planet_name': 'earth'})
... except:
...   print "No planet on the solar system with name earth"
>>>

Now it's time to update:

>>> ftr= {'planet_name': 'earth'}
>>> Sun.updateMultiplePlanets({'planet_mass': 42}, filter= ftr)

This call update all planets on the solar system with planet_name = 'earth', setting planet_mass= 42.

Deleting children is similar:

>>> Sun.deleteMultiplePlanets("planet_mass > 12 ")
>>> Sun.deleteMultiplePlanets({'planet_mass': 2})

Handling Relations

As for the children of one table we can handle relations with Mother. Now we focus our attention on the table Lifeforms, Planets and Civilizations.

A record on the Civilizations table means that a certain form of life lives on a certein planet.

To handle children we need to enable the Children Manager calling initChildManager(). To enable the relation manager we have to call initRelationManager().

Let's edit sample.py, inserting a class for the Lifeforms table:

from mother.mothers import *

class ClsLifeforms(DbMother):
    table_name= 'lifeforms'
    def __init__(self, store= {}, flag= MO_NOA, session= None):
        DbMother.__init__(self, store, flag, session)

class ClsPlanets(DbMother, MotherManager):
    table_name= 'planets'
    def __init__(self, store= {}, flag= MO_NOA, session= None):
        self.initRelationManager([ClsLifeforms])
        DbMother.__init__(self, store, flag, session)

Now we can start to handle lifeforms and civilization in a powerful way:

>>> from sample import *
>>> init_mother('/my/conf/file')
>>> Mars= ClsPlanets({'planet_name': 'mars'}, MO_SAVE)
>>> martians_dict= {'life_name': 'green_people'}
>>> Martians= Mars.assignLifeforms(martians_dict, MO_SAVE)

What happens? A new record is inserted on the table Lifeforms (MO_SAVE) and, after that, a new record is inserted on the table Civilizations. At the end, Martians is a Mother instance for the record inserted on the table lifeforms.

We did two things in one: we inserted the martian Life and we assigned a relation between the planet Mars and this form of life. What about if the martian Life is already present? It's simple:

>>> Martians= ClsLifeforms(martian_dict, MO_SAVE)
>>> Mars.assignLifeforms(Martians.getFields())

We can also load the related record:

>>> martians_dict= {'life_id': 1}
>>> Martians= Mars.assignLifeforms(martians_dict, MO_LOAD)
>>> print Martians

If we want to insert some informations on the relation record, for example the field "age", which specifies that a certain form of life lives on a certain planet from "age" years, we can do it:

>>> Mars.assignLifeforms(martians_dict, MO_NOA, params= {'age': 12})

Dropping relation is easy too. To drop all relations between Mars and Lifeforms with age > 5:

>>> Mars.dropMultipleLifeforms(filter= 'age > 5')

If we want to use a filter on the table Lifeforms, we have to specify the argument jfilter:

>>> Mars.dropMultipleLifeforms(jfilter= {'life_name': 'green_people'})

These calls delete records on the table Civilizations. Moreover, we can decide to act also on the table Lifeforms:

>>> Mars.dropMultipleLifeforms(filter= 'age > 5', flag= MO_DEL)

With this call we delete not only the relation records, but also the records on the table lifeforms that live on Mars from at least 5 years.

Now we want all form of life thae live on Mars:

>>> lifes_box= Mars.joinLifeforms()

If we are interested only about life names, we can specify this passing a list of fields:

>>> lifes_box= Mars.joinLifeforms(fields= ['life_name'])

We can filter and/or order the records:

>>> filter= 'age > 5'
>>> jfilter= {'life_name': 'green_people'}
>>> lifes_box= Mars.joinLifeforms(order= ['life_id'], filter= filter)
>>> lifes_box= Mars.joinMultipleLifeforms(filter= filter, jfilter= jfilter)

Finally, we could be interested about the relation record between Mars and Martions:

>>> rel= Mars.paramsLifeforms(Martians)
>>> # we can also use dicts:
>>> martian_dict= {'life_id': Martians.getField('life_id')}
>>> rel= Mars.paramsLifeforms(martian_dict)

We could be interested to retrive also a specific field:

>>> rel= Mars.paramsLifeforms(Martians, fields= ['age'])

It's possible to retrieve a Mother class (there is no need to define the class on sample.py):

>>> momma=  Mars.paramsLifeforms(Martians, flag_obj= True)

Handling Complex Children

What about if we want to retrieve planets of the solar system with two moons or planets on the solar system where humans live?

Mother is able to handle this type of JOIN queries in an easy and transaparent way.

Let's start retreving all planets on the solar system where humans live:

>>> humans_filter = {'life_id': 1}
>>> box= Sun.getMultiplePlanets(jbuilder= ClsLifeforms, jfilter= humans_filter)

The same concept applies when dealing with Moons (it's your work to define the class ClsMoons on sample.py):

>>> moons_filter= {'num_moons': 2}
>>> box= Sun.getMultiplePlanets(jbuilder= ClsMoonsInfo, jfilter= moons_filter)

Note that Mother is able to understand which tables have to be joined, altought the two situations are a little bit different.

Obviously you can use at the same time the other arguments for the getMultiplePlanets() method:

>>> Sun.getMultiplePlanets(filter= 'planet_mass > 5',
...                 jbuilder= ClsLifeforms, jfilter= humans_filter)

The MotherFusion Class

The MotherFusion class was introduced with mother version 0.6.2. All the Mother classes presented just now are able to load records only from a unique table.

The MotherFusion class allows to load records from two or three tables. For example, we could be interested to load stars and planets at the same time, performing a join between the two tables:

>>> from mother.mothers import MotherFusion
>>> momma= MohterFusion(ClsStars, ClsPlanets)
>>> print len(momma)
>>> momma.getRecords()
>>> momma.getRecords(flag_obj= True)

The table 'planets' is a child of the table 'stars'. As we can expect, the MotherFusion class is able to understand the database structure. So we can use it for two tables linked with a relation, as for the tables 'planet' and 'lifeforms', that are linked with the table 'civilizations':

>>> momma= MotherFusion(ClsPlanets, ClsLifeforms)

Note that in both cases, you don't need to specify the nature of the tables relation: Mother is able to understand it.

As we can expect, we can use filters as usual:

>>> momma= MotherFusion(ClsPlanets, ClsStars, filter= 'planet_mass > 12')
>>> momma= MotherFusion(ClsPlanets, ClsStars, filter= {'star_mass': 14})

We can use also a MoFilter class to specify a complex filter.

We can specify also which fields we want to load; to do it, we can choose different ways. If there is no problem for ambiguity, we can list them:

>>> momma= MotherFusion(ClsStars, ClsPlanets, \
        fields= ['star_mass', 'planet_id'])

If there is an ambiguity problem, we can provide two dicts or two list with a tuple:

>>> momma= MotherFusion(ClsStars, ClsPlanets, \
        fields= ({'star_name': 'foo'}, {'planet_mass': 'bar'}))

This will produce: "SELECT stars.star_nams AS foo, planet.planet_mass AS bar". Following this case, remember that an empty dict means 'all table fields':

>>> momma= MohterFusion(ClsStars, ClsPlanets, \
        fields= ({}, {'planet_mass': 'bar'}))

will produce: "SELECT stars.*, planet.planet_mass as bar". The following fields specification is also valid:

>>> fields= (['star_name'], {'planet_mass': 'bar'})
>>> fields= (['star_name', 'star_id'], {})
>>> fields= (['star_name'], {})

Obvioulsy, we can provide a MotherSession and an order:

>>> momma= MotherFusion(ClsLifeforms, ClsPlanets,
        session= MySession, order= ['star_name'])

If we set to True the 'distinct' argument, we will have a "SELECT DISTINCT" statement:

>>> momma= MotherFusion(ClsLifeforms, ClsPlanets, distinct= True)

If the two tables are not Father-Child (or Child-Fater) tables, we can force the relation table to be used:

>>> momma= MohterFusion(ClsLifeforms, ClsPlanets, rtbl= 'civilizations')

In the same case, we could be interested to load also the records on the relation tables (for example 'civilizations.age'). If we set params= True, MotherFusion will load all the fields on the relation table, excluding the foreign keys:

>>> momma= MotherFusion(ClsLifeforms, ClsPlanets, params= True)

We can also specify manually which field to load on the relation table:

>>> momma= MotherFusion(ClsLifeforms, ClsPlanets, params= ['age'])

Performing Custom queries

SQL is a rich language: we need to perform some custom queries. To do that, we need to use the Mother database adapter. Once more we assumes that the persistent connection is used (sessions are explained after).

>>> from mother.mothers import *
>>> init_mother('/my/conf/file')
>>> from mother.abdbda import DbOne
>>> # one commit query, no return
>>> DbOne.oc_query('delete from stars')
>>> # one value query: a value is returned
>>> DbOne.ov_query('select star_name where star_id = 1)
>>> # one record query: a unique dict is returned
>>> one_record= DbOne.or_query('select * from starts where star_id = 1')
>>> # multiple records query: a list of dict is returned
>>> record_list= DbOne.mr_query('select * from stars')

Note that the same functions are exported to each Mother instace, so there is no need to import abdbda:

>>> sun= ClsStars()
>>> one_record= sun.or_query('select * from lifeforms where star_id = 1')

Transactions

Transactions are handled by Mother, which allow nested transactions. In this section we deal once more with the persistent connection.

As for the methods to perform action queries, beginTrans(), commit() and rollback() are DbOne methods, but they are exported to each Mother instance. Note that calling these methods from a Mother instance or calling them from the DbOne class produces the same effect:

>>> DbOne.beginTrans()
>>> try:
...     Sun.insert()
...     Sun.commit()
... except:
...     Sun.rollback()
>>>

The chance to call nested transactions is very useful: if we call two times beginTrans() we need to call two times commit() to commit our queries. Instead, rollback could be called once (and calling rollback more times does not produce any errror).

This allows the following code:

>>> def foo():
>>>   Sun.beginTrans()
...   try:
...     Sun.insert()
...     Sun.commit()
...   except:
...     Sun.rollback()
...     raise 'Foo'
>>>
>>> def bar():
...   DbOne.beginTrans()
...   try:
...     DbOne.oc_query(myquery)
...     foo()
...     DbOne.commit()
...   except:
...     DbOne.rollback()
...     raise 'Bar'
>>>
>>> bar()

The function foo() is not dangerous for his transaction: we can safely call it from everywhere, because Mother is able to deal with nested transaction, doing exactly what you need: calling bar(), queries will be committed only with the last commit() statement inside bar() itself.

Obviously you can call directly foo(), obtaining now a classic behaviour.

Sessions and Threaded Environments

When we need to develop applications in a threaded environments, we need isolated transactions. In fact the persistent connection is not enough, because different flux of code have to behave indipendently, while the persistent connection is shared to each Mother instance.

Mother implements a connection pool: editing the Mother configuration file it's possible to tune it in a deep way. The file is strongly commented.

To get a session, the MotherSession() call is used; we can give a name to each session: this is very useful for debugging purposes, but you can safely call this function without arguments: Mother will assign a random name to your session:

>>> from mother.mothers import *
>>> init_mother('/my/conf/file')
>>> session= MotherSession('hello_world')

Now that a session is ready, we can begin to use it:

>>> Sun= ClsStars(sun_dict, MO_SAVE, session)
>>> earth= Sun.insertPlanets(earth_dict)
>>> earth.setField('planet_mass', 34)
>>> earth.update()

The db actions are now inside your session: note that this applies also to the db actions produced by Earth, because Earth is born inside a session.

Sessions are always in a transaction state. To commit the queries we can call commit() or endSession(). The endSession() call closes also the transactions, which is putted back to the pool:

>>> session.endSession()

To rollback the queries inside a session we use rollback():

>>> session= MotherSession('hello_world')
>>> try:
...   Sun= ClsStars(sun_dict, MO_SAVE, session)
...   earth= Sun.insertPlanets(earth_dict)
...   earth.setField('planet_mass', 34)
...   earth.update()
... except:
...   session.rollback()
>>>
>>> session.endSession()

To perform custom queries inside sessions, just use the session methods or the Mother instance methods:

>>> session= MotherSession('CustomQueries')
>>> try:
...   Sun= ClsStars(sun_dict, MO_SAVE, session)
...   Sun.oc_query('delete from planets')
...   session.or_query('select * from lifeforms where life_id = 1')
... except:
...   session.rollback()
>>>
>>> session.endSession()

When you develop internal methods, make sure to propagate the session:

>>> class ClsFoo(DbMother):
...   table_name= 'foo'
...   def __init__(....):
...     ...
...
...   def wrong_method(self, *args):
...     ClsBar(mydict, MO_SAVE)
...
...   def correct_method(self, *args):
...     # this works also if no session was used to
...     # initialize this instance:
...     ClsBar(mydict, MO_SAVE, self.session)
...
...   def always_correct(self, *args):
...     # this query is executed inside a session
...     # if this instance was initialized with a
...     # session, with the persistent connection
...     # otherwise.
...     self.oc_query('delete from foobar')

Finally, to monitor the connection pool, use the following methods:

>>> from mother.mothers import MotherPoolStatus, MotherPoolStratus
>>> print MotherPoolStatus()
>>> print MotherPoolStratus()

Logging

Logging, likw the pool, is configurable on the Mother configuration file.

>>> from mother.Speaker import RED, GREEN, YELLOW
>>> import datetime
>>> Sun.log_info('It's %s', GREEN(datetime.datetime.today))
>>> Sun.log_warning('aia aia %s %s', RED(1), YELLOW('foo'))
>>> from mother.speaker import *
>>> Speaker.log_debug('the same methods are callable from the Speaker class')
>>> Speaker.log_noise('Noise Noise %s', RED('noise'))
>>> Speaker.log_insane('Insane Insane %s', RED('noise'))
>>> Sun.log_noise('Soft Soft %s', RED('noise'))

Setting the configuration level, some of the previous logged string will be dropped. To use a custom logging level, use log_log():

>>> Speaker.log_log(23, 'hi %s %s %s', 1, 2 ,'a')

If smtp logging is enabled, the following function will be available: log_mail().

Triggers

Let's use triggers:

>>> from mother.mothers import *
>>> from mother.speaker import *
>>> def before_trigger(*args):
...   Speaker.log_info(GREEN('this is my before trigger'))
>>>
>>> def after_trigger(*args):
...   Speaker.log_info(GREEN('this is my after trigger'))
>>>
>>> class TriggeredStar(DbMother):
...   table_name= 'stars'
...   def __init__(self, store= {}, flag= MO_NOA, session= None):
...       self.add_trigger(MO_SAVE, MO_BEFORE, before_trigger)
...       self.add_trigger(MO_SAVE, MO_AFTER, after_trigger)
...       DbMother.__init__(self, store, flag, session)
>>>
>>> sun= TriggeredStar({'star_name': 'sun'}, MO_SAVE)

Custom Complex Filters

Sometime we have to use strings as filters. For example, to get all planets with planet_mass > 5, we must do:

>>> MotherBox(ClsPlanets, filter= 'planet_mass > 5', flag= MO_LOAD)

When no-string filters are provided, Mother is able to escape correctly the various variables, but when we work with strings, we are tempted to do:

>>> ftr= 'blabla %s %s' % (foo, bar)
>>> MotherBox(ClsPlanets, filter= ftr, flag= MO_LOAD)

This is not good, because SQL injection is possible. To let Mother escapes your string filters, you have to use a class: MoFilter.

It's easy, instead of:

>>> filter= 'blabla %(foo)s %(bar)s' % {'foo': foo, 'bar': bar}

it's possible to do:

>>> from mother.mothers import MoFilter
>>> store= {'foo': foo, 'bar': bar}
>>> filter= MoFilter('blabla %(foo)s %(bar)s', store= store}
>>> # For SQLite you have to use:   'blabla :foo :bar'

Now Mother will escape for you the filter, adding the security layer vs SQL injection.

Moreover, we can add different type of filter in the same class:

>>> filter= MoFilter('blabla %(foo)s %(bar)s', store= store}
>>> filter.add_filter({'age': 1})
>>> filter.add_filter('dkafsak %(az)s', store= {'az': 5})
>>> MotherBox(ClsPlanets, filter= filter, flag= MO_LOAD)

Using Mother without Class Definition

Sometimes we don't need the Child Manager, the Relation Manager or other complex tools: we just want to perform basic action on some table.

To perform the basic db actions there is no need to define Mother classes: it's possible to create on demand these classes:

>>> from mother.mothers import getMotherBuilder
>>> FastClsStars= getMotherBuilder('stars')
>>> FastClsStart({'star_name': 'sun'}, MO_SAVE, MySession)

This is a very fast way to perform basic actions without writing useless lines of code.