# HG changeset patch # User Sandro Knauß # Date 1324500707 -3600 # Node ID acd4c2006602b2d100c419dbba2fcaec82731256 # Parent edf7e94cd6079754786e56a15fa93c5b9f7a7f93# Parent cb50c2c53d11eb4967cc88546fd65ae593276517 merging Verschiebe inst Dateien diff -r cb50c2c53d11 -r acd4c2006602 createdoc.py --- a/createdoc.py Wed Dec 21 21:48:27 2011 +0100 +++ b/createdoc.py Wed Dec 21 21:51:47 2011 +0100 @@ -8,7 +8,9 @@ import re import inspect from iro.user import User as Current -from iro.newuser import User as New +from iro.newinterface import Interface as New + +from createerm import createSchemaPlot,tables @@ -59,7 +61,7 @@ def keywords(f): - doc=f.__doc__ + doc=f.__doc__.decode('utf8') kwds=re.search("Keywords:\n(?P(?P\s*)(.+\n)*)\n",doc) k=kwds.group("keywords") #get rid of beginning whitespaces @@ -67,7 +69,7 @@ return section(k) def ret(f): - doc=f.__doc__ + doc=f.__doc__.decode('utf8') kwds=re.search("Return:\n(?P(?P\s*)(.+\n)*)\n",doc) k=kwds.group("ret") #get rid of beginning whitespaces @@ -94,15 +96,24 @@ (args, varargs, keywords, defaults)=inspect.getargspec(m) args= [b for b in args if b is not "self"] self.func_line=inspect.formatargspec(args, varargs, keywords, defaults) - self.description = m.__doc__.split("\n")[0] + self.description = m.__doc__.split("\n")[0].decode("utf8") self.args=[Arg(a,m) for a in args] _, self.rets=ret(m) +class Table(Link): + def __init__(self,cls): + name=cls.__name__ + self.tablename=cls.__tablename__ + title=self.tablename[0].upper()+self.tablename[1:] + Link.__init__(self,name,title) + self.description = cls.__doc__.split("\n")[0].decode("utf8") + def main(): sites=[Site("index.html","Iro"), Site("current.html","API Documentation"), Site("new.html","geplante API Documentation"), + Site("database.html","Datenbank Schema"), Site("impressum.html","Impressum"), ] @@ -131,7 +142,9 @@ Method("routes",new_methods), Method("defaultRoute",new_methods), ] - + + t = [Table(f.class_) for f in tables] + createSchemaPlot('doc/images/db-schema.svg') for site in sites: print("generiere %s" % site.name) @@ -139,7 +152,7 @@ def a(s): if s == site: return {"class":"menu active"} - stream = tmpl.generate(sites=sites,active=a,current=current,new=newm) + stream = tmpl.generate(sites=sites,active=a,current=current,new=newm,tables=t) with open('doc/'+site.name, "w") as g: g.write(stream.render('html', doctype='html')) diff -r cb50c2c53d11 -r acd4c2006602 createerm.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/createerm.py Wed Dec 21 21:51:47 2011 +0100 @@ -0,0 +1,48 @@ +from iro import schema + +from sqlalchemy.orm import class_mapper +tables = [] +for attr in schema.__tables__: + if attr[0] == '_': continue + try: + cls = getattr(schema, attr) + tables.append(class_mapper(cls)) + except: + pass + + +#schema plot +def createSchemaPlot(fname): + from sqlalchemy_schemadisplay3 import create_schema_graph + graph = create_schema_graph(metadata=schema.Base.metadata, + show_datatypes=True, # The image too large if datatypes shown + show_indexes=True, # ditto for indexes + rankdir='LR', # From left to right (instead of top to bottom) + concentrate=True, # Don't try to join the relation lines together + ) + + graph.set_size('6.5,10') + #graph.set_ratio("fill") + graph.write_svg(fname) + +#umlplot +def createUMLPlot(fname): + from sqlalchemy_schemadisplay3 import create_uml_graph + from sqlalchemy.orm import class_mapper + mappers = [] + for attr in dir(schema.model): + if attr[0] == '_': continue + try: + cls = getattr(schema.model, attr) + mappers.append(class_mapper(cls)) + except: + pass + #pass them to the function and set some formatting options + graph = create_uml_graph(mappers, + show_operations=False, # not necessary in this case + show_multiplicity_one=True, # some people like to see the ones + show_attributes=True, + ) + graph.set_size('6,5') + graph.set_ratio("fill") + graph.write_png('test.png') diff -r cb50c2c53d11 -r acd4c2006602 doc/current.html --- a/doc/current.html Wed Dec 21 21:48:27 2011 +0100 +++ b/doc/current.html Wed Dec 21 21:51:47 2011 +0100 @@ -19,7 +19,7 @@ diff -r cb50c2c53d11 -r acd4c2006602 doc/database.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/database.html Wed Dec 21 21:51:47 2011 +0100 @@ -0,0 +1,75 @@ + + + + + + Iro · Datenbank + + + + + + + + + + + + +
+
+
+

Datenbank Schema

+
+

+

+
    +
  1. 1. Datenbankschema
  2. +
  3. 3. + Tabellen +
      +
    1. 2.1 Apiuser
    2. 2.2 Job
    3. 2.3 Message
    4. 2.4 Offer
    5. 2.5 Userright
    6. +
    +
  4. +
+
+

Schema

+ +

Dies ist eine Übersicht der benutzen Tabellen die Iro benötigt.

+
+

Tabellen

+
+

Apiuser

+

Die Benutzerdatenbank von Iro.

+
+

Job

+

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.

+
+

Message

+

Wenn ein Vorgang von Iro Kosten erzeugt hat wird eine neue Zeile eingefügt. Solange nicht bezahlt wurde ist isBilled=0.

+
+

Offer

+

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.

+
+

Userright

+

Über welche Routen darf ein Benutzer Daten verschicken und welches sind die Standardrouten (isDefault=1) für den Benuter.

+
+
+
+
+
+
+
+ +
+ + \ No newline at end of file diff -r cb50c2c53d11 -r acd4c2006602 doc/images/db-schema.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/images/db-schema.svg Wed Dec 21 21:51:47 2011 +0100 @@ -0,0 +1,96 @@ + + + + + + +G + + +message + +message + +- id : INTEGER +- recipient : VARCHAR +- isBilled : BOOLEAN +- date : DATETIME +- price : NUMERIC(8, 2) +- job : INTEGER +- offer : VARCHAR + + +job + +job + +- hash : VARCHAR +- info : VARCHAR +- status : VARCHAR(7) +- user : VARCHAR + + +message->job + + ++ hash ++ job + + +offer + +offer + +- name : VARCHAR +- provider : VARCHAR +- route : VARCHAR +- typ : VARCHAR + + +message->offer + + ++ name ++ offer + + +userright + +userright + +- user : VARCHAR +- offer : VARCHAR +- isDefault : BOOLEAN + + +apiuser + +apiuser + +- name : VARCHAR +- apikey : VARCHAR + + +job->apiuser + + ++ name ++ user + + +apiuser->userright + ++ name ++ user + + +offer->userright + ++ name ++ offer + + + diff -r cb50c2c53d11 -r acd4c2006602 doc/impressum.html --- a/doc/impressum.html Wed Dec 21 21:48:27 2011 +0100 +++ b/doc/impressum.html Wed Dec 21 21:51:47 2011 +0100 @@ -19,7 +19,7 @@ diff -r cb50c2c53d11 -r acd4c2006602 doc/index.html --- a/doc/index.html Wed Dec 21 21:48:27 2011 +0100 +++ b/doc/index.html Wed Dec 21 21:51:47 2011 +0100 @@ -19,7 +19,7 @@ diff -r cb50c2c53d11 -r acd4c2006602 doc/new.html --- a/doc/new.html Wed Dec 21 21:48:27 2011 +0100 +++ b/doc/new.html Wed Dec 21 21:51:47 2011 +0100 @@ -19,7 +19,7 @@ diff -r cb50c2c53d11 -r acd4c2006602 doc/tmpl/database.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/tmpl/database.html Wed Dec 21 21:51:47 2011 +0100 @@ -0,0 +1,43 @@ + + + + + Datenbank + + + Datenbank Schema +
+

+ +

+
    +
  1. 1. Datenbankschema
  2. +
  3. 3. + Tabellen +
      +
    1. 2.${key+1} ${table.title}
    2. +
    +
  4. +
+
+ + +
+

Schema

+ +

Dies ist eine Übersicht der benutzen Tabellen die Iro benötigt.

+
+
+

Tabellen

+
+

${table.title}

+

+ Dieser Tabelle fehlt noch die Beschreibung. +

+
+
+ + diff -r cb50c2c53d11 -r acd4c2006602 iro/__init__.py --- a/iro/__init__.py Wed Dec 21 21:48:27 2011 +0100 +++ b/iro/__init__.py Wed Dec 21 21:51:47 2011 +0100 @@ -1,1 +1,1 @@ - +__version__='2.0rc0' diff -r cb50c2c53d11 -r acd4c2006602 iro/dump_test_log.py --- a/iro/dump_test_log.py Wed Dec 21 21:48:27 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -import time, os, signal -LOG_FILE = 'test.log' - -log_file = open(LOG_FILE, 'a') - -def log(msg): - log_file.write(msg + '\n') - log_file.flush() - -def SigUSR1Handler(signum, frame): - print "Reacting on USR1 signal (signal 10)" - global log_file - log_file.close() - log_file = open(LOG_FILE, 'a') - return - -def init(): - if os.path.isfile('/var/usr/dump_test_log.pid'): - print 'Have to stop server first' - os.exit(1) - else: - print 'Starting server...' - #write process id file - f = open('/var/run/dump_test_log.pid', 'w') - f.write(str(os.getpid())) - f.flush() - f.close() - print 'Process start with pid ', os.getpid() - - signal.signal(signal.SIGUSR1, SigUSR1Handler) - -def main(): - init() - count = 1 - while True: - log('log line #%d, pid: %d' % (count, os.getpid())) - count = count + 1 - time.sleep(1) - -if __name__ == '__main__': - main() - diff -r cb50c2c53d11 -r acd4c2006602 iro/merlin --- a/iro/merlin Wed Dec 21 21:48:27 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -#!/bin/bash -# -# merlin commando -# -# eine überprüfung auf korrekten aufruf findet nicht statt -# -# beispiel: -# -# ./merlin ./arthur -# -# startet programm arthur und wenn er stirbt, wird er sofort -# wiederbelebt. -# harmlose magie halt... :-) -# -LOG=/home/sandy/var/log/merlin_Iro.log -while : ; do - echo -n "$(date +'%F %T %Z') " >> $LOG - $1 status >> $LOG - if [ $? -eq 1 ]; then - echo $(date +'%F %T %Z') $1 neustarten >> $LOG - $1 start >> $LOG - fi - sleep 60 -done - diff -r cb50c2c53d11 -r acd4c2006602 iro/merlin_daemon --- a/iro/merlin_daemon Wed Dec 21 21:48:27 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -#! /bin/sh -NAME="merlin" -DEAMON=/home/sandy/svn/iro/$NAME -DEAMON_OPTS="/home/sandy/svn/iro/MyIro_daemon" -PID=/home/sandy/var/run/$NAME.pid - -test -x $DEAMON || exit 0 - -. /lib/lsb/init-functions - -case "$1" in - start) - log_daemon_msg "Starting $NAME server" $NAME - if start-stop-daemon --start --quiet --oknodo --pidfile $PID --make-pidfile --background --startas $DEAMON -- $DEAMON_OPTS; then - log_end_msg 0 - else - log_end_msg 1 - fi - ;; - stop) - log_daemon_msg "Stopping $NAME server" $NAME - if start-stop-daemon --stop --quiet --oknodo --pidfile $PID; then - log_end_msg 0 - else - log_end_msg 1 - fi - ;; - - restart) - $0 stop - sleep 1 - $0 start - ;; - - status) - status_of_proc -p $PID $DEAMON $NAME && exit 0 || exit $? - ;; - - *) - log_action_msg "Usage: $0 {start|stop|restart|status}" - exit 1 -esac - -exit 0 diff -r cb50c2c53d11 -r acd4c2006602 iro/newinterface.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/newinterface.py Wed Dec 21 21:51:47 2011 +0100 @@ -0,0 +1,269 @@ +# -*- 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 +import logging +logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)s(%(processName)s)-%(levelname)s: %(message)s') + + +class User(object): + def __init__(self,name,userhash): + self.name=name + self.userhash=userhash + + def __repr__(self): + return"User<'%s','%s'>"%(self.name,self.userhash) + +users={"1":User("spam","1"), + "2":User("foo","2") +} + +def getuser(userhash): + try: + return users[userhash] + except KeyError: + raise UserNotFound() + +def with_user(f): + def new_f(*args,**kargs): + args=list(args) + logging.debug("Entering %s"%f.__name__) + try: + kargs["user"]=getuser(kargs["apikey"]) + del kargs["apikey"] + except KeyError: + kargs["user"]=getuser(args[1]) + del args[1] + ret=f(*args,**kargs) + logging.debug("Exited %s"%f.__name__) + return ret + new_f.__name__ = f.__name__ + return new_f + + +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 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 + + + ''' + #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 "" + + 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(Interface,xmlrpc.XMLRPC): + def __init__(self): + xmlrpc.XMLRPC.__init__(self) + Interface.__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(Interface,soap.SOAPPublisher): + def __init__(self): + soap.SOAPPublisher.__init__(self) + Interface.__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 main(): + from twisted.internet import reactor + root = resource.Resource() + root.putChild('RPC2', XMLRPCInterface()) + root.putChild('SOAP', SOAPInterface()) + reactor.listenTCP(7080, server.Site(root)) + reactor.run() + +if __name__ == '__main__': + main() diff -r cb50c2c53d11 -r acd4c2006602 iro/newuser.py --- a/iro/newuser.py Wed Dec 21 21:48:27 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,138 +0,0 @@ -# -*- 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 . - -class User: - ''' - class for a xmlrpc user - ''' - - def status(self, apikey, id=None, detailed=False): - u'''Gibt den aktuellen Status eines Auftrages 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 - - - ''' - pass - - def stop(self, apikey,id): - u'''Stoppt den angegeben Auftrag. - - Keywords: - apikey[string]: Der API Key - id[hash]: Eine Auftragsnummer - - Return: - - ''' - pass - - def sms(self, apikey, message, recipients, route="default"): - u'''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 - - ''' - pass - - - def fax(self, apikey, subject, fax, recipients, route="default"): - u'''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 - - ''' - pass - - def mail(self, apikey, subject, body, recipients, frm, route="default"): - u'''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 - - ''' - pass - - def routes(self, apikey, typ): - u'''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 - - ''' - pass - - def defaultRoute(self, apikey, typ): - u'''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 - - - ''' - pass - - def statistic(self,apikey): - u'''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 - ''' - pass diff -r cb50c2c53d11 -r acd4c2006602 iro/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/schema.py Wed Dec 21 21:51:47 2011 +0100 @@ -0,0 +1,71 @@ +# -*- 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 cb50c2c53d11 -r acd4c2006602 iro/tests/__init__.py diff -r cb50c2c53d11 -r acd4c2006602 iro/tests/stopableServer.py --- a/iro/tests/stopableServer.py Wed Dec 21 21:48:27 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,118 +0,0 @@ -import ConfigParser - -import threading - -from multiprocessing import Queue -from multiprocessing.managers import BaseManager - -from iro import xmlrpc,anbieter -from iro.user import User, Admin -from iro.iro import MySMTP,MySmstrade,MyUserDB -from iro.job import SMSJob, FAXJob, MailJob -from iro.joblist import Joblist -from iro.providerlist import Providerlist - -class StoppableXMLRPCServer(xmlrpc.SecureUserDBXMLRPCServer, threading.Thread): - running=False - def __init__(self, *args, **kwargs): - xmlrpc.SecureUserDBXMLRPCServer.__init__(self, *args, **kwargs) - threading.Thread.__init__(self) - self.timeout=.5 - - def start(self): - self.running=True - threading.Thread.start(self) - - - def run(self): - # *serve_forever* muss in einem eigenen Thread laufen, damit man es - # unterbrochen werden kann! - while (self.running): - try: - self.handle_request() - except : - break - - def stop(self): - if (self.running): - self.running=False - self.server_close() - self.join() - - def __del__(self): - self.stop() - - def __enter__(self): - self.start() - return self - - def __exit__(self,type,value,traceback): - self.stop() - - -class Internal: - pass - -def init_server(): - userlist=[{"name":"test","password":"test", "class":User}, - {"name":"test2","password":"test2", "class": User}, - {"name":"admin","password":"admin", "class": Admin}] - - - - class MyManager(BaseManager): - pass - - internal=Internal() - - MyManager.register('SMSJob', SMSJob) - MyManager.register('FaxJob', FAXJob) - MyManager.register('MailJob',MailJob) - MyManager.register('Providerlist',Providerlist) - manager = MyManager() - manager.start() - - internal.manager=manager - - #anbieter erzeugen und konfigurieren - sip=anbieter.sipgate() - sip.read_basic_config("iro.conf") - - localhost=MySMTP() - localhost.read_basic_config("iro.conf") - - smstrade=MySmstrade() - smstrade.read_basic_config("iro.conf") - - #Benutzerdatenbank erstellen - queue = Queue() - internal.queue=queue - provider=Providerlist() - internal.provider=provider - provider.add("sipgate", sip, ["sms", "fax", ]) - provider.add("smstrade", smstrade, ["sms", ]) - provider.add("geonet", None, ["sms", "fax", ]) - provider.add("fax.de", None, ["sms", "fax", ]) - provider.add("localhost", localhost, ["mail", ]) - provider.setDefault("sms","smstrade") - provider.setDefault("fax","sipgate") - provider.setDefault("mail","localhost") - jobqueue=Joblist(manager, queue, provider) - internal.jobqueue=jobqueue - userdb=MyUserDB(userlist,jobqueue) - internal.userdb=userdb - - - #Server starten - cp = ConfigParser.ConfigParser() - cp.read(["iro.conf"]) - cert=cp.get('server', 'cert') - key=cp.get('server', 'key') - serv = StoppableXMLRPCServer(addr=("localhost", 8000), - userdb=userdb, - certificate=cert,privatekey=key, - logRequests=False) - serv.relam="xmlrpc" - internal.serv=serv - return internal - diff -r cb50c2c53d11 -r acd4c2006602 iro/tests/test.pdf Binary file iro/tests/test.pdf has changed diff -r cb50c2c53d11 -r acd4c2006602 iro/tests/testJob.py --- a/iro/tests/testJob.py Wed Dec 21 21:48:27 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- - -import unittest - -import xmlrpclib -from stopableServer import init_server -from iro.anbieter.content import SMS,FAX,Mail - -class TestServer(unittest.TestCase): - - def setUp(self): - self.i = init_server() - self.serv=self.i.serv - self.serv.start() - - def tearDown(self): - self.serv.stop() - - - def SendSMS(self,msg): - servstr="https://test:test@localhost:8000" - client=xmlrpclib.Server(servstr) - id=client.startSMS(msg,["01234", ] ) - self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': unicode(msg)}} ) - ele=self.i.queue.get(.1) - self.assertEqual(ele.getRecipients(),["01234", ] ) - self.assertNotEqual(ele.getMessage(),SMS('') ) - self.assertEqual(ele.getMessage(),SMS(msg) ) - - def testSimpleSMS(self): - self.SendSMS("test") - - def testSpecialCharacters(self): - self.SendSMS(u"!\"§$%&/()=?\'") - self.SendSMS(u"@ł€ł€¶ŧł¼¼½¬¬↓ŧ←ĸ↓→øđŋħ“”µ·…–|") - - def testSendFAX(self): - servstr="https://test:test@localhost:8000" - client=xmlrpclib.Server(servstr) - msg="2134wergsdfg4w56q34134æſðđæðſđŋ³@¼ŧæðđŋł€¶ŧ€¶ŧ" - id=client.startFAX("test",xmlrpclib.Binary(msg),["01234", ] ) - self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': 'test'}} ) - ele=self.i.queue.get(.1) - self.assertEqual(ele.getRecipients(),["01234", ] ) - self.assertEqual(ele.getMessage(),FAX('test','',[msg])) - - def testDoubleFAX(self): - servstr="https://test:test@localhost:8000" - client=xmlrpclib.Server(servstr) - msg="2134wergsdfg4w56q34134æſðđæðſđŋ³@¼ŧæðđŋł€¶ŧ€¶ŧ" - pdf=open('tests/test.pdf').read() - id=client.startFAX("test",[xmlrpclib.Binary(msg),xmlrpclib.Binary(pdf)],["01234", ] ) - self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': 'test'}} ) - ele=self.i.queue.get(.1) - self.assertEqual(ele.getRecipients(),["01234", ] ) - self.assertEqual(ele.getMessage(),FAX('test','',[msg, pdf])) - - def testSendMail(self): - servstr="https://test:test@localhost:8000" - client=xmlrpclib.Server(servstr) - msg=u"2134wergsdfg4w56q34134æſðđæðſđŋ³@¼ŧæðđŋł€¶ŧ€¶ŧ" - id=client.startMail("test",msg,["test@test.de", ],'absender@test.de' ) - self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': 'test'}} ) - ele=self.i.queue.get(.1) - self.assertEqual(ele.getRecipients(),["test@test.de", ] ) - self.assertEqual(ele.getMessage(),Mail('test',msg,'absender@test.de')) - self.assertEqual(ele.getMessage().as_string(),"""Content-Type: text/plain; charset="utf-8" -MIME-Version: 1.0 -Content-Transfer-Encoding: base64 -Subject: =?utf-8?q?test?= - -MjEzNHdlcmdzZGZnNHc1NnEzNDEzNMOmxb/DsMSRw6bDsMW/xJHFi8KzQMK8xafDpsOwxJHFi8WC -4oKswrbFp+KCrMK2xac= -""") - sub=u"³¼½ſðđŋſ€¼½ÖÄÜß" - id=client.startMail(sub,msg,["test@test.de", ],'absender@test.de' ) - self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': sub}}) - ele=self.i.queue.get(.1) - self.assertEqual(ele.getMessage(),Mail(sub, msg, 'absender@test.de')) - -if __name__ == "__main__": - unittest.main() diff -r cb50c2c53d11 -r acd4c2006602 iro/tests/testWorker.py --- a/iro/tests/testWorker.py Wed Dec 21 21:48:27 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,147 +0,0 @@ -# -*- coding: utf-8 -*- - -import unittest -import logging -from time import sleep - -from multiprocessing import Queue -from multiprocessing.managers import BaseManager, ListProxy - -from iro.worker import Worker -from iro.job import Job, SMSJob -from iro.anbieter.anbieter import anbieter -from iro.anbieter.content import SMS -from iro.providerlist import Providerlist - -from logging.handlers import BufferingHandler - -class MyHandler(BufferingHandler): - def __init__(self,buffer=None): - '''BufferingHandler takes a "capacity" argument - so as to know when to flush. As we're overriding - shouldFlush anyway, we can set a capacity of zero. - You can call flush() manually to clear out the - buffer. - buffer: log messages to this buffer, needed f.ex for processes or threads''' - BufferingHandler.__init__(self, 0) - self.buffer=buffer - - def shouldFlush(self): - return False - - def emit(self, record): - if record.exc_info: #sonst geht das append schief, weil nicht picklebar - record.exc_info=record.exc_info[:-1] - self.buffer.append(record.__dict__) - - -class BadJob(Job): - def start(self,id=None): - Job.start(self,id) - raise Exception("Error") - -#einen Manager anlegen, der Job und eine Liste anbietet -class MyManager(BaseManager): - pass -MyManager.register('Job', Job) -MyManager.register('SMSJob', SMSJob) -MyManager.register('BadJob', BadJob) -MyManager.register('list', list, ListProxy) -MyManager.register('Providerlist',Providerlist) - -class TestWorker(unittest.TestCase): - def setUp(self): - #erstelle eine Queue&Manager - self.manager = MyManager() - self.queue = Queue() - - #manager starten, damit wir Logging anpassen können - self.manager.start() - self.setUpLogging() - - self.providerlist=self.manager.Providerlist() - self.providerlist.add("test", anbieter() , ["sms", ]) - - #eigentlich Workerprocess starten - self.worker= Worker(self.queue) - self.worker.start() - - def tearDown(self): - #Thread&Queue stoppen - self.stop() - - #Logging änderungen rückgängig - self.tearDownLogging() - self.manager.shutdown() - - def stop(self): - self.queue.close() - self.queue.join_thread() - self.worker.terminate() - - - def setUpLogging(self): - '''Logging so umbasteln, das wir alle logging Meldung in self.buf sind''' - #wir brauchen eine threadsichere liste - self.buffer=self.manager.list() - - #Handler erstellen, der in den Buffer schreibt - self.handler = h = MyHandler(self.buffer) - self.logger =l= logging.getLogger() - - #Level anpassen - l.setLevel(logging.DEBUG) - h.setLevel(logging.DEBUG) - l.addHandler(h) - - def tearDownLogging(self): - '''crazy logging hacks wieder entfernen''' - self.logger.removeHandler(self.handler) - self.logger.setLevel(logging.NOTSET) - self.handler.close() - - - def testJob(self): - '''einen Job verarbeiten''' - job=self.manager.Job(None,None,"test") - self.assertEqual(job.getStatus(),("init",{})) - self.queue.put(job) - sleep(.1) - self.stop() - self.assertEqual(job.getStatus(),("started",{})) - self.assertEqual([(l['levelno'],l['msg']) for l in self.buffer if l['name']=="iro.worker"], - [(20,'Workerprocess läuft nun...'), - (20,'ein neuer Job(1)'), - (20,'Job(1) fertig ;)')]) - - def testBadJob(self): - '''einen Job verarbeiten, der fehlschlägt''' - job=self.manager.BadJob(None,None,"test") - self.assertEqual(job.getStatus(),("init",{})) - self.queue.put(job) - sleep(.1) - self.stop() - self.assertEqual(job.getStatus(),("error",{})) - self.assertEqual([(l['levelno'],l['msg']) for l in self.buffer if l['name']=="iro.worker"], - [(20,'Workerprocess läuft nun...'), - (20,'ein neuer Job(1)'), - (40,'Job(1) fehlgeschlagen :(')]) - error=Exception('Error') - self.assertEqual(self.buffer[-1]['exc_info'][0],type(error)) - self.assertEqual(str(self.buffer[-1]['exc_info'][1]),str(error)) - - def testSMSJob(self): - job=self.manager.SMSJob(self.providerlist, "test", "name", SMS("message"),[012345]) - self.assertEqual(job.getStatus(),("init",{})) - self.queue.put(job) - sleep(.1) - self.stop() - self.assertEqual(job.getStatus(),("sended",{'failed': [], 'good': []})) - self.assertEqual([(l['levelno'],l['msg']) for l in self.buffer if l['name']=="iro.worker"], - [(20,'Workerprocess läuft nun...'), - (20,'ein neuer Job(1)'), - (20,'Job(1) fertig ;)')]) - - -if __name__ == "__main__": - unittest.main() diff -r cb50c2c53d11 -r acd4c2006602 iro/tests/testXMLRPCServer.py --- a/iro/tests/testXMLRPCServer.py Wed Dec 21 21:48:27 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- - -import unittest - -import xmlrpclib -from stopableServer import init_server - -class TestServer(unittest.TestCase): - - def setUp(self): - self.i = init_server() - self.serv=self.i.serv - - self.serv.start() - - def tearDown(self): - self.serv.stop() - - def testLogin(self): - self.assertEqual(xmlrpclib.Server("https://test:test@localhost:8000").status(), {}) - self.assertEqual(xmlrpclib.Server("https://test2:test2@localhost:8000").status(), {}) - self.assertRaises(xmlrpclib.ProtocolError, xmlrpclib.Server("https://test2:test@localhost:8000").status) - self.assertRaises(xmlrpclib.ProtocolError,xmlrpclib.Server ("https://test:test2@localhost:8000").status) - - def testsendSMS(self): - servstr="https://test:test@localhost:8000" - client=xmlrpclib.Server(servstr) - id=client.startSMS("test",["01234", ] ) - self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': 'test'}} ) - - def testTwoUser(self): - u1="https://test:test@localhost:8000" - u2="https://test2:test2@localhost:8000" - admin="https://admin:admin@localhost:8000" - client1=xmlrpclib.Server(u1) - client2=xmlrpclib.Server(u2) - admin=xmlrpclib.Server(admin) - id1=client1.startSMS("test",["01234"] ) - self.assertEqual(client2.status(),{} ) - self.assertEqual(admin.status(id1),{id1: {'status': ['init', {}], 'name': 'test'}} ) - id2=client2.startSMS("test2",["01234"] ) - self.assertNotEqual(id1, id2) - self.assertEqual(client1.status(),{id1: {'status': ['init', {}], 'name': 'test'}}) - self.assertEqual(client2.status(),{id2: {'status': ['init', {}], 'name': 'test2'}}) - self.assertEqual(admin.status(),{id1: {'status': ['init', {}], 'name': 'test'}, - id2: {'status': ['init', {}], 'name': 'test2'}} ) - - self.assertEqual(client2.status(id1), {}) - self.assertEqual(client1.status(id2), {}) - - def testGetProvider(self): - servstr="https://test:test@localhost:8000" - client=xmlrpclib.Server(servstr) - self.assertEqual(client.getProvider("sms"), ["fax.de","geonet", "sipgate", "smstrade"]) - self.assertEqual(client.getProvider("fax"), ["fax.de","geonet", "sipgate"]) - self.assertEqual(client.getProvider("mail"), ["localhost"]) - - self.assertRaises(xmlrpclib.ProtocolError,client.getProvider, "temp") - - def testGetDefault(self): - servstr="https://test:test@localhost:8000" - client=xmlrpclib.Server(servstr) - self.assertEqual(client.getDefaultProvider("sms"), "smstrade") - self.assertEqual(client.getDefaultProvider("fax"),"sipgate") - self.assertEqual(client.getDefaultProvider("mail"), "localhost") - - self.assertRaises(xmlrpclib.ProtocolError,client.getDefaultProvider, "temp") - - -if __name__ == "__main__": - unittest.main() diff -r cb50c2c53d11 -r acd4c2006602 iro/tests/testloglock.py --- a/iro/tests/testloglock.py Wed Dec 21 21:48:27 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ - -import unittest -import os -import tempfile -import signal -import threading -import time - -class testLogLock(unittest.TestCase): - def test_ThreadingAndLocks(self): - #create a thread, have that thread grab a lock, and sleep. - fd, file = tempfile.mkstemp('.nonlog') - _lock = threading.RLock() - os.close(fd) - def locker(): - os.write(fd, 'Thread acquiring lock\n') - _lock.acquire() - os.write(fd, 'Thread acquired lock\n') - time.sleep(0.4) - os.write(fd, 'Thread releasing lock\n') - _lock.release() - os.write(fd, 'Thread released lock\n') - - #Then in the main thread, throw a signal to self - def handleSignal(sigNum, frame): - os.write(fd, 'Main Thread acquiring lock\n') - lock = False - endtime = time.time() + 1.0 - while not lock: - lock = _lock.acquire(blocking=0) - time.sleep(0.01) - if time.time() > endtime: - break - if not lock: - os.write(fd, 'Main Thread could not acquire lock\n') - return - os.write(fd, 'Main Thread acquired lock\n') - os.write(fd, 'Main Thread releasing lock\n') - _lock.release() - os.write(fd, 'Main Thread released lock\n') - - sighndlr = signal.signal(signal.SIGUSR1, handleSignal) - try: - fd = os.open(file, os.O_SYNC | os.O_WRONLY | os.O_CREAT) - thread = threading.Thread(target=locker) - thread.start() - time.sleep(0.1) - os.kill(os.getpid(), signal.SIGUSR1) - thread.join() - - #check the results - os.close(fd) - fileconts = open(file, 'r').read() - self.assertTrue('Main Thread released lock' in fileconts) - self.assertEqual(fileconts, - '''Thread acquiring lock -Thread acquired lock -Main Thread acquiring lock -Thread releasing lock -Thread released lock -Main Thread acquired lock -Main Thread releasing lock -Main Thread released lock -''') - - #Now try after acquiring the lock from the main thread - fd = os.open(file, os.O_SYNC | os.O_WRONLY | os.O_CREAT) - _lock.acquire() - thread = threading.Thread(target=locker) - thread.start() - time.sleep(0.1) - os.kill(os.getpid(), signal.SIGUSR1) - _lock.release() - thread.join() - os.close(fd) - fileconts = open(file, 'r').read() - self.assertEqual(fileconts, - '''Thread acquiring lock -Main Thread acquiring lock -Main Thread acquired lock -Main Thread releasing lock -Main Thread released lock -Thread acquired lock -Thread releasing lock -Thread released lock -''') - - finally: - signal.signal(signal.SIGUSR1, sighndlr) - try: - os.close(fd) - except OSError: - pass - try: - os.unlink(file) - except OSError: - pass - diff -r cb50c2c53d11 -r acd4c2006602 iro/user.py --- a/iro/user.py Wed Dec 21 21:48:27 2011 +0100 +++ b/iro/user.py Wed Dec 21 21:51:47 2011 +0100 @@ -28,8 +28,7 @@ class User: - ''' - class for a xmlrpc user + '''class for a xmlrpc user ''' def __init__(self, name, jobqueue): self.jobqueue=jobqueue @@ -38,7 +37,7 @@ self.features=["mail", "sms", "fax", ] def status(self,id=None,detailed=False): - u'''Gibt den aktuellen Status eines Auftrages zurück. + '''Gibt den aktuellen Status eines Auftrages zurück. Keywords: id[hash]: Eine Auftragsnummer @@ -75,7 +74,7 @@ return {} def stop(self,id): - u'''Stoppt den angegeben Auftrag. + '''Stoppt den angegeben Auftrag. Keywords: id[hash]: Eine Auftragsnummer @@ -92,7 +91,7 @@ def startSMS(self, message, recipients, provider="default"): - u'''Versendet eine SMS. + '''Versendet eine SMS. Keywords: message[string]: Nachricht @@ -111,7 +110,7 @@ def startFAX(self, subject, fax, recipients, provider="default"): - u'''Versendet ein FAX. + '''Versendet ein FAX. Keywords: subject[string]: der Betreff @@ -136,7 +135,7 @@ return id def startMail(self, subject, body, recipients, frm, provider="default"): - u'''Versendet eine Email. + '''Versendet eine Email. Keywords: subject[string]: der Betreff @@ -157,7 +156,7 @@ return id def getProvider(self, typ): - u'''Gibt eine Liste aller verfügbaren Provider zurück. + '''Gibt eine Liste aller verfügbaren Provider zurück. Keywords: typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll @@ -173,7 +172,7 @@ return self.jobqueue.providerlist.getProviderlist(typ) def getDefaultProvider(self, typ): - u'''Gibt den Standardprovider zurück. + '''Gibt den Standardprovider zurück. Keywords: typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll diff -r cb50c2c53d11 -r acd4c2006602 setup.py --- a/setup.py Wed Dec 21 21:48:27 2011 +0100 +++ b/setup.py Wed Dec 21 21:51:47 2011 +0100 @@ -1,2 +1,16 @@ +# -*- coding: utf-8 -*- + from setuptools import setup -setup(name="iro",version='0.1',install_requires=["xmlrpclib","ConfigParser","base64","multiprocessing"]) +from iro import __version__ + +setup(name='iro', + version=__version__, + packages=['iro'], + setup_requires = ['nose>=0.11'], + install_requires=['twisted>=11.1.0',"xmlrpclib","ConfigParser","base64","multiprocessing"], + test_suite="nose.collector", + description='Non Blocking Interface for sending a bunsh of SMSes, FAXes and Mails', + author='Sandro Knauß', + author_email='knauss@netzguerilla.net', + url='https://netzguerilla.net/admin/hg/iro', +) diff -r cb50c2c53d11 -r acd4c2006602 sqlalchemy_schemadisplay3.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sqlalchemy_schemadisplay3.py Wed Dec 21 21:51:47 2011 +0100 @@ -0,0 +1,173 @@ +# updated SQLA schema display to work with pydot 1.0.2 +# download from: http://www.sqlalchemy.org/trac/wiki/UsageRecipes/SchemaDisplay + + +from sqlalchemy.orm.properties import PropertyLoader +import pydot +import types + +__all__ = ['create_uml_graph', 'create_schema_graph', 'show_uml_graph', 'show_schema_graph'] + +def _mk_label(mapper, show_operations, show_attributes, show_datatypes, bordersize): + html = '<' % (bordersize, mapper.class_.__name__) + def format_col(col): + colstr = '+%s' % (col.name) + if show_datatypes: + colstr += ' : %s' % (col.type.__class__.__name__) + return colstr + + if show_attributes: + html += '' % '
'.join(format_col(col) for col in sorted(mapper.columns, key=lambda col:not col.primary_key)) + else: + [format_col(col) for col in sorted(mapper.columns, key=lambda col:not col.primary_key)] + if show_operations: + html += '' % '
'.join( + '%s(%s)' % (name,", ".join(default is _mk_label and ("%s") % arg or ("%s=%s" % (arg,repr(default))) for default,arg in + zip((func.func_defaults and len(func.func_code.co_varnames)-1-(len(func.func_defaults) or 0) or func.func_code.co_argcount-1)*[_mk_label]+list(func.func_defaults or []), func.func_code.co_varnames[1:]) + )) + for name,func in mapper.class_.__dict__.items() if isinstance(func, types.FunctionType) and func.__module__ == mapper.class_.__module__ + ) + html+= '
%s
%s
%s
>' + return html + + +def create_uml_graph(mappers, show_operations=True, show_attributes=True, show_multiplicity_one=False, show_datatypes=True, linewidth=1.0, font="Bitstream-Vera Sans"): + graph = pydot.Dot(prog='neato',mode="major",overlap="0", sep="0.01",dim="3", pack="True", ratio=".75") + relations = set() + for mapper in mappers: + graph.add_node(pydot.Node(mapper.class_.__name__, + shape="plaintext", label=_mk_label(mapper, show_operations, show_attributes, show_datatypes, linewidth), + fontname=font, fontsize="8.0", + )) + if mapper.inherits: + graph.add_edge(pydot.Edge(mapper.inherits.class_.__name__,mapper.class_.__name__, + arrowhead='none',arrowtail='empty', style="setlinewidth(%s)" % linewidth, arrowsize=str(linewidth))) + for loader in mapper.iterate_properties: + if isinstance(loader, PropertyLoader) and loader.mapper in mappers: + if hasattr(loader, 'reverse_property'): + relations.add(frozenset([loader, loader.reverse_property])) + else: + relations.add(frozenset([loader])) + + for relation in relations: + #if len(loaders) > 2: + # raise Exception("Warning: too many loaders for join %s" % join) + args = {} + def multiplicity_indicator(prop): + if prop.uselist: + return ' *' + if any(col.nullable for col in prop.local_side): + return ' 0..1' + if show_multiplicity_one: + return ' 1' + return '' + + if len(relation) == 2: + src, dest = relation + from_name = src.parent.class_.__name__ + to_name = dest.parent.class_.__name__ + + def calc_label(src,dest): + return '+' + src.key + multiplicity_indicator(src) + args['headlabel'] = calc_label(src,dest) + + args['taillabel'] = calc_label(dest,src) + args['arrowtail'] = 'none' + args['arrowhead'] = 'none' + args['constraint'] = False + else: + prop, = relation + from_name = prop.parent.class_.__name__ + to_name = prop.mapper.class_.__name__ + args['headlabel'] = '+%s%s' % (prop.key, multiplicity_indicator(prop)) + args['arrowtail'] = 'none' + args['arrowhead'] = 'vee' + + graph.add_edge(pydot.Edge(from_name,to_name, + fontname=font, fontsize="7.0", style="setlinewidth(%s)"%linewidth, arrowsize=str(linewidth), + **args) + ) + + return graph + +#from sqlalchemy import Table, text + +def _render_table_html(table, metadata, show_indexes, show_datatypes): + def format_col_type(col): + try: + return col.type.get_col_spec() + except NotImplementedError: + return str(col.type) + except AttributeError: + return str(col.type) + def format_col_str(col): + if show_datatypes: + return "- %s : %s" % (col.name, format_col_type(col)) + else: + return "- %s" % col.name + html = '<' % table.name + + html += ''.join('' % (col.name, format_col_str(col)) for col in table.columns) + html += '
%s
%s
>' + return html + +def create_schema_graph(tables=None, metadata=None, show_indexes=True, show_datatypes=True, font="Bitstream-Vera Sans", + concentrate=True, relation_options={}, rankdir='TB'): + relation_kwargs = { + 'fontsize':"7.0" + } + relation_kwargs.update(relation_options) + + if not metadata and len(tables): + metadata = tables[0].metadata + elif not tables and metadata: + if not len(metadata.tables): + metadata.reflect() + tables = metadata.tables.values() + else: + raise Exception("You need to specify at least tables or metadata") + + graph = pydot.Dot(prog="dot",mode="ipsep",overlap="ipsep",sep="0.01",concentrate=str(concentrate), rankdir=rankdir) + for table in tables: + graph.add_node(pydot.Node(str(table.name), + shape="plaintext", + label=_render_table_html(table, metadata, show_indexes, show_datatypes), + fontname=font, fontsize="7.0" + )) + + for table in tables: + for fk in table.foreign_keys: + edge = [table.name, fk.column.table.name] + is_inheritance = fk.parent.primary_key and fk.column.primary_key + if is_inheritance: + edge = edge[::-1] + graph_edge = pydot.Edge( + headlabel="+ %s"%fk.column.name, taillabel='+ %s'%fk.parent.name, + arrowhead=is_inheritance and 'none' or 'odot' , + arrowtail=(fk.parent.primary_key or fk.parent.unique) and 'empty' or 'crow' , + fontname=font, + #samehead=fk.column.name, sametail=fk.parent.name, + *edge, **relation_kwargs + ) + graph.add_edge(graph_edge) + +# not sure what this part is for, doesn't work with pydot 1.0.2 +# graph_edge.parent_graph = graph.parent_graph +# if table.name not in [e.get_source() for e in graph.get_edge_list()]: +# graph.edge_src_list.append(table.name) +# if fk.column.table.name not in graph.edge_dst_list: +# graph.edge_dst_list.append(fk.column.table.name) +# graph.sorted_graph_elements.append(graph_edge) + return graph + +def show_uml_graph(*args, **kwargs): + from cStringIO import StringIO + from PIL import Image + iostream = StringIO(create_uml_graph(*args, **kwargs).create_png()) + Image.open(iostream).show(command=kwargs.get('command','gwenview')) + +def show_schema_graph(*args, **kwargs): + from cStringIO import StringIO + from PIL import Image + iostream = StringIO(create_schema_graph(*args, **kwargs).create_png()) + Image.open(iostream).show(command=kwargs.get('command','gwenview')) diff -r cb50c2c53d11 -r acd4c2006602 tests/__init__.py diff -r cb50c2c53d11 -r acd4c2006602 tests/dump_test_log.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/dump_test_log.py Wed Dec 21 21:51:47 2011 +0100 @@ -0,0 +1,42 @@ +import time, os, signal +LOG_FILE = 'test.log' + +log_file = open(LOG_FILE, 'a') + +def log(msg): + log_file.write(msg + '\n') + log_file.flush() + +def SigUSR1Handler(signum, frame): + print "Reacting on USR1 signal (signal 10)" + global log_file + log_file.close() + log_file = open(LOG_FILE, 'a') + return + +def init(): + if os.path.isfile('/var/usr/dump_test_log.pid'): + print 'Have to stop server first' + os.exit(1) + else: + print 'Starting server...' + #write process id file + f = open('/var/run/dump_test_log.pid', 'w') + f.write(str(os.getpid())) + f.flush() + f.close() + print 'Process start with pid ', os.getpid() + + signal.signal(signal.SIGUSR1, SigUSR1Handler) + +def main(): + init() + count = 1 + while True: + log('log line #%d, pid: %d' % (count, os.getpid())) + count = count + 1 + time.sleep(1) + +if __name__ == '__main__': + main() + diff -r cb50c2c53d11 -r acd4c2006602 tests/stopableServer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/stopableServer.py Wed Dec 21 21:51:47 2011 +0100 @@ -0,0 +1,118 @@ +import ConfigParser + +import threading + +from multiprocessing import Queue +from multiprocessing.managers import BaseManager + +from iro import xmlrpc,anbieter +from iro.user import User, Admin +from iro.iro import MySMTP,MySmstrade,MyUserDB +from iro.job import SMSJob, FAXJob, MailJob +from iro.joblist import Joblist +from iro.providerlist import Providerlist + +class StoppableXMLRPCServer(xmlrpc.SecureUserDBXMLRPCServer, threading.Thread): + running=False + def __init__(self, *args, **kwargs): + xmlrpc.SecureUserDBXMLRPCServer.__init__(self, *args, **kwargs) + threading.Thread.__init__(self) + self.timeout=.5 + + def start(self): + self.running=True + threading.Thread.start(self) + + + def run(self): + # *serve_forever* muss in einem eigenen Thread laufen, damit man es + # unterbrochen werden kann! + while (self.running): + try: + self.handle_request() + except : + break + + def stop(self): + if (self.running): + self.running=False + self.server_close() + self.join() + + def __del__(self): + self.stop() + + def __enter__(self): + self.start() + return self + + def __exit__(self,type,value,traceback): + self.stop() + + +class Internal: + pass + +def init_server(): + userlist=[{"name":"test","password":"test", "class":User}, + {"name":"test2","password":"test2", "class": User}, + {"name":"admin","password":"admin", "class": Admin}] + + + + class MyManager(BaseManager): + pass + + internal=Internal() + + MyManager.register('SMSJob', SMSJob) + MyManager.register('FaxJob', FAXJob) + MyManager.register('MailJob',MailJob) + MyManager.register('Providerlist',Providerlist) + manager = MyManager() + manager.start() + + internal.manager=manager + + #anbieter erzeugen und konfigurieren + sip=anbieter.sipgate() + sip.read_basic_config("iro.conf") + + localhost=MySMTP() + localhost.read_basic_config("iro.conf") + + smstrade=MySmstrade() + smstrade.read_basic_config("iro.conf") + + #Benutzerdatenbank erstellen + queue = Queue() + internal.queue=queue + provider=Providerlist() + internal.provider=provider + provider.add("sipgate", sip, ["sms", "fax", ]) + provider.add("smstrade", smstrade, ["sms", ]) + provider.add("geonet", None, ["sms", "fax", ]) + provider.add("fax.de", None, ["sms", "fax", ]) + provider.add("localhost", localhost, ["mail", ]) + provider.setDefault("sms","smstrade") + provider.setDefault("fax","sipgate") + provider.setDefault("mail","localhost") + jobqueue=Joblist(manager, queue, provider) + internal.jobqueue=jobqueue + userdb=MyUserDB(userlist,jobqueue) + internal.userdb=userdb + + + #Server starten + cp = ConfigParser.ConfigParser() + cp.read(["iro.conf"]) + cert=cp.get('server', 'cert') + key=cp.get('server', 'key') + serv = StoppableXMLRPCServer(addr=("localhost", 8000), + userdb=userdb, + certificate=cert,privatekey=key, + logRequests=False) + serv.relam="xmlrpc" + internal.serv=serv + return internal + diff -r cb50c2c53d11 -r acd4c2006602 tests/test.pdf Binary file tests/test.pdf has changed diff -r cb50c2c53d11 -r acd4c2006602 tests/testJob.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/testJob.py Wed Dec 21 21:51:47 2011 +0100 @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +import unittest + +import xmlrpclib +from stopableServer import init_server +from iro.anbieter.content import SMS,FAX,Mail + +class TestServer(unittest.TestCase): + + def setUp(self): + self.i = init_server() + self.serv=self.i.serv + self.serv.start() + + def tearDown(self): + self.serv.stop() + + + def SendSMS(self,msg): + servstr="https://test:test@localhost:8000" + client=xmlrpclib.Server(servstr) + id=client.startSMS(msg,["01234", ] ) + self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': unicode(msg)}} ) + ele=self.i.queue.get(.1) + self.assertEqual(ele.getRecipients(),["01234", ] ) + self.assertNotEqual(ele.getMessage(),SMS('') ) + self.assertEqual(ele.getMessage(),SMS(msg) ) + + def testSimpleSMS(self): + self.SendSMS("test") + + def testSpecialCharacters(self): + self.SendSMS(u"!\"§$%&/()=?\'") + self.SendSMS(u"@ł€ł€¶ŧł¼¼½¬¬↓ŧ←ĸ↓→øđŋħ“”µ·…–|") + + def testSendFAX(self): + servstr="https://test:test@localhost:8000" + client=xmlrpclib.Server(servstr) + msg="2134wergsdfg4w56q34134æſðđæðſđŋ³@¼ŧæðđŋł€¶ŧ€¶ŧ" + id=client.startFAX("test",xmlrpclib.Binary(msg),["01234", ] ) + self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': 'test'}} ) + ele=self.i.queue.get(.1) + self.assertEqual(ele.getRecipients(),["01234", ] ) + self.assertEqual(ele.getMessage(),FAX('test','',[msg])) + + def testDoubleFAX(self): + servstr="https://test:test@localhost:8000" + client=xmlrpclib.Server(servstr) + msg="2134wergsdfg4w56q34134æſðđæðſđŋ³@¼ŧæðđŋł€¶ŧ€¶ŧ" + pdf=open('tests/test.pdf').read() + id=client.startFAX("test",[xmlrpclib.Binary(msg),xmlrpclib.Binary(pdf)],["01234", ] ) + self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': 'test'}} ) + ele=self.i.queue.get(.1) + self.assertEqual(ele.getRecipients(),["01234", ] ) + self.assertEqual(ele.getMessage(),FAX('test','',[msg, pdf])) + + def testSendMail(self): + servstr="https://test:test@localhost:8000" + client=xmlrpclib.Server(servstr) + msg=u"2134wergsdfg4w56q34134æſðđæðſđŋ³@¼ŧæðđŋł€¶ŧ€¶ŧ" + id=client.startMail("test",msg,["test@test.de", ],'absender@test.de' ) + self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': 'test'}} ) + ele=self.i.queue.get(.1) + self.assertEqual(ele.getRecipients(),["test@test.de", ] ) + self.assertEqual(ele.getMessage(),Mail('test',msg,'absender@test.de')) + self.assertEqual(ele.getMessage().as_string(),"""Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: base64 +Subject: =?utf-8?q?test?= + +MjEzNHdlcmdzZGZnNHc1NnEzNDEzNMOmxb/DsMSRw6bDsMW/xJHFi8KzQMK8xafDpsOwxJHFi8WC +4oKswrbFp+KCrMK2xac= +""") + sub=u"³¼½ſðđŋſ€¼½ÖÄÜß" + id=client.startMail(sub,msg,["test@test.de", ],'absender@test.de' ) + self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': sub}}) + ele=self.i.queue.get(.1) + self.assertEqual(ele.getMessage(),Mail(sub, msg, 'absender@test.de')) + +if __name__ == "__main__": + unittest.main() diff -r cb50c2c53d11 -r acd4c2006602 tests/testWorker.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/testWorker.py Wed Dec 21 21:51:47 2011 +0100 @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- + +import unittest +import logging +from time import sleep + +from multiprocessing import Queue +from multiprocessing.managers import BaseManager, ListProxy + +from iro.worker import Worker +from iro.job import Job, SMSJob +from iro.anbieter.anbieter import anbieter +from iro.anbieter.content import SMS +from iro.providerlist import Providerlist + +from logging.handlers import BufferingHandler + +class MyHandler(BufferingHandler): + def __init__(self,buffer=None): + '''BufferingHandler takes a "capacity" argument + so as to know when to flush. As we're overriding + shouldFlush anyway, we can set a capacity of zero. + You can call flush() manually to clear out the + buffer. + buffer: log messages to this buffer, needed f.ex for processes or threads''' + BufferingHandler.__init__(self, 0) + self.buffer=buffer + + def shouldFlush(self): + return False + + def emit(self, record): + if record.exc_info: #sonst geht das append schief, weil nicht picklebar + record.exc_info=record.exc_info[:-1] + self.buffer.append(record.__dict__) + + +class BadJob(Job): + def start(self,id=None): + Job.start(self,id) + raise Exception("Error") + +#einen Manager anlegen, der Job und eine Liste anbietet +class MyManager(BaseManager): + pass +MyManager.register('Job', Job) +MyManager.register('SMSJob', SMSJob) +MyManager.register('BadJob', BadJob) +MyManager.register('list', list, ListProxy) +MyManager.register('Providerlist',Providerlist) + +class TestWorker(unittest.TestCase): + def setUp(self): + #erstelle eine Queue&Manager + self.manager = MyManager() + self.queue = Queue() + + #manager starten, damit wir Logging anpassen können + self.manager.start() + self.setUpLogging() + + self.providerlist=self.manager.Providerlist() + self.providerlist.add("test", anbieter() , ["sms", ]) + + #eigentlich Workerprocess starten + self.worker= Worker(self.queue) + self.worker.start() + + def tearDown(self): + #Thread&Queue stoppen + self.stop() + + #Logging änderungen rückgängig + self.tearDownLogging() + self.manager.shutdown() + + def stop(self): + self.queue.close() + self.queue.join_thread() + self.worker.terminate() + + + def setUpLogging(self): + '''Logging so umbasteln, das wir alle logging Meldung in self.buf sind''' + #wir brauchen eine threadsichere liste + self.buffer=self.manager.list() + + #Handler erstellen, der in den Buffer schreibt + self.handler = h = MyHandler(self.buffer) + self.logger =l= logging.getLogger() + + #Level anpassen + l.setLevel(logging.DEBUG) + h.setLevel(logging.DEBUG) + l.addHandler(h) + + def tearDownLogging(self): + '''crazy logging hacks wieder entfernen''' + self.logger.removeHandler(self.handler) + self.logger.setLevel(logging.NOTSET) + self.handler.close() + + + def testJob(self): + '''einen Job verarbeiten''' + job=self.manager.Job(None,None,"test") + self.assertEqual(job.getStatus(),("init",{})) + self.queue.put(job) + sleep(.1) + self.stop() + self.assertEqual(job.getStatus(),("started",{})) + self.assertEqual([(l['levelno'],l['msg']) for l in self.buffer if l['name']=="iro.worker"], + [(20,'Workerprocess läuft nun...'), + (20,'ein neuer Job(1)'), + (20,'Job(1) fertig ;)')]) + + def testBadJob(self): + '''einen Job verarbeiten, der fehlschlägt''' + job=self.manager.BadJob(None,None,"test") + self.assertEqual(job.getStatus(),("init",{})) + self.queue.put(job) + sleep(.1) + self.stop() + self.assertEqual(job.getStatus(),("error",{})) + self.assertEqual([(l['levelno'],l['msg']) for l in self.buffer if l['name']=="iro.worker"], + [(20,'Workerprocess läuft nun...'), + (20,'ein neuer Job(1)'), + (40,'Job(1) fehlgeschlagen :(')]) + error=Exception('Error') + self.assertEqual(self.buffer[-1]['exc_info'][0],type(error)) + self.assertEqual(str(self.buffer[-1]['exc_info'][1]),str(error)) + + def testSMSJob(self): + job=self.manager.SMSJob(self.providerlist, "test", "name", SMS("message"),[012345]) + self.assertEqual(job.getStatus(),("init",{})) + self.queue.put(job) + sleep(.1) + self.stop() + self.assertEqual(job.getStatus(),("sended",{'failed': [], 'good': []})) + self.assertEqual([(l['levelno'],l['msg']) for l in self.buffer if l['name']=="iro.worker"], + [(20,'Workerprocess läuft nun...'), + (20,'ein neuer Job(1)'), + (20,'Job(1) fertig ;)')]) + + +if __name__ == "__main__": + unittest.main() diff -r cb50c2c53d11 -r acd4c2006602 tests/testXMLRPCServer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/testXMLRPCServer.py Wed Dec 21 21:51:47 2011 +0100 @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +import unittest + +import xmlrpclib +from stopableServer import init_server + +class TestServer(unittest.TestCase): + + def setUp(self): + self.i = init_server() + self.serv=self.i.serv + + self.serv.start() + + def tearDown(self): + self.serv.stop() + + def testLogin(self): + self.assertEqual(xmlrpclib.Server("https://test:test@localhost:8000").status(), {}) + self.assertEqual(xmlrpclib.Server("https://test2:test2@localhost:8000").status(), {}) + self.assertRaises(xmlrpclib.ProtocolError, xmlrpclib.Server("https://test2:test@localhost:8000").status) + self.assertRaises(xmlrpclib.ProtocolError,xmlrpclib.Server ("https://test:test2@localhost:8000").status) + + def testsendSMS(self): + servstr="https://test:test@localhost:8000" + client=xmlrpclib.Server(servstr) + id=client.startSMS("test",["01234", ] ) + self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': 'test'}} ) + + def testTwoUser(self): + u1="https://test:test@localhost:8000" + u2="https://test2:test2@localhost:8000" + admin="https://admin:admin@localhost:8000" + client1=xmlrpclib.Server(u1) + client2=xmlrpclib.Server(u2) + admin=xmlrpclib.Server(admin) + id1=client1.startSMS("test",["01234"] ) + self.assertEqual(client2.status(),{} ) + self.assertEqual(admin.status(id1),{id1: {'status': ['init', {}], 'name': 'test'}} ) + id2=client2.startSMS("test2",["01234"] ) + self.assertNotEqual(id1, id2) + self.assertEqual(client1.status(),{id1: {'status': ['init', {}], 'name': 'test'}}) + self.assertEqual(client2.status(),{id2: {'status': ['init', {}], 'name': 'test2'}}) + self.assertEqual(admin.status(),{id1: {'status': ['init', {}], 'name': 'test'}, + id2: {'status': ['init', {}], 'name': 'test2'}} ) + + self.assertEqual(client2.status(id1), {}) + self.assertEqual(client1.status(id2), {}) + + def testGetProvider(self): + servstr="https://test:test@localhost:8000" + client=xmlrpclib.Server(servstr) + self.assertEqual(client.getProvider("sms"), ["fax.de","geonet", "sipgate", "smstrade"]) + self.assertEqual(client.getProvider("fax"), ["fax.de","geonet", "sipgate"]) + self.assertEqual(client.getProvider("mail"), ["localhost"]) + + self.assertRaises(xmlrpclib.ProtocolError,client.getProvider, "temp") + + def testGetDefault(self): + servstr="https://test:test@localhost:8000" + client=xmlrpclib.Server(servstr) + self.assertEqual(client.getDefaultProvider("sms"), "smstrade") + self.assertEqual(client.getDefaultProvider("fax"),"sipgate") + self.assertEqual(client.getDefaultProvider("mail"), "localhost") + + self.assertRaises(xmlrpclib.ProtocolError,client.getDefaultProvider, "temp") + + +if __name__ == "__main__": + unittest.main() diff -r cb50c2c53d11 -r acd4c2006602 tests/testloglock.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/testloglock.py Wed Dec 21 21:51:47 2011 +0100 @@ -0,0 +1,98 @@ + +import unittest +import os +import tempfile +import signal +import threading +import time + +class testLogLock(unittest.TestCase): + def test_ThreadingAndLocks(self): + #create a thread, have that thread grab a lock, and sleep. + fd, file = tempfile.mkstemp('.nonlog') + _lock = threading.RLock() + os.close(fd) + def locker(): + os.write(fd, 'Thread acquiring lock\n') + _lock.acquire() + os.write(fd, 'Thread acquired lock\n') + time.sleep(0.4) + os.write(fd, 'Thread releasing lock\n') + _lock.release() + os.write(fd, 'Thread released lock\n') + + #Then in the main thread, throw a signal to self + def handleSignal(sigNum, frame): + os.write(fd, 'Main Thread acquiring lock\n') + lock = False + endtime = time.time() + 1.0 + while not lock: + lock = _lock.acquire(blocking=0) + time.sleep(0.01) + if time.time() > endtime: + break + if not lock: + os.write(fd, 'Main Thread could not acquire lock\n') + return + os.write(fd, 'Main Thread acquired lock\n') + os.write(fd, 'Main Thread releasing lock\n') + _lock.release() + os.write(fd, 'Main Thread released lock\n') + + sighndlr = signal.signal(signal.SIGUSR1, handleSignal) + try: + fd = os.open(file, os.O_SYNC | os.O_WRONLY | os.O_CREAT) + thread = threading.Thread(target=locker) + thread.start() + time.sleep(0.1) + os.kill(os.getpid(), signal.SIGUSR1) + thread.join() + + #check the results + os.close(fd) + fileconts = open(file, 'r').read() + self.assertTrue('Main Thread released lock' in fileconts) + self.assertEqual(fileconts, + '''Thread acquiring lock +Thread acquired lock +Main Thread acquiring lock +Thread releasing lock +Thread released lock +Main Thread acquired lock +Main Thread releasing lock +Main Thread released lock +''') + + #Now try after acquiring the lock from the main thread + fd = os.open(file, os.O_SYNC | os.O_WRONLY | os.O_CREAT) + _lock.acquire() + thread = threading.Thread(target=locker) + thread.start() + time.sleep(0.1) + os.kill(os.getpid(), signal.SIGUSR1) + _lock.release() + thread.join() + os.close(fd) + fileconts = open(file, 'r').read() + self.assertEqual(fileconts, + '''Thread acquiring lock +Main Thread acquiring lock +Main Thread acquired lock +Main Thread releasing lock +Main Thread released lock +Thread acquired lock +Thread releasing lock +Thread released lock +''') + + finally: + signal.signal(signal.SIGUSR1, sighndlr) + try: + os.close(fd) + except OSError: + pass + try: + os.unlink(file) + except OSError: + pass +