
Mother Manual
=============
(DocUtils Powered)

Manual for Mother versions >= 0.6.0


Index:
------
    * `Install Mother`_
    * `The Database example used on this guide`_
    * `Creating a Mother Environment`_
    * `Simple Database Actions`_
    * `Handling Multiple Records`_
    * `Handling Children`_
    * `Handling Relations`_
    * `Handling Complex Children`_
    * `The MotherFusion Class`_
    * `Performing Custom queries`_
    * `Transactions`_
    * `Sessions and Threaded Environments`_
    * `Logging`_
    * `Triggers`_
    * `Custom Complex Filters`_
    * `Using Mother without Class Definition`_

Install Mother
--------------

Pre-install requirements, one of the 2 libraries:

    * psycopg2  http://www.initd.org/            { If your using Postgres }
    * apsw      http://initd.org/tracker/pysqlite/wiki/APSW  { for SQLite }

Grab the DBMother tarball at:

    * http://dbmother.org/download

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.


