umbau zu MVC devel
authorSandro Knauß <knauss@netzguerilla.net>
Thu, 22 Dec 2011 03:13:34 +0100
branchdevel
changeset 92 f479738b4879
parent 91 191c2c1d6e53
child 93 ee10253258ad
umbau zu MVC
createdoc.py
createerm.py
iro/controller/__init__.py
iro/controller/database.py
iro/controller/user.py
iro/controller/viewinterface.py
iro/error.py
iro/model/__init__.py
iro/model/schema.py
iro/schema.py
iro/view/__init__.py
iro/view/xmlrpc.py
tests/xmlrpc.py
--- a/createdoc.py	Wed Dec 21 22:10:21 2011 +0100
+++ b/createdoc.py	Thu Dec 22 03:13:34 2011 +0100
@@ -8,9 +8,9 @@
 import re
 import inspect
 from iro.user import User as Current
-from iro.newinterface import Interface as New
+from iro.controller.viewinterface import Interface as New
 
-from createerm import createSchemaPlot,tables
+from createerm import createSchemaPlot, tables
 
 
 
--- a/createerm.py	Wed Dec 21 22:10:21 2011 +0100
+++ b/createerm.py	Thu Dec 22 03:13:34 2011 +0100
@@ -1,4 +1,4 @@
-from iro import schema
+from iro.model import schema
 
 from sqlalchemy.orm import class_mapper
 tables = []
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/controller/database.py	Thu Dec 22 03:13:34 2011 +0100
@@ -0,0 +1,29 @@
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+from model import Base
+
+engine = create_engine('sqlite:///:memory:', echo=True)
+
+def createDatabase():
+    Base.metadata.create_all(engine)
+
+Session = sessionmaker(bind=engine)
+
+class WithSession():
+    def __init__(self,autocommit=False):
+        self.autocommit=autocommit
+
+    def __enter__(self):
+        self.conn = engine.connect()
+        self.session = Session(bind=self.conn)
+        return self.session
+    
+    def __exit__(self,exc_type, exc_value, traceback):
+        if exc_type is None:
+            if self.autocommit:
+                self.session.commit()
+        else:
+            self.session.rollback()
+        self.session.close()
+        self.conn.close()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/controller/user.py	Thu Dec 22 03:13:34 2011 +0100
@@ -0,0 +1,55 @@
+import logging
+import re
+
+from .database import WithSession
+from model import User
+
+from error import UserNotFound, InterfaceException, ValidateException
+
+
+
+def rehash(hash):
+    if not re.match(r'^[a-f0-9]{15,}$', hash.lower()):
+        raise ValidateException()
+    return True
+
+def validate(**kargs):
+    def v(f):
+        def new_f(*a,**k):
+            for i in kargs:
+                kargs[i](k[i])
+            return f(*a,**k)
+        new_f.__name__ = f.__name__
+        return new_f
+    return v
+            
+
+@validate(userhash=rehash)
+def getuser(userhash):
+    with WithSession() as session:
+        user = session.query(User).filter_by(apikey=userhash).first()
+        if user is None:
+            raise UserNotFound()
+        else:
+            return user
+
+def with_user(f):
+    def new_f(*args,**kargs):
+        args=list(args)
+        logging.debug("Entering %s"%f.__name__)
+        try:
+            kargs["user"]=getuser(userhash = kargs["apikey"])
+            del kargs["apikey"]
+        except KeyError:
+            try:
+                kargs["user"]=getuser(userhash = args[1])
+            except IndexError:
+                raise InterfaceException() 
+            del args[1]
+        ret=f(*args,**kargs)
+        logging.debug("Exited %s"%f.__name__)
+        return ret
+    new_f.__name__ = f.__name__
+    return new_f
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/controller/viewinterface.py	Thu Dec 22 03:13:34 2011 +0100
@@ -0,0 +1,139 @@
+# -*- coding: utf-8 -*-
+from .user import with_user
+
+class Interface(object):
+    '''class for a xmlrpc user
+    '''
+    
+    @with_user
+    def status(self, 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
+
+        Return:
+        jobs[list]: Eine Liste der Aufträge.
+        job.name[string]: Angebener Name
+        job.status[string]: Status des Auftrages
+
+
+        '''
+        print user
+        return str(user)
+        #return user.status(id,detailed)
+        return ""
+
+    @with_user
+    def stop(self, user, id):
+        '''Stoppt den angegeben Auftrag.
+
+        Keywords:
+        apikey[string]: Der API Key
+        id[hash]: Eine Auftragsnummer
+
+        Return:
+
+        '''
+        return ""
+    
+    @with_user
+    def sms(self, user, message, recipients, route="default"):
+        '''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, 
+                            oder eine Liste von Routen, um Fallbacks anzugeben
+
+        Return:
+        id[hash]: Die ID des Auftrages
+
+        '''
+        return ""
+   
+    @with_user
+    def fax(self, user, subject, fax, recipients, route="default"):
+        '''Versendet ein FAX.
+
+        Keywords:
+        apikey[string]: Der API Key
+        subject[string]: Der Betreff
+        fax[string]: Das PDF base64 kodiert
+        recipients[list]: Eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
+        route[string|list]: Route über den geschickt werden soll, 
+                            oder eine Liste von Routen, um Fallbacks anzugeben
+
+        Return:
+        id[hash]: Die ID des Auftrages
+
+        '''
+        return ""
+
+    @with_user
+    def mail(self, user, subject,  body, recipients, frm, route="default"):
+        '''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, 
+                            oder eine Liste von Routen, um Fallbacks anzugeben
+
+        Return:
+        id[hash]: Die ID des Auftrages
+
+        '''
+        return ""
+       
+    @with_user
+    def routes(self, 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
+                     Einer der Liste ["sms","fax","mail"]
+
+        Return:
+        providerlist[list]: Eine Liste aller möglichen Provider
+
+        '''
+        return ""
+        
+    @with_user
+    def defaultRoute(self, user, typ):
+        '''Gibt den Standardprovider zurück.
+ 
+        Keywords:
+        apikey[string]: Der API Key
+        typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll
+                     Einer der Liste ["sms","fax","mail"]
+
+        Return:
+        provider[string]: Der Standardprovider für den angeben Typ
+
+
+        '''
+        return ""
+
+    @with_user
+    def statistic(self, user):
+        '''Gibt eine Statik zurück über die versendendeten Nachrichten und des Preises.
+
+        Keywords:
+        apikey[string]: Der API Key
+
+        Return:
+        statistic[list]: Eine Liste nach Nachrichtentypen
+        '''
+        return ""
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/error.py	Thu Dec 22 03:13:34 2011 +0100
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+class InterfaceException(Exception):
+    def __init__(self, code=999, msg="Unbekannter Fehler."):
+        self.code=code
+        self.msg=msg
+
+    def dict(self):
+        return {"code":self.code,
+                "msg":self.msg,
+                }
+    def __str__(self):
+        return "%i:%s"%(self.code,self.msg)
+
+class UserNotFound(InterfaceException):
+    def __init__(self):
+        InterfaceException.__init__(self, 901, "Der API-Key ist ungültig.")
+
+class ExternalException(InterfaceException):
+    def __init__(self):
+        InterfaceException.__init__(self, 950, "Fehler in externer API.")
+
+class ValidateException(Exception):
+    pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/model/__init__.py	Thu Dec 22 03:13:34 2011 +0100
@@ -0,0 +1,2 @@
+from schema import Offer, Message, Job, User
+from schema import Base
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/model/schema.py	Thu Dec 22 03:13:34 2011 +0100
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*- 
+
+from sqlalchemy import Column, Integer, String, Sequence, Boolean, DateTime, Numeric, Enum
+from sqlalchemy.ext.declarative import declarative_base
+
+#relationship
+from sqlalchemy import ForeignKey
+from sqlalchemy.orm import relationship, backref
+
+Base = declarative_base()
+
+__tables__=["User", "Job", "Message", "Offer", "Userright"]
+
+class Userright(Base):
+    """Über welche Routen darf ein Benutzer Daten verschicken und welches sind die Standardrouten (<em>isDefault=1</em>) für den Benuter."""
+    __tablename__ = 'userright'
+    user_name = Column('user', String, ForeignKey('apiuser.name'), primary_key=True)
+    offer_name = Column('offer', String, ForeignKey('offer.name'), primary_key=True)
+    isDefault = Column(Boolean)
+    offer = relationship("Offer")
+
+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."""
+    __tablename__ = "offer"
+    name = Column(String, primary_key=True)
+    provider = Column(String)
+    route = Column(String)
+    typ = Column(String)
+    
+
+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>."""
+    __tablename__ = "message"
+    id = Column(Integer, Sequence('message_id_seq'), primary_key=True)
+    recipient = Column(String)
+    isBilled = Column(Boolean)
+    date = Column(DateTime)
+    price = Column(Numeric(8,2))
+    job_hash = Column("job",Integer, ForeignKey('job.hash'))
+    job = relationship("Job", backref=backref('messages'))
+    offer_id = Column("offer",String, ForeignKey('offer.name'))
+    offer = relationship("Offer", backref=backref('messages'))
+
+
+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."""
+    __tablename__ = "job"
+    hash = Column(String, primary_key=True)
+    info = Column(String)
+    status = Column(Enum("init","started","sending","sended","error"))
+    user_id = Column("user", String, ForeignKey('apiuser.name'))
+    user = relationship("User", backref=backref('jobs'))
+
+class User(Base):
+    """Die Benutzerdatenbank von Iro. <em>ng_kunde</em> ist der verknüpfte netzguerilla.net Benutzer, der die Rechnung zahlt."""
+    __tablename__ = "apiuser"
+    name = Column(String, primary_key=True)
+    ng_kunde = Column(Integer)
+    apikey = Column(String,unique=True)
+    rights = relationship('Userright')
+    def __init__(self, name, apikey):
+        self.name=name
+        self.apikey=apikey
+
+    def __repr__(self):
+        return "<User('%s','%s')>"%(self.name,self.apikey)
+
+
--- a/iro/schema.py	Wed Dec 21 22:10:21 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-# -*- coding: utf-8 -*- 
-
-from sqlalchemy import Column, Integer, String, Sequence, Boolean, DateTime, Numeric, Enum
-from sqlalchemy import create_engine
-from sqlalchemy.ext.declarative import declarative_base
-
-#relationship
-from sqlalchemy import ForeignKey
-from sqlalchemy.orm import relationship, backref
-
-engine = create_engine('sqlite:///:memory:', echo=True)
-Base = declarative_base()
-
-__tables__=["User", "Job", "Message", "Offer", "Userright"]
-
-class Userright(Base):
-    """Über welche Routen darf ein Benutzer Daten verschicken und welches sind die Standardrouten (<em>isDefault=1</em>) für den Benuter."""
-    __tablename__ = 'userright'
-    user_name = Column('user', String, ForeignKey('apiuser.name'), primary_key=True)
-    offer_name = Column('offer', String, ForeignKey('offer.name'), primary_key=True)
-    isDefault = Column(Boolean)
-    offer = relationship("Offer")
-
-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."""
-    __tablename__ = "offer"
-    name = Column(String, primary_key=True)
-    provider = Column(String)
-    route = Column(String)
-    typ = Column(String)
-    
-
-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>."""
-    __tablename__ = "message"
-    id = Column(Integer, Sequence('message_id_seq'), primary_key=True)
-    recipient = Column(String)
-    isBilled = Column(Boolean)
-    date = Column(DateTime)
-    price = Column(Numeric(8,2))
-    job_hash = Column("job",Integer, ForeignKey('job.hash'))
-    job = relationship("Job", backref=backref('messages'))
-    offer_id = Column("offer",String, ForeignKey('offer.name'))
-    offer = relationship("Offer", backref=backref('messages'))
-
-
-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."""
-    __tablename__ = "job"
-    hash = Column(String, primary_key=True)
-    info = Column(String)
-    status = Column(Enum("init","started","sending","sended","error"))
-    user_id = Column("user", String, ForeignKey('apiuser.name'))
-    user = relationship("User", backref=backref('jobs'))
-
-class User(Base):
-    """Die Benutzerdatenbank von Iro. <em>ng_kunde</em> ist der verknüpfte netzguerilla.net Benutzer, der die Rechnung zahlt."""
-    __tablename__ = "apiuser"
-    name = Column(String, primary_key=True)
-    ng_kunde = Column(Integer)
-    apikey = Column(String,unique=True)
-    rights = relationship('Userright')
-    def __init__(self, name, apikey):
-        self.name=name
-        self.apikey=apikey
-
-    def __repr__(self):
-        return "<User('%s','%s')>"%(self.name,self.apikey)
-
-
-#Base.metadata.create_all(engine)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/view/xmlrpc.py	Thu Dec 22 03:13:34 2011 +0100
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+#Copyright (C) 2009  Sandro Knauß <bugs@sandroknauss.de>
+
+#This program is free software; you can redistribute it and/or modify it under the terms
+#of the GNU General Public License as published by the Free Software Foundation;
+#either version 3 of the License, or any later version.
+#This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+#without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+#See the GNU General Public License for more details.
+
+#You should have received a copy of the GNU General Public License
+#along with this program; if not, see <http://www.gnu.org/licenses/>.
+from twisted.web import soap, xmlrpc, resource, server
+from twisted.internet import reactor
+
+import logging
+
+
+from controller.viewinterface import Interface
+
+
+class TwistedInterface(Interface):
+    
+    def __init__(self):
+        Interface.__init__(self)
+    
+
+    def listMethods(self):
+        """Since we override lookupProcedure, its suggested to override
+        listProcedures too.
+        """
+        return self.listProcedures()
+  
+
+    def listProcedures(self):
+        """Since we override lookupProcedure, its suggested to override
+        listProcedures too.
+        """
+        return ['listMethods','status','stop','sms','fax','mail','routes','defaultRoute','statistic']
+
+
+class XMLRPCInterface(TwistedInterface,xmlrpc.XMLRPC): 
+    def __init__(self):
+        xmlrpc.XMLRPC.__init__(self) 
+        TwistedInterface.__init__(self)
+    
+    def lookupProcedure(self, procedurePath):
+        logging.debug("lookupProcedure('%s')"%procedurePath)
+        if procedurePath not in self.listProcedures():
+            raise xmlrpc.NoSuchFunction(self.NOT_FOUND,
+                        "procedure %s not found" % procedurePath)
+        try:
+            return getattr(self,procedurePath)
+        except KeyError:
+            raise xmlrpc.NoSuchFunction(self.NOT_FOUND,
+                        "procedure %s not found" % procedurePath)
+
+class SOAPInterface(TwistedInterface,soap.SOAPPublisher): 
+    def __init__(self):
+        soap.SOAPPublisher.__init__(self) 
+        TwistedInterface.__init__(self)
+        
+    def lookupFunction(self, functionName):
+        """Lookup published SOAP function.
+
+        Override in subclasses. Default behaviour - publish methods
+        starting with soap_, if they have true attribute useKeywords
+        they are expected to accept keywords.
+        
+        @return: tuple (callable, useKeywords), or (None, None) if not found.
+        """
+        if functionName in self.listProcedures():
+            function = getattr(self, functionName, None)
+            if function:
+                return function, getattr(function, "useKeywords", False)
+            return None
+        else:
+            return None
+
+def getResource():
+    root = resource.Resource()
+    root.putChild('RPC2', XMLRPCInterface())
+    root.putChild('SOAP', SOAPInterface())
+    return root
+
+def main():
+    reactor.listenTCP(7080, server.Site(getResource()))
+    reactor.run()
+
+if __name__ == '__main__':
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/xmlrpc.py	Thu Dec 22 03:13:34 2011 +0100
@@ -0,0 +1,19 @@
+from twisted.internet import reactor
+from twisted.web import server
+
+from view.xmlrpc import getResource
+from controller.database import createDatabase,WithSession
+from model import User
+
+import logging
+logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)s(%(processName)s)-%(levelname)s: %(message)s')
+
+def main():
+    reactor.listenTCP(7080, server.Site(getResource()))
+    reactor.run()
+
+if __name__ == '__main__':
+    createDatabase()
+    with WithSession(autocommit=True) as session:
+        session.add(User(name='test',apikey='abcdef123456789'))
+    main()