py Project mit web.py

Django, Flask, Bottle, WSGI, CGI…
Antworten
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

Zwischenzeitlich läuft mein Projekt recht gut auch Danke der Hilfe in diesem Thread:
http://www.python-forum.de/viewtopic.php?t=33895

Die bisherige Version wird als Python Funktion per Konsole gestartet und gibt den jeweiligen Status der Daten auch auf der Konsole aus.
Das soll auch so bleiben. Allerdings möchte ich einen Zugang per Browser/Mobilem Gerät haben.
Mir scheint http://webpy.org/ ist ein geeignetes Tool hierfür.

Mein Problem, zwar kann ich 'web' einbinden, aber eine entsprechende class hat keinen Zugriff auf die Parameter der eigentlichen Anwendung. (Es wird / und soll keine Datenbank verwendet werden).

Welche Beschreibung ist notwendig, damit ihr mir auf die Sprünge helfen könnt?
(... und bitte keine Häme von wegen globale Parameter)
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@gNeandr: diese Url- und Klassendefinitionen von webpy sehen mir etwas verworren aus. Ich würde Dir flask oder bottle empfehlen.
Dein Problem verstehe ich nicht ganz. Wenn ein Skript hast, das Deine Methoden per Konsole zugänglich macht, mußt Du doch einfach nur diese Aufrufe über Urls verfügbar machen. Also wenn das Konsolen-Skript Zugriff hat, warum dann das Web-Interface nicht?
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

Sirius3 hat geschrieben:@gNeandr: diese Url- und Klassendefinitionen von webpy sehen mir etwas verworren aus. Ich würde Dir flask oder bottle empfehlen.
Dein Problem verstehe ich nicht ganz. Wenn ein Skript hast, das Deine Methoden per Konsole zugänglich macht, mußt Du doch einfach nur diese Aufrufe über Urls verfügbar machen. Also wenn das Konsolen-Skript Zugriff hat, warum dann das Web-Interface nicht?
Zu web.py etc .. web.py schien mir recht plausibel und einfach, wohl deshalb habe ich mir flask und bottle nicht angesehen .. werd's nachholen.

Mein Skript enthält einen Hauptthread und zwei weitere jobs, die
1\ die Daten erzeugen/auf der Konsole ausgeben (mittels Threads und event handling zur Beendigung)
2\ auf weitere Befehlsdaten (derzeit) über Konsoleeingaben mittels eines anderen Skripts warten (Listener)

Das Web-Interface soll auf die mittels 1\ erzeugten Daten zugreifen und anzeigen
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

Hier die Struktur meines Skripts mit dem derzeitigen web.py Ansatz ... der leider insofern nicht funktioniert, als ich keinen Zugriff auf die Daten jobs = [] # store scheduled tasks bekomme:

Code: Alles auswählen

#!/usr/bin/env python
 #  import part

# globals
__version__ = '0.1g'
sched = BackgroundScheduler()
jobs = []   # store scheduled tasks

def jobs_listing(exit_event, name, calling):
   try:
      while not exit_event.is_set():   # loop to keep scheduler alive
	  updateJobsListing()   #  updating 'jobs', .. 
          sleep(10)       # this runs every 10 sec

   finally:
       print (' piScheduler - Exit >{0}<'.format(name))

def job_serve(exit_event, name):
    try:
        while True:
        #     doing stuff and 
        #     waiting for condition to leave try using 'break'
           break

    except:
       pass

    finally:
        #     closing part
        exit_event.set()
        listener.close()
        sched.shutdown()
        sys.exit(0)

# for web.py
urls = (
    '/', 'index'
)

# for web.py
class index:
    def GET(self, globals()):
        x =  ("hello this is RPI on .16\n")
        x1 =  __version__ + "  " +  str(datetime.datetime.now())[:19]
        #    >>>>>> no access to changed values like 'jobs' <<<<<<
        return x + x1

def main():
    #     initializing with parameter
    sched.start()  # start the scheduler
    exit_event = Event()  # define an event to allow keyboard interrupt and other
    Thread(target=jobs_listing, args=(exit_event, 'pilight Jobs', calling)).start()
    #     use web.py to get the scheduler data on a web page 
    app = web.application(urls, globals())
    app.run()

    #    a routine to read the schedule data, update 'jobs' 
    #    and kick the RPI functions
    job_serve(exit_event, 'piScheduler')

    #    close down after exit_event was set true
    sched.shutdown()
    exit()

#---------------------------------
if __name__ == "__main__":
    main()
Mit der class index soll der aktuelle Stand von 'jobs' an die web-Seite übergeben werden. Siehe Zeile 45. Das klappt nicht.
Es werden nur Daten im Zustand vor dem Aufruf von app.run() angezeigt .. nicht was notwendig ist.

FMPOV ist es kein Problem von web.py mit app.run() sondern es liegt an der Gesamtstruktur. But here I'm struggeling :?: :!:
BlackJack

@gNeandr: Natürlich liegt das an `app.run()` das Du nur Zustände hast die davor gesetzt wurden, denn ein *danach* gibt's nicht. Der `run()`-Aufruf kehrt nicht zurück, der `job_server()`-Aufruf wird nie ausgeführt.
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

@BlackJack
Ja, so ist es .. und ein Test print Befehl zeigt es ja auch ganz klar .. klar wie Kloßbrühe :roll:

D.h hier sollte ich wohl einen zusätzlichen Thread haben und dort app.run() laufen lassen,
oder gibt's 'n andere / bessere Möglichkeit?
(Wie gesagt die web.py Eigenschaften oder deren Alternativen mal außen vor gelassen)
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

Sirius3 hat geschrieben: Dein Problem verstehe ich nicht ganz. Wenn ein Skript hast, das Deine Methoden per Konsole zugänglich macht, mußt Du doch einfach nur diese Aufrufe über Urls verfügbar machen. Also wenn das Konsolen-Skript Zugriff hat, warum dann das Web-Interface nicht?
Zurück auf null .. bzw zu deiner/dieser Frage, wahrscheinlich ist das der richtigere Weg! Zumal ich die Kommunikation mit einem zweiten Skript genauso mache. In meinem Post des Codes habe ich die Details von 'job_serve' ausgelassen. Da steht

Code: Alles auswählen

def job_serve(exit_event, name):
    address = (192.168.178.xxx, 6001)
 #    listener = Listener(address, authkey='secret password')
    listener = Listener(address')

    try:
        while True:
            connection = listener.accept()
            print (name, str(connection))
            message = connection.recv().strip()     <<<<<< error line
            #
            # process the incoming message...
            #
            print (' .... incoming msg: ', message)
    except:
       pass

    finally:
        # abgesang 
Ein Konsole Script und folgenden Aufruf benutze ich um Steuerungsparameter bzw Befehle (in 'message') zu übertragen:

Code: Alles auswählen

address = (server, port)
conn = Client(address)
conn.send(message)
Das funktioniert einwandfrei ...
Allerdings verstehe ich nicht, wie ich per URL entsprechende Befehle absetzen kann, zB. '192.168.178.xxx:6001' gibt NUR einen Memory Fehler:

Code: Alles auswählen

xSchedule <read-write Connection, handle 6>
Traceback (most recent call last):
  File "xSchedule.py", line 572, in <module>
    main()
  File "xSchedule.py", line 564, in main
    job_serve(exit_event, 'xSchedule')
  File "xSchedule.py", line 446, in job_serve
    message = connection.recv().strip()
MemoryError
Note: den authkey in der Listener Definition habe ich bewusst (erst mal) weggelassen, hat aber keinen Einfluss auf den Mem-Fehler.

Was ist zu tun, um per Web Befehl zu arbeiten?
BlackJack

@gNeandr: Vielleicht liegt's ja nur an mir oder an der vorgerückten Stunde, aber ich habe irgendwie keinen Überblick was da so gemacht wird und es sieht sehr komplex aus. Ein `MemoryError` bei einem `recv()` auf einem Socket (?) sieht komisch aus. Wobei man das eigentlich gar nicht ohne Argument aufrufen können dürfte‽
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@gNeandr: was hat connection für einen Typ? Und was ist ein Listener? Ein nacktes except das jede Exception einfach so ignoriert, ist immer schlecht. Woher hast Du dann überhaupt die Fehlermeldung?
Um Befehle zu verarbeiten verwende ich immer einen ganz normalen Web-Server, der per json kodierte Nachrichten bekommt und sendet. Da Du sowieso schon einen laufen hast, kannst Du den gleich mitbenutzen.
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

@Sirius und @BlackJack und @Leonidas
Meine "Lösung" geht zurück auf einen Beitrag, den wir im Frühjahr diskutiert haben, siehe hier:
http://www.python-forum.de/posting.php? ... 1&p=258055
und wie gesagt, das läuft zur Zufriedenheit.

Es geht jetzt darum nicht nur Befehle zu übergeben wie mit einem separaten Script ala:

Code: Alles auswählen

if len(sys.argv) == 2:
    message = sys.argv[1]
else:
    message = 'pyClient : ' + str(datetime.datetime.now())

address = (server, 6001)
conn = Client(address, authkey='secret password')
conn.send(message)
conn.close()
Im Codeteil des og. Posts ist ab Zeile 21 'def server(..)' die das skizzierte Clientscript bedient.

Ich sollte doch diesen Teil nutzen können, um auch Aufrufe über eine Web-Seite zu bedienen. Allerdings tue ich mich da schwer.

Die Memory Err Meldung würde entsprechen dem zitierten Code an Zeile 27 kommen.
BlackJack

@gNeandr: Wenn das an der Stelle einen `MemoryError` gibt versuchst Du entweder deutlich zu viele Daten zu empfangen, was aber unwahrscheinlich ist wenn man sich den Kommandozeilenclient anschaut, oder Du hast anderweitig ein Speicherleck und an der Stelle ist dann die Message einfach zu viel.

Wenn Dein Hauptprogramm sowieso auf einem Port oder einer Pipe auf Nachrichten lauscht, sollte es doch eigentlich trivial sein dem mit einer *externen* Webanwendung Nachrichten zukommen zu lassen. Ich würde dann nicht versuchen den Webserver mit in das Hauptprogramm zu basteln. Du kannst dann natürlich nicht direkt auf irgendwelche Interna vom Hauptprogramm zugreifen, aber das wäre IMHO sowieso ein wenig unsauber. Dafür könnte man Nachrichten und Antworten programmieren um diese Informationen abzufragen. Das könnte man dann auch von dem Kommandozeilenclient aus benutzen.
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

@BlackJack Ja, den bestehenden Client zu nutzen (oder einen weiteren mit gleichartigem Code), daran hatte ich auch schon gedacht. Allerdings bin ich mir immer noch unsicher (siehe Mai Beiträge) wie das aufzubauen ist. Auf der Web-Seite brauchst dann wohl sowas wie ein Javascript Teil?
Ggf. hast du hierzu Pointer zum Nachlesen .. oder Beispiele? (Immer noch Neuling :wink: )

Bzgl. eines ev. MemeoryLeaks .. wie kann ich das finden/reparieren?
BlackJack

@gNeandr: Das würde mit einer Webanwendung im Grunde genau so aussehen, nur dass die `message` dann nicht von der Kommandozeile kommt, sondern über den Browser eingegeben wird. Also so grob skizziert mittels `bottle`:

Code: Alles auswählen

@route('/send_message', method='POST')
def send_message():
    message = request.params['message']
    if not message:
        message = 'pyClient : {0}'.format(datetime.datetime.now())
    address = (server, 6001)
    conn = Client(address, authkey='secret password')
    conn.send(message)
    conn.close()

    return {}
Die URL muss man per POST-Anfrage aufrufen und dort ein Parameter `message` mitgeben — das kann zum Beispiel aus einem <form> kommen — und man bekommt als Antwort ein leeres JSON-Objekt geliefert. Das heisst das eignet sich eher für Anfragen via Javascript, statt diese URL direkt als `action`-Attribut im <form> zu hinterlegen. *Dafür* wäre es besser wenn die Funktion mit einer HTML-Seite antworten würde, denn die meisten Benutzer können mit JSON-Antworten nicht viel anfangen. ;-)

Speicherlecks kann man versuchen mit einem Profiler aufzuspüren.

Die Fehlermeldung an der Stelle kommt mir allerdings komisch vor. Die würde da zum Beispiel kommen können wenn man ein DVD-Image oder etwas vergleichbar grosses über das `Connection`-Objekt versendet.
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

@BlackJack
Danke für den web-Vorschlag, werd das mir mal später zu Gemüte führen.

Bzgl. MemoryError: Habe den RPI neu gestartet zumal ich etliche unsaubere Abbrüche hatte. Aber das hat nix gebracht (wahrscheinlich nicht überraschend :twisted: )

Profiler == valgrind
... oder was empfiehlst du?
BlackJack

@gNeandr: Nein ich meinte eher einen der Python kennt. Also zum Beispiel das `memory_profiler`-Modul aus dem Package-Index.
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

@BlackJack
Danke ... damit ist mein Abend gerettet .. ohne TV oder so :)
gNeandr
User
Beiträge: 68
Registriert: Sonntag 11. Mai 2014, 16:48

@BlackJack: Danke für den obigen Vorschlag:
... skizziert mittels `bottle`:

Code: Alles auswählen

@route('/send_message', method='POST')
def send_message():
    message = request.params['message']
    if not message:
        message = 'pyClient : {0}'.format(datetime.datetime.now())
    address = (server, 6001)
    conn = Client(address, authkey='secret password')
    conn.send(message)
    conn.close()

    return {}
Das klappt keine Frage, habe es auch mit conn.recv() erweitert und bekomme auch "einfache" Strings zurück.
Allerdings scheitere ich im Moment daran ein JSON zu übertragen.
Als Rückmeldung wandle ich JSON Daten mittels str(..) um was dann kein Problem beim Senden macht. Aber auf der Empfangsseite sollte ich wieder aus dem String JSON Daten machen, um damit vernünftig zu arbeiten.

Kannst mir da Hinwesie geben .. vllt. ist ja der Ansatz aus JSON mittels str(..) die Übertragung zu machen schon "schief" :?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Python hat ein json-Modul, das nimmt dir die ganze Arbeit ab. In diesem Fall brauchst du die loads- und die dumps-Methoden. Letztere solltest du, statt deines str-Aufrufs, zur Umwandlung in einen json-String verwenden.
Das Leben ist wie ein Tennisball.
BlackJack

Wobei ich vorher schauen würde was das Webrahmenwerk bietet. Bottle macht das zum Beispiel automatisch wenn der Rückgabewert ein `dict` ist. Dann muss man sich eventuell auch nicht um so etwas wie die HTTP-Header kümmern die sagen das es sich um JSON und nicht um HTML handelt.
Antworten