merging: deletion of IroSession devel
authorSandro Knauß <knauss@netzguerilla.net>
Thu, 29 Mar 2012 17:28:13 +0200
branchdevel
changeset 261 6b28b135a919
parent 259 5d9c24c2cb8d (diff)
parent 260 4a03119a98c1 (current diff)
child 262 212a69cc4d44
merging: deletion of IroSession
iro/model/session.py
iro/model/utils.py
--- a/iro/controller/viewinterface.py	Thu Mar 29 17:25:14 2012 +0200
+++ b/iro/controller/viewinterface.py	Thu Mar 29 17:28:13 2012 +0200
@@ -7,8 +7,7 @@
 from .task import createJob
 
 class Interface(object):
-    '''class for a xmlrpc user
-    '''
+    '''Interface for views.'''
     
     @validate(kwd="detailed", func=vBool, need=False)
     @validate(kwd="id", func=vInteger, minv=0, need=False, none_allowed=True)
@@ -17,17 +16,23 @@
     def status(self, session, user, id=None, detailed=False):
         '''Gibt den aktuellen Status eines Auftrages oder Mehreren zurück.
 
-        Keywords:
-        apikey[string]: Der API Key
-        id[hash]: Eine Auftragsnummer
-        detailed[boolean]: Details ausgeben
+        :param string apikey: der API Key
+        :param hash id: Eine Auftragsnummer
+        :param `boolean` detailed: Details ausgeben
+
+        :return dict: 
+           - `key` ist die Auftragsnummer
+           - [`key`][**'status'**] -- Status des Auftrages
 
-        Return:
-        jobs[list]: Eine Liste der Aufträge.
-        job.name[string]: Angebener Name
-        job.status[string]: Status des Auftrages
+        .. warning:: detailed is not used yet.
+        
+        >>> status(APIKEY)
+        {"1":  {"status":"sended"},
+         "2":  {"status":"error"},
+         "10": {"status":"sending"}}
 
-
+        >>> status(APIKEY,"10")
+        {"10": {"status":"sending"}}
         '''
         user = session.merge(user)
         ret={}
@@ -45,16 +50,15 @@
     def sms(self, user, message, recipients, route="default", info=""):
         '''Versendet eine SMS.
 
-        Keywords:
-        apikey[string]: Der API Key
-        message[string]: Nachricht
-        recipients[list]: eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
-        route[string|list]: Route über den geschickt werden soll, 
+        :param string apikey: Der API Key
+        :param string message: Nachricht
+        :param list recipients: eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
+        :param route: Route über den geschickt werden soll, 
                             oder eine Liste von Routen, um Fallbacks anzugeben
-        info[string]: Infostring um eine Nachricht einer Gruppe zuorden zu können.
+        :type route: string|list
+        :param string info: Infostring um eine Nachricht einer Gruppe zuorden zu können.
 
-        Return:
-        id[hash]: Die ID des Auftrages
+        :return hash: Die ID des Auftrages
 
         '''
         d = createJob(user, recipients, SMS(message), route, info)
@@ -69,18 +73,16 @@
     def fax(self, user, subject, fax, recipients, route="default", info=""):
         '''Versendet ein FAX.
 
-        Keywords:
-        apikey[string]: Der API Key
-        subject[string]: Der Betreff
-        fax[string]: Der base64 kodierte content
-        recipients[list]: Eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
-        route[string|list]: Route über den geschickt werden soll, 
+        :param string apikey: Der API Key
+        :param string subject: Der Betreff
+        :param string fax: Der base64 kodierte content
+        :param list recipients: Eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
+        :param route: Route über den geschickt werden soll, 
                             oder eine Liste von Routen, um Fallbacks anzugeben
-        info[string]: Infostring um eine Nachricht einer Gruppe zuorden zu können.
+        :type route: string|list
+        :param string info: Infostring um eine Nachricht einer Gruppe zuorden zu können.
 
-        Return:
-        id[hash]: Die ID des Auftrages
-
+        :return hash: Die ID des Auftrages
         '''
         d = createJob(user, recipients, Fax(subject, fax), route, info)
         def ret(job):
@@ -95,19 +97,17 @@
     def mail(self, user, subject, body, recipients, frm=None, route="default", info=""):
         '''Versendet eine Email.
 
-        Keywords:
-        apikey[string]: Der API Key
-        subject[string]: Der Betreff
-        body[string]: Der Email Body
-        recipients[list]: Eine Liste von Emailadressen
-        frm[string]: Die Absender Emailadresse
-        route[string|list]: Route über den geschickt werden soll, 
+        :param string apikey: Der API Key
+        :param string subject: Der Betreff
+        :param string body: Der Email Body
+        :param list recipients: Eine Liste von Emailadressen
+        :param string frm: Die Absender Emailadresse
+        :param route: Route über den geschickt werden soll, 
                             oder eine Liste von Routen, um Fallbacks anzugeben
-        info[string]: Infostring um eine Nachricht einer Gruppe zuorden zu können.
+        :type route: string|list
+        :param string info: Infostring um eine Nachricht einer Gruppe zuorden zu können.
 
-        Return:
-        id[hash]: Die ID des Auftrages
-
+        :return hash: Die ID des Auftrages
         '''
 
         d = createJob(user, recipients, Mail(subject, body, frm), route, info)
@@ -122,14 +122,11 @@
     def routes(self, session, user, typ):
         '''Gibt eine Liste aller verfügbaren Provider zurück.
 
-        Keywords:
-        apikey[string]: Der API Key
-        typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll
+        :param string apikey: Der API Key
+        :param string typ: Der Typ zu dem die Providerloste ausgeben werden soll
                      Einer der Liste ["sms","fax","mail"]
 
-        Return:
-        providerlist[list]: Eine Liste aller möglichen Provider
-
+        :return list: Eine Liste aller möglichen Provider
         '''
         user = session.merge(user)
         offers = user.routes(typ)
@@ -141,15 +138,11 @@
     def defaultRoute(self, session, user, typ):
         '''Gibt den Standardprovider zurück.
  
-        Keywords:
-        apikey[string]: Der API Key
-        typ[string]: Der Typ zu dem die Providerliste ausgeben werden soll
+        :param string apikey: Der API Key
+        :param string typ: Der Typ zu dem die Providerliste ausgeben werden soll
                      Einer der Liste ["sms","fax","mail"]
 
-        Return:
-        provider[string]: Der Standardprovider für den angeben Typ
-
-
+        :return string: Der Standardprovider für den angeben Typ
         '''
         user = session.merge(user)
         offers = user.routes(typ, default=True)
@@ -160,17 +153,25 @@
     def bill(self, session, user):
         '''Gibt eine Rechnung zurück über die noch nicht abgerechneten Nachrichten und des Preises.
 
-        Keywords:
-        apikey[string]: Der API Key
+        :param string apikey: Der API Key
+
+        :return dict:
+            - `route` -- Entspricht einer Route; **"total"** für die Gesammtübersicht
+            - [`route`][`info`][**anz**] -- Anzahl der verschickten Nachrichten pro "Infoklasse"
+            - [`route`][`info`][**price**] -- Preis pro "Infoklasse"
+            - [`route` | **total**][**anz**] -- Anzahl der verschickten Nachrichten pro Route 
+            - [`route` | **total**][**price**] -- Preis für eine Route
 
-        Return:
-        bills[dict]: Eine Liste nach Nachrichtentypen
-        bill[route][info].anz: Anzahl der verschickten Nachrichten pro "Infoklasse"
-        bill[route][info].price: Preis pro "Infoklasse"
-        bill[route].anz: Anzahl der verschickten Nachrichten pro Route 
-        bill[route].price: Preis für eine Route
-        total.price: der Gesammtpreis
-        total.anz: die Gesammtanzahl
+        >>> bill(APIKEY)
+        {"route1": {"info1":{"anz":1,"price":2.00},
+                    "info2":{"anz":2,"price":5.00},
+                    "anz":3,"price":7.00},
+        "route2":  {"info1":{"anz":3, "price":1.00},
+                    "info3":{"anz":4, "price":8.00},
+                    "anz":7, "price":9.00},
+        "total":   {"anz":10, "price":16.00}
+        }
+
         '''
         ret={'total':{'price':0, 'anz':0}}
         user=session.merge(user)
@@ -192,11 +193,9 @@
     def telnumber(self,recipients):
         '''Gibt True zurück, falls alle übergebene Telefonnummern valide sind.
         
-        Keywords:
-        recipients[list]: Eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
+        :param list recipients: Eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
 
-        Return:
-        True: alle übergebene Nummern sind valide
+        :return: True -- alle übergebene Nummern sind valide
         '''
         return True
 
@@ -204,10 +203,8 @@
     def email(self,recipients):
         '''Gibt True zurück, falls alle übergebene Emailadressen valide sind.
         
-        Keywords:
-        recipients[list]: Eine Liste von Emailadressen
+        :param list recipients: Eine Liste von Emailadressen
 
-        Return:
-        True: alle übergebene Nummern sind valide
+        :return: True -- alle übergebene Nummern sind valide
         '''
         return True
--- a/iro/model/dbdefer.py	Thu Mar 29 17:25:14 2012 +0200
+++ b/iro/model/dbdefer.py	Thu Mar 29 17:28:13 2012 +0200
@@ -7,8 +7,16 @@
 import inspect
 
 class DBDefer(object):
-    '''a twisted sqlalchemy connector this Decorator adds a session parameter, with a valid session connection'''
+    '''a twisted sqlalchemy connector.
+    
+    This is used as a Decorator class.
+    It adds a session parameter to the function with a valid session.
+    If the session parmaeter is used in calling this function only calls a commit after running the function, instead of createing a new session.'''
     def __init__(self, engine, autocommit=False):
+        """
+        :param `sqlalchemy.engine.base.Engine` engine: a valid sqlalchemy engine object (normally created via :func:`sqlalchemy.create_engine`).
+        :param boolean autocommit: autocommit after running the function.
+        """
         self.autocommit=autocommit
         self.engine = engine
 
@@ -53,8 +61,10 @@
         return wrap
 
 dbdefer=DBDefer(None)
+"""the decorator to use. Use :func:`setEngine` to set a valid engine after program has started."""
 
 def setEngine(engine,autocommit=False): 
+    """set the engine and autocommit for the decorator (see :class:`DBDefer`)."""
     dbdefer.engine = engine
     dbdefer.autocommit = autocommit
 
--- a/iro/model/decorators.py	Thu Mar 29 17:25:14 2012 +0200
+++ b/iro/model/decorators.py	Thu Mar 29 17:28:13 2012 +0200
@@ -1,5 +1,11 @@
+"""All decorators, that are created by this package.
+
+Imports:
+
+- :func:`.user.vUser` -- a validator for apikeys.
+- :func:`.pool.runInDBPool` -- runs a actual function in dbpool.
+"""
 import types
-from decorator import decorator
 
 from .user import vUser
 from .schema import Offer
@@ -10,6 +16,17 @@
 
 @dbdefer
 def vRoute(session, value, field, typ, allowString=True, allowList=True):
+    """ a validator to test a valid route. use with :func:`iro.validate.validate`. 
+
+    :param session: a valid session object (is created by decorator :func:`iro.model.dbdefer.dbdefer`)
+    :param value: the value to test
+    :param string field: the field that is tested (only used to get a propper error message).
+    :param string typ: a typ to test the route in
+    :param boolean allowString: a single route is allowd.
+    :param boolean allowList: a list of routes is allowed.
+    :return: *value*, if value is a valid route for a given typ.
+    :raises: :exc:`iro.error.ValidateException`
+    """
     str_ = False
     ret = []
 
@@ -34,6 +51,15 @@
 
 @dbdefer
 def vTyp(value,field, session):
+    """ a validator to test a valid typ. use with :func:`iro.validate.validate`. 
+
+    :param session: a valid session object (is created by decorator :func:`iro.model.dbdefer.dbdefer`)
+    :param value: the value to test
+    :param string field: the field that is tested (only used to get a propper error message).
+    :return: *value*, if value is a valid typ.
+    :raises: :exc:`iro.error.ValidateException`
+    """
+
     for typ in Offer.typs(session):
         if value == typ[0]:
             break
--- a/iro/model/job.py	Thu Mar 29 17:25:14 2012 +0200
+++ b/iro/model/job.py	Thu Mar 29 17:28:13 2012 +0200
@@ -8,12 +8,25 @@
 from .dbdefer import dbdefer
 
 class ExJob:
-    '''One Job is a class that handles one job. One Job has multiple tasks.'''
+    ''' A ExJob object represents a message to multiple recipients over multiple offers to send.  
+
+    One single message to one recipient is handeld in :class:`iro.controller.task.Task`.
+    This class holds connections to all tasks.
+    This class is responsiple to update the status in database of one job and updating the bill.
+    '''
     
     synchronized = ["incStatus", "_status"]
 
     def __init__(self, dbjob, recipients, message, offers):
-        self.dbjob = dbjob       #Connection to mysql job element (id)
+        """Constructor of ExJob.
+        
+        :param dbjob: primary key of the job element in database
+        :param list recipients: list of all recipients
+        :param `iro.model.message.Message` message: message to send
+        :param list offers: list of all possible offers to send message over
+        """
+
+        self.dbjob = dbjob       #Connection to database job element (id)
         self.message = message
         self.recipients = recipients
         self.offers = offers
@@ -23,13 +36,23 @@
         log.msg("Job(%s) created."%(self.dbjob))
 
     def addTask(self,task):
+        """adding a task to tasks dict - key is the recipient.
+        
+        :param `iro.controller.task.Task` task: a task
+        """
         self.tasks[task.recipient] = task
 
     def incStatus(self):
+        """increments the processed messages (function is threadsafe)."""
         self.c += 1
         return self.c
 
     def _status(self, session, status):
+        """updates the status of the database object (function is threadsafe).
+
+        :param session: a valid database session
+        :param string status: new status
+        """
         job = schema.Job.get(session, self.dbjob)
         if self.status == "error":
             return
@@ -42,6 +65,10 @@
 
     @dbdefer
     def setStatus(self, session, task, status):
+        """callback of one task.
+        
+        This function  updates the database object and the bill.
+        """
         c = self.incStatus()
         job = schema.Job.get(session, self.dbjob)
         
@@ -59,6 +86,10 @@
 
     @dbdefer
     def setError(self, session, task, err):
+        """errback for one task.
+
+        This function updates the database object.
+        """
         self.incStatus()
         if self.status != "error":
             self._status(session,"error")
@@ -67,9 +98,19 @@
 threadable.synchronize(ExJob)
 
 class ExJobs(dict, MutableMapping):
-
+    """ a dict to handle all jobs.
+    """
     @dbdefer
     def create(self, session, user, recipients, message, offers, info=None):
+        """creates on new Job.
+        
+        :param session: a valid session ( created by decorator :func:`iro.model.dbdefer.dbdefer`)
+        :param `iro.model.schema.User` user: a user object
+        :param list recipients: list of all recipients
+        :param `iro.model.message.Message` message: message to send
+        :param list offers: a list of offers ( list will be reduced to the allowed offers for the **user** -- using :func:`iro.model.offer.extendProvider`)
+        :returns: the new job
+        """
         user = session.merge(user)
         job = schema.Job(info=info, status="started")
         user.jobs.append(job)
@@ -80,3 +121,4 @@
         return self[job.id]
 
 exJobs = ExJobs()
+"""the dict of all available jobs."""
--- a/iro/model/message.py	Thu Mar 29 17:25:14 2012 +0200
+++ b/iro/model/message.py	Thu Mar 29 17:28:13 2012 +0200
@@ -1,36 +1,65 @@
 # -*- coding: utf-8 -*-
-
+"""All available message typs to send send.
+"""
 from email.mime.text import MIMEText
 from email.header import Header
 from email.Utils import formatdate
 
 class Message:
+    """ Baseclass for all different message typs."""
     def __init__(self,content, typ="Message"):
+        """Constructor of Message class.
+        
+        :param content: content of the message
+        :param string typ: typ of the message
+
+        .. automethod:: __eq__
+        .. automethod:: __neq__
+        """
         self.content=content
         self.typ = typ
 
 
     def getContent(self):
+        """returns the content of the message"""
         return self.content
 
     def __eq__(self,other):
+        """return ``True`` if **other** has the same content."""
         return self.content == other.content
 
-    def __ne__(self,other):
+    def __neq__(self,other):
+        """return ``False`` if **other** has the same content."""
         return not self.__eq__(other)
 
 class SMS(Message):
+    """ A representation of one SMS"""
     def __init__(self, cont, from_ = None):
+        """Constructor of SMS class.
+
+        :param string cont: SMS content
+        :param string from_: the telnumber from the SMS should be sended.
+        """
         Message.__init__(self, cont.encode("utf-8"), typ="sms")
         self.from_ = from_
 
 class Fax(Message):
+    """A representation of one fax."""
     def __init__(self,header,cont,attachments=[]):
+        """Constructor of one fax.
+        
+        :param string header: Headline of fax
+        :param string cont: fax content
+        :param list attachments: attachments of fax
+        """
         Message.__init__(self,cont.encode("utf-8"),typ="fax")
         self.header=header
         self.attachments=attachments
 
     def getAttachment(self,i):
+        """returns a attachment
+        :param integer i:  the i-th attachment
+        """
         return self.attachments[i]
 
     def __eq__(self,other):
@@ -51,7 +80,16 @@
 
 
 class Mail(Message):
+    """A representation of one Mail"""
     def __init__(self, subject, body, frm):
+        """Constructor of one mail.
+
+        :param string subject: subject of the mail
+        :param string body: body of the mail
+        :param string frm: mailaddress to send mail from
+
+        .. automethod:: __repr__
+        """
         con = MIMEText(body.encode("utf-8"), _charset='utf-8')
         sub = Header(subject.encode('utf-8'), 'utf-8')
         con['Subject'] = sub
@@ -62,9 +100,11 @@
         Message.__init__(self, con, typ='mail')
 
     def as_string(self):
+        """returns created mail"""
         return self.content.as_string()
 
     def getFrom(self):
+        """returns the from mailaddress"""
         return self.frm
     
     def __eq__(self,other):
@@ -77,4 +117,10 @@
         return True
 
     def __repr__(self):
-        return "<Mail(%s,%s,%s)>"%(self.subject,self.body,self.frm)
+        """string representation of the class.
+
+        :returns: ``<Mail(subject, body, frm)>``
+        """
+        return "<Mail(%s, %s, %s)>"%(self.subject,self.body,self.frm)
+
+__all__=["Message", "SMS", "Fax", "Mail"]
--- a/iro/model/offer.py	Thu Mar 29 17:25:14 2012 +0200
+++ b/iro/model/offer.py	Thu Mar 29 17:28:13 2012 +0200
@@ -6,6 +6,17 @@
 
 @dbdefer
 def extendProvider(session, user, typ, ps):
+    """extend and reduce the offer list to allowed routes for **user**.
+
+    - extend the "default" to the default offerlist of the **user**.
+    - extend a Provider name to all routes of that provider.
+
+    :param session: a valid session ( created by decorator :func:`iro.model.dbdefer.dbdefer`)
+    :param `iro.model.schema.User` user: a user object
+    :param string typ: typ of the message
+    :param ps: a list of strings or a string, each one offer name or provider name
+    :return: a extended an reduced offer list
+    """
     user = session.merge(user)
     ret = []
     if ps == "default" or ps == ["default"]:
@@ -22,6 +33,10 @@
 
 @dbdefer
 def loadOffers(session):
+    """loading Offers from database and configuration file and store them in :attr:`~iro.model.offer.offers` and :attr:`~iro.model.offer.providers`.
+
+    :param session: a valid session ( created by decorator :func:`iro.model.dbdefer.dbdefer`)
+    """
     offers.clear()
     providers.clear()
     for provider in ( s for s in configParser.sections() if not s in ["main",]):
@@ -33,7 +48,11 @@
         providers[provider]=p
 
 offers={}
+"""A dict of all available offers -- key is the offer name"""
+
 providers={}
+"""A dict of all available providers -- key is the provider name"""
+
 
 configParser.registerReload(loadOffers)
 
--- a/iro/model/pool.py	Thu Mar 29 17:25:14 2012 +0200
+++ b/iro/model/pool.py	Thu Mar 29 17:28:13 2012 +0200
@@ -1,18 +1,23 @@
 from decorator import decorator
 import sqlalchemy
 class Data:
+    """A very simple class to save the thread pool for database requests"""
     def __init__(self):
         self.pool =  None
 
 data = Data()
-#a valid dbDefer decorator
+"""holds connection to the actual thread pool for the database requests"""
 
 def setPool(pool): 
+    """setting the thread pool"""
     data.pool = pool
 
 @decorator
 def runInDBPool(f,*args,**kwargs):
-    """Decorator to run DB queries in Twisted's thread pool"""
+    """Decorator to run DB queries in Twisted's thread pool.
+    
+    If last argument is a session object, the function is called directly.
+    """
     if isinstance(args[-1],sqlalchemy.orm.session.Session):
         return f(*args,**kwargs)
     else:
--- a/iro/model/schema.py	Thu Mar 29 17:25:14 2012 +0200
+++ b/iro/model/schema.py	Thu Mar 29 17:28:13 2012 +0200
@@ -17,21 +17,32 @@
 Base = declarative_base()
 
 class Userright(Base):
-    """Über welche Routen darf ein Benutzer Daten verschicken und welches sind die Standardrouten (<em>default!=None</em>) für den Benuter. Geordnert werden die Standardrouten nach default."""
+    """Allowed offers for one user. Default routes are sorted by **default** value."""
     __tablename__ = 'userright'
     user_name = Column('user', String(100), ForeignKey('apiuser.name'), primary_key=True)
+    """username"""
     offer_name = Column('offer', String(100), ForeignKey('offer.name'), primary_key=True)
+    """offername"""
     default = Column(Integer)
+    """sorting defaults routes with this value"""
     offer = relationship("Offer")
+    """connected :class:`Offer` object"""
     user = relationship("User")
+    """connected :class:`User` object"""
 
     def __init__(self, offer, default=None):
+        """Constructor of Userright class.
+
+        :param `Offer` offer: a offer object
+        :param integer default: default value
+        """
+
         self.offer = offer
         self.default = default
 
     @property
     def bill(self):
-        '''getting a list of unbilled messages grouped by Job.info'''
+        """returns a list of unbilled messages grouped by Job.info"""
         query = [ func.count(Message.id).label('anz'),      # anz of messages
                   func.sum(Message.price).label("price"),   # price of the messages
                   Job.info.label('info'),                   # info tag
@@ -45,88 +56,177 @@
         return object_session(self).query(*query).filter(and_(*filters)).group_by(Job.info)
 
 class Offer(Base):
-    """Alle Routen über die SMS, Faxe und Mails verschickt werden könnnen. <em>provider</em>, <em>typ</em> und <em>route</em> werden verwendet, um die entsprechenden Zugangsdaten laden zu können."""
+    """All possible Offers over a Message can be sended. **provider**, **typ** and **route** are used to get the data form configuration file."""
     __tablename__ = "offer"
     name = Column(String(100), primary_key=True)
+    """name of the offer"""
     provider = Column(String(100))
+    """provider name"""
     route = Column(String(100))
+    """route of the provider"""
     typ = Column(String(100))
+    """typ of message"""
+    
+    def __init__(self, **kwargs):
+        Base.__init__(self,**kwargs)
   
     @classmethod
     def get(cls, session, provider, route, typ):
+        """returns a Offer object."""
         return session.query(cls).filter(and_(cls.provider==provider, cls.route==route, cls.typ==typ)).first()
 
     @classmethod
     def routes(cls, session, typ):
+        """returns a query object of all possible offers.
+        
+        :param string typ: get all offers that support this typ
+        """
         return session.query(cls).filter_by(typ=typ)
 
     @classmethod
     def typs(cls, session):
+        """returns a list of all possible types.
+        """
         return session.query(cls.typ).distinct()
 
     @classmethod
     def providers(cls, session, typ):
+        """returns a list of all possible providers.
+        
+        :param string typ: get all providers that support this typ
+        """
         return session.query(cls.provider).filter_by(typ=typ).distinct()
 
 class Message(Base):
-    """Wenn ein Vorgang von Iro Kosten erzeugt hat wird eine neue Zeile eingefügt. Solange nicht bezahlt wurde ist <em>isBilled=0</em>."""
+    """A message that has created costs.
+    
+    **isBilled** is False since the bill is paid.
+    """
     __tablename__ = "message"
     id = Column(Integer, Sequence('message_id_seq'), primary_key=True)
+    """primary key of the message"""
+
     recipient = Column(String(100))
+    """string representation of the recipient"""
+    
     isBilled = Column(Boolean)
+    """is bill paid?"""
+
     date = Column(DateTime)
+    """date of sending the message"""
+
     price = Column(Numeric(8,4))
+    """price of sending the message"""
+
     count = Column(Integer)
+    """Count of sended messages"""
     exID = Column(String(100))
+    """external API id """
+
     job_id = Column("job", String(40), ForeignKey('job.id'))
+    """id of the connected job"""
     job = relationship("Job", backref=backref('messages'))
+    """connected :class:`Job` object"""
+    
     offer_id = Column("offer",String(100), ForeignKey('offer.name'))
+    """sended message over this offer woth ithe offer.name"""
+
     offer = relationship("Offer", backref=backref('messages'))
+    """connected :class:`Offer` object"""
+    
+    def __init__(self, **kwargs):
+        Base.__init__(self,**kwargs)
 
 
 class Job(Base):
-    """Ein kompletter Auftrag, der an Iro zum verschicken übergeben wird. Status zeigt den generellen Status des Auftrages an (<em>init</em>, <em>started</em>, <em>sending</em>, <em>sended</em> oder <em>error</em>). <em>info</em> wird verwendet um dem Benutzer eine Möglickeit zu geben verschiede Auftragsgruppen zu erstellen."""
+    """A complete Job.
+
+    - **status** show the status of the job (``init``, ``started``, ``sending``, ``sended`` or ``error``). 
+    - **info** is used to make it possible to create diffrent billing groups for user.
+    """
     __tablename__ = "job"
     id = Column(Integer, Sequence('job_id_seq'), primary_key=True)
+    """job id"""
     info = Column(String(100))
+    """job info, for billing porpuse"""
     status = Column(Enum("init","started","sending","sended","error"))
+    """status of a job"""
     user_id = Column("user", String(100), ForeignKey('apiuser.name'))
+    """connected user id"""
+
     user = relationship("User", backref=backref('jobs'))
+    """connected :class:`User` object"""
+    
+    def __init__(self, **kwargs):
+        """
+        .. automethod:: __repr__
+        """
+        Base.__init__(self,**kwargs)
 
     @property
     def extend(self):
+        """returns the connected :class:`iro.model.job.ExJob`"""
         return job.exJobs[self.id]
 
 
     def __repr__(self):
+        """string representation of the Job class.
+
+        :return: ``<Job('id' ,'info', 'status', 'user_id')>``
+        """
         try:
-            return "<Job('%s','%s','%s','%s')>"%(self.id,self.info, self.status, self.user_id)
+            return "<Job('%s' ,'%s', '%s', '%s')>"%(self.id,self.info, self.status, self.user_id)
         except DetachedInstanceError:
             return Base.__repr__(self)
 
     @classmethod
     def get(cls, session, id):
+        """returns a job object from a given id"""
         return session.query(cls).filter_by(id=id).first()
 
 class User(Base):
-    """Die Benutzerdatenbank von Iro. <em>ng_kunde</em> ist der verknüpfte netzguerilla.net Benutzer, der die Rechnung zahlt."""
+    """One user in iro"""
     __tablename__ = "apiuser"
+    
     name = Column(String(100), primary_key=True)
+    """Username"""
+    
     ng_kunde = Column(Integer)
+    """Connection to the netzguerilla userdatabase"""
+
     apikey = Column(String(50),unique=True)
+    """apikey only [0-9a-f]"""
+    
     rights = relationship('Userright')
+    """all allowed offers to send with."""
+
     def __init__(self, name, apikey):
+        """Constructor of User class.
+
+        :param string name: username
+        :param string apikey: apikey for the user
+
+        .. automethod:: __repr__
+        """
         self.name=name
         self.apikey=apikey
 
     def __repr__(self):
+        """string representation of the user class.
+
+        :return: ``<User('name', 'apikey')>``
+        """
         try:
             return "<User('%s','%s')>"%(self.name,self.apikey)
         except DetachedInstanceError:
             return Base.__repr__(self)
     
     def routes(self, typ, default = False):
-        '''returns a query object to get all possible routes for a given typ'''
+        """returns a query object to get all possible routes for a given typ
+
+        :param string typ: the typ
+        :param boolean default: use only default routes
+        """
         filters=[User.name == self.name,
                 Offer.typ == typ, 
                 ]
@@ -135,7 +235,11 @@
         return object_session(self).query(Userright.offer_name).join(Offer,User).filter(and_(*filters)).order_by(Userright.default)
    
     def providers(self, typ, default = False):
-        '''return a query object for all possible providers for a given typ'''
+        """return a query object for all possible providers for a given typ
+
+        :param string typ: the typ
+        :param boolean default: use only default routes
+        """
         filters=[User.name == self.name,
                 Offer.typ == typ, 
                 ]
@@ -144,8 +248,14 @@
         return object_session(self).query(Offer.provider).join(Userright,User).filter(and_(*filters))
 
     def has_right(self, typ, offer_name = None, provider = None, route = None):
-        '''returns offer_name, if the user is allowed to use offer otherwise None
-           ->raise sqlalchemy.orm.exc.MultipleResultsFound if not a single offer match'''
+        """if a user has the right to use a offer, provider e. al. (arguments are and connected).
+        
+        :param string typ: the typ
+        :param string offer_name: offer name
+        :param string provider: provider name
+        :param string route: a route name
+        :return: offer_name or  None (not allwoed)
+        :raises: :class:`sqlalchemy.orm.exc.MultipleResultsFound` if not a single offer match"""
         filters=[User.name == self.name,
                 Offer.typ == typ, 
                 ]
@@ -159,6 +269,12 @@
         return object_session(self).query(Userright.offer_name).join(Offer,User).filter(and_(*filters)).scalar()
 
     def job(self, id):
+        """returns a job object.
+
+        :param integer id: id of a Job
+        :return: :class:`Job`
+        :raises: :exc:`iro.error.JobNotFound`
+        """
         job = object_session(self).query(Job).join(User).filter(and_(User.name == self.name, Job.id==id)).first()
         if job is None:
             raise JobNotFound()
--- a/iro/model/utils.py	Thu Mar 29 17:25:14 2012 +0200
+++ b/iro/model/utils.py	Thu Mar 29 17:28:13 2012 +0200
@@ -1,12 +1,19 @@
 from sqlalchemy.orm import sessionmaker
 
 class WithSession(object):
-    '''a with statement for a database session connection'''
+    '''a with statement for a database session connection.'''
     def __init__(self, engine, autocommit=False):
+        """
+        :param `sqlalchemy.engine.base.Engine` engine: a valid sqlalchemy engine object (normally created via :func:`sqlalchemy.create_engine`).
+        :param boolean autocommit: autocommit after running the function.
+
+        .. automethod:: __enter__
+        """
         self.engine = engine
         self.autocommit=autocommit
     
     def __enter__(self):
+        """returns a vaild session object"""
         self.session = sessionmaker(bind=self.engine)()
         return self.session
     
--- a/iro/offer/__init__.py	Thu Mar 29 17:25:14 2012 +0200
+++ b/iro/offer/__init__.py	Thu Mar 29 17:28:13 2012 +0200
@@ -1,3 +1,12 @@
+"""Package all about providers. This packages handles the code for sending a message through a provider.
+
+- :attr:`iro.offer.provider.providers` -- a dict of all available providers
+- :func:`iro.offer.provider.getProvider` -- returns a object, from a provider name
+- :class:`iro.offer.provider.Provider` -- Baseclass for all providers
+- :class:`iro.offer.smtp.SMTP` -- a provider to send emails over smtp
+- :class:`iro.offer.smstrade.Smstrade` -- a provider to send sms via http://smstrade.de
+- :class:`iro.offer.offer.Offer` -- a represantation of an offer, this is used to send (see :mod:`iro.model.offer`).
+"""
 from .offer import Offer
 from .provider import Provider, providers, getProvider
 from .smtp import SMTP
--- a/iro/offer/offer.py	Thu Mar 29 17:25:14 2012 +0200
+++ b/iro/offer/offer.py	Thu Mar 29 17:28:13 2012 +0200
@@ -1,5 +1,21 @@
 class Offer():
+    """One Offer for sending.
+    This class is used to send a message via a provider.
+    """
     def __init__(self, name, provider, route, typ):
+        """ Constructor for Offer class.
+
+        :param string name: name is the name in database for the offer
+        :param `iro.offer.provider.Provider` provider: A valid provider object.
+        :param sting route: used to the the right send function via :meth:`iro.offer.provider.Provider.getSendFunc`.
+        :param sting typ: used to the the right send function via :meth:`iro.offer.provider.Provider.getSendFunc`.
+
+
+        .. automethod:: __call__
+        .. automethod:: __eq__
+        .. automethod:: __neq__
+        .. automethod:: __repr__
+        """
         self.name = name
         self.route = route
         self.provider = provider
@@ -7,14 +23,24 @@
         self.sendfunc = provider.getSendFunc(typ, route)
 
     def __call__(self, recipient, message):
+        """send a message to a recipient. This method uses :meth:`iro.offer.provider.Provider.send`
+
+        :param recipient: one recipient
+        :param `iro.model.message.Message` message: message to send
+        """
         return self.sendfunc(recipient, message)
 
     def __eq__(self,o):
+        """return ``True``, if o is equal."""
         return (self.name == o.name) and (self.route == o.route) and (self.provider == o.provider) and (self.typ == o.typ)
 
     def __neq__(self,o):
+        """return ``True``, if ``o`` is not equal (see :meth:`__eq__`)."""
         return not self.__eq__(o)
 
     def __repr__(self):
-        return "<Offer(%s,%s:%s,%s)>"%(self.name,self.provider,self.route,self.typ)
+        """string representation of this class for debugging purpose.
 
+        :return: ``<Offer(name, provider, route, typ)>`` """
+        return "<Offer(%s, %s, %s, %s)>"%(self.name,self.provider,self.route,self.typ)
+
--- a/iro/offer/provider.py	Thu Mar 29 17:25:14 2012 +0200
+++ b/iro/offer/provider.py	Thu Mar 29 17:28:13 2012 +0200
@@ -4,7 +4,20 @@
 from ..config import Option, Config
 
 class Provider(Config):
+    """Base class for Providerbackends."""
+
+    testmode = False
+    """- **True** -- no message to external provider should be send.
+    - **False** (default) -- message are send to external provider."""
     def __init__(self, name, typs={}):
+        """Constructor for Provider class.
+        
+        :param string name: Name of the Provider.
+        :param dict typs: A Dictonary with typs and routes.
+
+        >>> p = Provider("myProvider",{"sms":["route1","route2"]})
+        """
+
         Config.__init__(self, name)
         self.typs=typs
         self.testmode = False
@@ -12,13 +25,33 @@
         self.options = {
                 "typ":Option(vProvider, long="One available provider typ.", must=True, default=name)
                 }
+        """Options dict for Options used in configuration file (see :class:`iro.config.Option`). Ordering of configuration fields are done by :attr:`order`. 
+        
+        Sample::
+        
+               {"typ":Option(vProvider, long="One available provider typ.", must=True, default="default")}
+        
+        A child class typically use update to add more options (see code of :class:`iro.offer.smtp.SMTP`).
+        """
 
         self.order = ["typ"]
+        """ A list for ordering the options dict (:attr:`options`). """
 
     def send(self, typ, route, recipient, message):
+        """Main send function, that is called, for every single message.
+
+        .. note::
+            This function is not used directly. Normally :func:`~iro.offer.provider.Provider.getSendFunc` return this function with typ and route bound."""
         pass
 
     def getSendFunc(self, typ, route):
+        """Returns the actually send Functionfor a given typ and route.
+        
+        Normally it returns the send function with typ and route bound.
+
+        :raises: :exc:`~iro.error.NoRoute`, :exc:`~iro.error.NoTyp`
+        """
+
         try:
             if route not in self.typs[typ]:
                 raise NoRoute(route)
@@ -27,6 +60,12 @@
         return partial(self.send, typ, route)
 
 def getProvider(name, typ, config):
+    '''creates a provider object and init this with config.
+   
+    :param dict config: The Configuration dict. Normally you use ``configParser.items("section")``.
+    :param string typ: A valid typ
+    :raises: :exc:`~iro.error.NoProvider`
+    '''
     try:
         p = providers[typ](name)
         p.load(config)
@@ -35,8 +74,26 @@
         raise NoProvider(typ)
 
 def vProvider(typ, field):
+    '''validator to test the existence of the typ.
+
+    :param string typ: A typ
+    :param string field: A field name used for the Exception.
+    :return:
+        - valid -- returns typ
+        - invalid -- raises :class:`~iro.error.ValidateException`
+
+    :raises: :exc:`~iro.error.ValidateException`
+    '''
     if typ not in providers.keys():
-        raise ValidateException()
+        raise ValidateException(field=field)
     return typ
 
 providers={}
+"""Avalable Providers.
+    - **key** -- typ of provider (see configuration typ field).
+    - **value** -- class to handle specific Providertyp.
+
+To extend provider typs, just add this new typ to this dict.
+see :doc:`provider`
+   
+"""
--- a/iro/offer/smstrade.py	Thu Mar 29 17:25:14 2012 +0200
+++ b/iro/offer/smstrade.py	Thu Mar 29 17:28:13 2012 +0200
@@ -38,9 +38,11 @@
     90 : "Versand nicht moeglich.",
     100 : "SMS wurde versendet.",
     }
+"""statuscodes of external smstrade API"""
 
 
 class SmstradeException(ExternalException):
+    """An excetion that connects the status code with the excetion string (see :attr:`statusCodes`)"""
     def __init__(self,status):
         ExternalException.__init__(self)
         self.status = status
@@ -51,32 +53,41 @@
 
 
 class StatusCode:
-     def __init__(self,code, exID=None, costs=Decimal("0.0"), count=0):
+    """Class that represents the output of one smstrade request."""
+    def __init__(self,code, exID=None, costs=Decimal("0.0"), count=0):
         self.code = code
         
         self.exID = exID
         self.costs = Decimal(costs)
         self.count = int(count)
      
-     def __str__(self):
+    def __str__(self):
         if self.code in statusCodes.keys():
             return "%i: %s"%(self.code, statusCodes[self.code])
         
         return "%i: unknown statuscode."%self.code
 
-     def __int__(self):
+    def __int__(self):
         return self.code
 
 class Smstrade(Provider):
+    """A Provider to send SMS to recipients using smstrade.
+    Smstrade only supports to send SMS  and four diffrent routes: ``["basic","economy","gold","direct"]``.
+
+    It needs a smstrade Gateway Key https://login.smstrade.de/index.php?gateway in configuration file.
+
+    smstrade API documentation:  http://kundencenter.smstrade.de/sites/smstrade.de.kundencenter/__pdf/SMS-Gateway_HTTP_API_v2.pdf
+
+    The smstrade API supports a debug mode, that can be set  with :attr:`~iro.offer.provider.Provider.testmode`.
     """
-    s. auch http://kundencenter.smstrade.de/sites/smstrade.de.kundencenter/__pdf/SMS-Gateway_HTTP_API_v2.pdf
-    """
-    params= {"debug":("boolean",False),
+    _params= {"debug":("boolean",False),
             "concat_sms":('boolean',False),
             "message_id":('boolean',False),
             "count":('boolean',False),
             "cost":('boolean',False),
            }
+    '''dict for standrd values of the smstrade api, it is used to get the right values to the API.'''
+
     def __init__(self, name):       
         self.url = "https://gateway.smstrade.de"
         Provider.__init__(self, name, {"sms":["basic","economy","gold","direct"]})
@@ -86,7 +97,15 @@
         self.order.append("key")
 
     def send(self, route, recipient, sms):
-        """send SMS with $sms to $recipients"""
+        """send on SMS to recipients via route
+        
+        :param string route: A valid route ``["basic", "economy", "gold", "direct"]``
+        :param string recipient: Mobilenumber of recipient
+        :param `iro.model.message.SMS` sms: the sms to send
+        :return:
+            - All went ok -- :class:`iro.model.status.Status` object
+            - otherwise -- an exception
+        """
         #logger.debug('smstrade.sendSMS(%s,%s)'%(sms,  recipient))
 
         route = unicode(route)
@@ -105,9 +124,14 @@
             raise SmstradeException(s)
 
     def __send(self, route, to, sms):
-        """ This function is the main part of the request to the sms service.    
-        The function has to return a unicode formated string that will represent the answer of the sms service
-        to the request."""
+        """ This is the main function to request to the sms service.    
+
+        :param string route: A valid route ``["basic", "economy", "gold", "direct"]
+        :param string recipient: Mobilenumber of recipient
+        :param `iro.model.message.sms` sms: the sms to send
+        :return: a :class:`.StatusCode` object
+        """
+        
         #logger.debug('smstrade._send(%s,%s,%s)'%( route, to, sms))
         parameters= {"key": self.key,
                 "route": route,
@@ -132,9 +156,9 @@
 
         ps={}
         for p in parameters:
-            if p in self.params.keys():
-                if self.params[p][0] == "boolean":
-                    if parameters[p] != self.params[p][1]:
+            if p in self._params.keys():
+                if self._params[p][0] == "boolean":
+                    if parameters[p] != self._params[p][1]:
                         ps[p]=int(bool(parameters[p]))
             else:
                 ps[p] = parameters[p]
@@ -151,6 +175,8 @@
         return StatusCode(int(data[0]),exID=data[1],costs=data[2],count=data[3])
 
     def getSendFunc(self, typ, route):
+        """returns  a partial :meth:`send` methed with bounded route, if typ and route is valid."""
+
         Provider.getSendFunc(self, typ, route)
         return partial(self.send,route)
 
--- a/iro/offer/smtp.py	Thu Mar 29 17:25:14 2012 +0200
+++ b/iro/offer/smtp.py	Thu Mar 29 17:28:13 2012 +0200
@@ -21,6 +21,12 @@
 from .provider import Provider, providers
 
 class SMTP(Provider):
+    """A SMTP Provider to send emails.
+    This Provider has only one typ ``"mail"`` and one route ``None``. 
+    
+    If :attr:`~iro.offer.provider.Provider.testmode` is **True** no mail will be send, only a connection is created to server.
+
+    """
     def __init__(self, name):
         Provider.__init__(self,name,{"mail":[None]})
         self.options.update({
@@ -35,6 +41,14 @@
         self.order.extend(["host","port","user","password","SSL","TLS","send_from"])
 
     def send(self, recipient, mail):   
+        """sends a mail to recipient
+        
+        :param string recipient: A valid email address.
+        :param `iro.model.message.Mail` mail: Mail to send.
+        :return: 
+            - All went ok -- :class:`iro.model.status.Status` object
+            - otherwise -- an exception
+        """
         if not self.testmode:
             if self.SSL:
                 smtp = smtplib.SMTP_SSL(self.host,self.port)
@@ -54,7 +68,7 @@
             
             tmpmail=copy.deepcopy(mail)
             tmpmail.content['From'] = frm 
-            tmpmail.content['To']=recipient
+            tmpmail.content['To'] = recipient
             if not self.testmode:
                 smtp.sendmail(frm,  recipient, tmpmail.as_string())
             return Status(self, None)
@@ -62,6 +76,8 @@
             smtp.quit()
 
     def getSendFunc(self, typ, route):
+        """returns :meth:`send` method, if typ and route is valid."""
+
         Provider.getSendFunc(self, typ, route)
         return self.send