# HG changeset patch # User Sandro Knauß # Date 1333034893 -7200 # Node ID 6b28b135a9196f5d14dbb043f6d08f2cd2da6aba # Parent 5d9c24c2cb8d8da19fef5fd39c10fbfb8e55e2b7# Parent 4a03119a98c11c0528775a616e99f6451e68b8d9 merging: deletion of IroSession diff -r 4a03119a98c1 -r 6b28b135a919 iro/controller/viewinterface.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 diff -r 4a03119a98c1 -r 6b28b135a919 iro/model/dbdefer.py --- 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 diff -r 4a03119a98c1 -r 6b28b135a919 iro/model/decorators.py --- 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 diff -r 4a03119a98c1 -r 6b28b135a919 iro/model/job.py --- 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.""" diff -r 4a03119a98c1 -r 6b28b135a919 iro/model/message.py --- 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 ""%(self.subject,self.body,self.frm) + """string representation of the class. + + :returns: ```` + """ + return ""%(self.subject,self.body,self.frm) + +__all__=["Message", "SMS", "Fax", "Mail"] diff -r 4a03119a98c1 -r 6b28b135a919 iro/model/offer.py --- 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) diff -r 4a03119a98c1 -r 6b28b135a919 iro/model/pool.py --- 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: diff -r 4a03119a98c1 -r 6b28b135a919 iro/model/schema.py --- 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 (default!=None) 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. provider, typ und route 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 isBilled=0.""" + """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 (init, started, sending, sended oder error). info 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: ```` + """ try: - return ""%(self.id,self.info, self.status, self.user_id) + return ""%(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. ng_kunde 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: ```` + """ try: return ""%(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() diff -r 4a03119a98c1 -r 6b28b135a919 iro/model/utils.py --- 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 diff -r 4a03119a98c1 -r 6b28b135a919 iro/offer/__init__.py --- 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 diff -r 4a03119a98c1 -r 6b28b135a919 iro/offer/offer.py --- 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 ""%(self.name,self.provider,self.route,self.typ) + """string representation of this class for debugging purpose. + :return: ```` """ + return ""%(self.name,self.provider,self.route,self.typ) + diff -r 4a03119a98c1 -r 6b28b135a919 iro/offer/provider.py --- 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` + +""" diff -r 4a03119a98c1 -r 6b28b135a919 iro/offer/smstrade.py --- 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) diff -r 4a03119a98c1 -r 6b28b135a919 iro/offer/smtp.py --- 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