--- 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()