# HG changeset patch # User Sandro Knauß # Date 1324520014 -3600 # Node ID f479738b487954bc7bcabafa2dd302ba80a5bbdf # Parent 191c2c1d6e538852a418432cf5e9113b5521a5d2 umbau zu MVC diff -r 191c2c1d6e53 -r f479738b4879 createdoc.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 diff -r 191c2c1d6e53 -r f479738b4879 createerm.py --- 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 = [] diff -r 191c2c1d6e53 -r f479738b4879 iro/controller/__init__.py diff -r 191c2c1d6e53 -r f479738b4879 iro/controller/database.py --- /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() + diff -r 191c2c1d6e53 -r f479738b4879 iro/controller/user.py --- /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 + + diff -r 191c2c1d6e53 -r f479738b4879 iro/controller/viewinterface.py --- /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 "" + + diff -r 191c2c1d6e53 -r f479738b4879 iro/error.py --- /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 diff -r 191c2c1d6e53 -r f479738b4879 iro/model/__init__.py --- /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 diff -r 191c2c1d6e53 -r f479738b4879 iro/model/schema.py --- /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 (isDefault=1) 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. provider, typ und route 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 isBilled=0.""" + __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 (init, started, sending, sended oder error). info 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. ng_kunde 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 ""%(self.name,self.apikey) + + diff -r 191c2c1d6e53 -r f479738b4879 iro/schema.py --- 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 (isDefault=1) 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. provider, typ und route 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 isBilled=0.""" - __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 (init, started, sending, sended oder error). info 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. ng_kunde 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 ""%(self.name,self.apikey) - - -#Base.metadata.create_all(engine) diff -r 191c2c1d6e53 -r f479738b4879 iro/view/__init__.py diff -r 191c2c1d6e53 -r f479738b4879 iro/view/xmlrpc.py --- /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ß + +#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 . +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() diff -r 191c2c1d6e53 -r f479738b4879 tests/xmlrpc.py --- /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()