bottle - the right way to use


  • bottle - the right way to use



    Vorwort:
    Python wird nicht umsonst die Sprache der "Hacker" genannt. Alleine schon mit dem übermächtigen Scapy Modul, welches Paketmanipulation auf allen Netzwerkschichten erlaubt, besteht ein Grund, einen Webserver+Anwendung in Python zu schreiben. Zudem hat Python in seiner ganzen Historie lediglich eine Hand voll Sicherheitslücken zu beklagen - hingegen PHP¹ über alle Versionen hinweg tausende... der zweite Grund auf Python zu setzen.
    Da wir uns nun entscheiden haben, Python zu verwenden, nutzen wir auch dessen Stärken - nämlich das Verwenden von Modulen. Das Rad neu zu erfinden ist nie eine gute Idee.
    Mit bottle² steht uns ein kleines aber mächtiges Mikro-Rahmenwerk zur Verfügung, welches von Marcel Hellkamp geschrieben wurde. Bottle nimmt einem im Grunde drei Aufgaben ab:
    erstens kümmert sich bottle um das Routing, zweitens hat bottle eine Template-Engine mit an Bord und drittens stellt bottle eine Vielzahl an Serverschnittstellen (APIs) zur Verfügung. Zudem ist bottle eine einzige Datei, was dies sozusagen portabel macht, d.h., man müsste es gar nicht installieren sondern nur in den Projektordner einfügen. qpython liefert bottle von Haus aus mit.
    Mit bottle kann man also in wenigen Minuten eine vollständige Webanwendung (web application-->app) schreiben.
    Diese kleine Anleitung setzt gewisse Python-, HTML/CSS- und Netzwerk-(Grund)Kenntnisse voraus.



    Installation:

    Code
    pip install bottle
    pip3 install bottle


    Bottle setzt keinerlei Abhängigkeiten voraus. Sämtliche Module sind in der Python Standardbibliothek enthalten.
    Zusätzlich für später noch das Bootstrap Rahmenwerk sich besorgen unter http://getbootstrap.com



    Einrichtung:
    Um eine kleine App zu schreiben, richtet man von Beginn an sauber eine Ordner- und Dateistruktur ein, welche wie folgt aussieht:


    Der Ordner ``my_web_app`` kann beliebig liegen; vielleicht hat man bereits einen Projektordner, wo ein Unterordner für das Projekt erstellt werden kann. Die Dateien aus dem Bootstrap fügt
    man in die jeweiligen Ordner.



    Webanwendung:
    Nachdem die Ordnerstruktur und die app.py (<--UTF-8 ohne Byte Order Mark) erstellt wurden, öffnen wir die Datei und fügen folgendes ein:
    app.py


    Mit der Konstante ``STATIC_FOLDER`` verweisen wir auf unser Ordner ``htdocs``. Somit weiss bottle, wo all unsere .css, .js, Bilddateien etc. liegen werden. Im Verlauf der
    Weiterentwicklung unserer App muss nur noch zB. ``/static/img/mein_bild.jpg`` im Template hinterlegt werden.
    Für jede Route schreiben wir eine Funktion. Im obigen Beispiel wird einfach nur ein String an den Client/Browser gesendet. Da bottle aber eine Template-Engine hat, wollen
    wird dies so nicht tun.



    Templating:
    Bottle kennt zwei Arten um Templates³ in HTML wiederzugeben (rendering). ``template`` und ``view``. Ich selber nutze die template() Klassenmethode.
    Um gleich zu Beginn sauber anzufangen, setzten wir hier das mächtige und meistverwendetste HTML/CSS Rahmenwerk der Welt ein: bootstrap.
    Dazu legen wird im Ordner ``views`` eine neue Datei namens ``main.tpl`` an. Da bottle automatisch die Templates im Ordner ``views`` sucht, muss der Ordner auch so lauten.
    Die Dateiendung lautet auf ``.tpl``. Auch das weiss bottle und macht uns das Leben bzw. Schreiben im app.py Programm leichter.
    Unser Haupttemplate sieht so aus:
    main.tpl


    Wie im obigen Code zu erkennen, gibt es hier eine untypische HTML Schreibweise, nämlich ``{{}}``. Hierbei handelt es sich nicht um HTML sondern um bottles Template Syntax. Sogenannte Platzhalter.
    Mit ``{{!base}}`` laden wir im späteren Verlauf die Templates der jeweiligen Routes dynamisch in unser Grundgerüst. Dazu passen wir sogleich unsere app.py Datei wie folgt an:
    app.py

    Code
    @app.route('/index')
    def index():
    return template('index')


    Im Order ``views`` erstellen wir eine neue Datei namens ``index.tpl``. Im obigen Code brauchen wir in den ganzen Dateinamen nicht mehr auszuschreiben.
    index.tpl

    Code
    %rebase('main', title='Hello World')
    <h3>Hello World</h3>


    Der Ordner ``views`` muss nun so aussehen:

    Code
    ├── views/
    └── main.tpl
    └── index.tpl


    Layouts können hier http://getbootstrap.com/getting-started/ geladen werden.
    Diese Anleitung basiert auf: http://getbootstrap.com/examples/theme/#


    Server:
    Bottle kommt mit einem eigenen Webserver daher, welcher sich gut eignet, ein wenig zu testen und rumfuhrzuwerken. Wenn das Projekt aber wächst, wird es Zeit, sich für einen kampferprobten Server zu entscheiden.
    Ich selber nutze cherrypy und gevent. In diesem Beispiel installieren wir cherrypy (neu ab Version 10.2.2 cheroot) und nutzen dessen Webserver. Für die Dauer der Entwicklung stellt bottle zwei nützliche Methoden zur Verfügung: ``reloader`` und ``debug``.
    Wenn wir unsere app.py bearbeiten und speichern, müssen wir den Server bzw. das Programm nicht beenden und wieder starten. Aber Achtung, nach Vollendung des Projekts verzichtet man besser darauf wie es in der Doku auch nachzulesen ist.

    Quote

    The Debug Mode is very helpful during early development, but should be switched off for public applications. Keep that in mind.


    Code
    pip install cherrypy


    in der app.py stellen wir nun auf unser neuer Webserver um:

    Code
    from bottle import Bottle, static_file, run, template, CherootServer
    ...
    run(app, server=CherootServer, host='localhost', port=8089, reloader=True, debug=True)


    Cherrypy's Webserver ist ein robuster, multithreaded Server, der auch bei uns in zahlreichen Anwendungen seit Jahren eingesetzt wird.


    GPIO:
    Im folgenden Beispiel verwende ich das ``gpiozero`` Modul. Selbstverständlich kann jede beliebige Bibliothek verwendet werden. gpiozero ist jedoch anfängerfreundlich und sehr gut dokumentiert.
    Es ist nur ein Minimalbeispiel eines PIR-Sensors. Dazu schreiben wir eine neue Route und ein neues Template namens ``gpio.tpl`` sowie eine JavaScript Datei ``gpio.js``, welche in den Ordner htdocs/js/ kommt.
    https://gpiozero.readthedocs.io/en/v1.3.1/index.html
    Edit:
    RPi.GPIO und pigpio hinzugefügt.


    app.py


    gpio.tpl

    Code
    %rebase('main', title='GPIO with gpiozero')
    <br>
    <h3>GPIO with gpiozero</h3>
    <br>
    <br>
    <form>
    <button type="submit" id="pir_status" name="pir_status" class="btn btn-lg btn-success" value="1">PIR ON</button>
    <button type="submit" id="pir_status" name="pir_status" class="btn btn-lg btn-danger" value="0">PIR OFF</button>
    </form>


    gpio.js


    RPi.GPIO Version mit Template


    Aus der jQuery Bibliothek verwenden wir die Ajax (Asynchronous JavaScript and XML) Funktion, um Daten ohne URL-Paramenterübergabe an den Server zu senden.


    Datenbank:
    wird noch folgen
    In den allermeisten Fällen reicht eine SQLite DB. Vorher sollte man sich Gedanken über einen Wechsel des Rahmenwerks machen.
    https://bottlepy.org/docs/dev/…ugin-example-sqliteplugin
    https://github.com/iurisilvio/bottle-sqlalchemy



    WebSocket:
    wird noch folgen
    Sofern man nicht im Hochfrequenzhandel tätig ist und sich keinen Schlüssel für die API der Frankfurter Börse ergaunert hat, sehe ich kein Grund für WebSockets.
    WebSocket setzen fundierte Kenntnisse in JS voraus. Für ein wsgi Rahmenwerk sind die Möglichkeiten sowieso sehr limitiert.
    Eine Seite clientseitig neu laden zu lassen reicht in den meisten Fällen völlig aus.
    Siehe folgendes Beispiel:
    http://www.forum-raspberrypi.d…s4py?pid=220287#pid220287



    Fragen, Anregungen, Kritik, Ergänzungen etc. einfach hier stellen bzw. laut Boardregeln ein separates Thema aufmachen.


    Edit
    Wichtig: Seit dem Update Cherrypy auf die Version 10.2.2 wurde der WSGI Server abgetrennt und als separates Projekt namens Cherrot-Server dem Cherrypy Paket beigefügt

    Quote

    DeprecationWarning: Warning: Use of deprecated feature or API. (Deprecated in Bottle-0.13)
    Cause: The wsgi server part of cherrypy was split into a new project called 'cheroot'.
    Fix: Use the 'cheroot' server adapter instead of cherrypy.

    .
    Bottle hat dies in der aktuellen Version 0.12.13 noch nicht implementiert nur in der aktuellen Developer Version 0.13. Diese muss manuell runtergeladen werden oder man überschreibt die aktuelle Version unter ``/usr/local/lib/python2.7/dist-packages/bottle.py``
    Wenn jemand eine ältere Version <0.9 von Cherrypy im Einsatz hat, dann muss anstelle von

    Code
    from bottle import CherootServer

    neu

    Code
    from bottle import CherryPyServer

    verwendet werden.
    https://github.com/bottlepy/bo…ob/master/bottle.py#L3246
    https://github.com/cherrypy/ch…/master/cheroot/server.py



    Quellen:
    ¹) http://www.php.net/ChangeLog-5.php
    ²) http://bottlepy.org/docs/dev/#license
    ³) https://bottlepy.org/docs/dev/…tml#simpletemplate-syntax

  • Hallo,


    `reloader=True, debug=True` nutzt man (ich auch) zwar zum Entwickeln, produktiv sollte man beides aber `False` haben.


    Der Hinweis mit dem Wechsel des Rahmenwerks ist übrigens gut. An dem Punkt, wo ich Bottle + SQLAlchemy + WTForms kombiniert hatte bin ich nämlich zu Django gewechselt, weil dessen "all in" Paket dann IMHO wesentlich komfortabler ist.


    In der Einleitung fehlt IMHO noch der Hinweis, dass Bottle nur aus einer einzelnen Datei besteht. Was durchaus ein Vorteil sein kann.


    Gruß, noisefloor


    Gruß, noisefloor


  • Anbei eine kleines lauffähiges Beispiel, wie man mit bottle dynamische Diagramme anzeigen lassen kann, ohne sich mit JS etc. rumschlagen zu müssen.

    Code
    pip install pygal


    app.py


    Wie man sauber mit Templates arbeitet, wurde im ersten Beitrag erwähnt.


    Quelle:
    http://www.pygal.org/en/latest…ion/types/line.html#basic
    http://www.pygal.org/en/latest…ion/output.html#flask-app

  • Was habe ich für ein problem bzw was muss ich angeben um das zu lösen?


    Edit:

    Edited once, last by B4unty ().

  • Hallo,


    ich benutze als WSGI-Applicationsserver gerne (das ebenfalls in Python geschriebene) Gunicorn. Damit ist man von den Serverimplementierungen von Bottle unabhängig, hat bei Bedarf mehr Konfigurationsmöglichenkeiten und bei Bedarf in Kombination mit nginx noch ein performantes Gespann.


    Der Aufruf ist simple:

    Code
    gunicorn -b '127.0.0.1:8080' todo:app


    `-b` legt die Adresse und den Port fest, auf dem der Server laufen soll, `todo` ist der Name der Datei, die den Code bzw. die `app` Instanz enthält.


    Gruß, noisefloor



  • Dann werde ich das morgen oder übermorgen mal testen



    Gesendet von iPhone mit Tapatalk

  • B4unty
    Hab es im ersten Beitrag editiert. Aktuell gibt es nur die Lösung mit der dev Version.
    Gunicorn ist ein Multiprozess-Server. Ob das auf einem Pi Sinn macht... Cherrypy hat für dein Anliegen garantiert die bessere Performance.


    Angaben ohne Gewähr
    https://blog.appdynamics.com/e…thon-wsgi-servers-part-2/


    Edit:
    Hab für eine Alarmanlage (siehe Signatur) mit Live-Stream damals sämtliche Server getestet und Gunicorn und gevent hatten eine viel miesere Performance. Mit CherryPy läuft's seit Jahren alles butterweich.

  • Hallo,


    Quote

    Gunicorn ist ein Multiprozess-Server.


    Die Anzahl der Prozesse ist bei Gunicorn konfigurierbar. Ebenso, wie die Worker-Prozesse synchron oder asynchron laufen sollen. Wie ich schon schrieb: der Vorteil ist, dass man von der Serverimplementierung von Bottle unabhängig ist. Alternativ zu Guncorn könnte man auch uWSGI oder den Twisted Server oder den Gevent WSGI-Server oder ... nehmen.


    Grundsätzlich finde ich - so gut wie ich Bottle auch finde - die internen Serveranbindungen unglücklich. Ein Teil ist ungetestet - was sowohl in der Doku also auch im Quelltext steht - und außerdem nicht wirklich konfigurierbar. Da stellt sich für mich die Frage nach dem Sinn - aber das ist wohl eher eine Frage für Marcel als für den Thread hier ;-)


    Gruß, noisefloor

  • Moin


    Hier mal ein kleines WebSocket-Beispiel mit einem DHT22 Sensor.
    Zuvor müssen Module installiert werden mit pip oder pip3:

    Code
    pip install gevent
    pip install gevent-websocket


    ws_app.py


    index.tpl


    Dies ist ein minimales lauffähiges Beispiel. Wie mit Platzhaltern bzw. mit der Template-Engine gearbeitet wird, steht im ersten Beitrag.
    Ob hier das WebSocket Protokoll Sinn macht oder nicht, kann jeder selber entscheiden. Für mich macht's null Sinn, da nur in eine Richtung kommuniziert wird ;)
    Die Mittel über wsgi mittels Websocket zu kommunizieren sind sowieso massiv limitiert. Es gibt andere Rahmenwerke (Tornado, Autobahn etc.), welche dies von Haus aus anbieten - nicht aber wsgi basierend sind.



    Quelle:
    https://github.com/jgelens/gevent-websocket


    PS:
    die kaputte Forensoftware hat mir hier wieder Leerzeichen gestohlen. 4 Leerzeichen pro Einrückung.

  • Hallo Forum. Ich bin gerade am Bottle lernen und habe da gleich eine Frage.


    Ich Bin noch nicht ganz auf den Hund gekommen. Aber ich möchte ein Eingabefeld für einen Wert. Den Wert möchte ich an eine Variable in Python3 übergeben wie geh ich da vor? Habt Ihr ein Beispiel?

    Code
    <form action="/test" method="post">Test: <input name="keywords" type="text" /><input type="submit" /></form>

    Über einen :thumbup: freue ich mich(;

  • Hi,

    bei dem Beispiel mit dem GPIO nutzt du globale Variablen.

    @app.route('/gpio', method='POST')
    def set_gpio_status():
    #global pir
    global Motion

    Ich frage mich warum die als global deklariert werden?

    Könnte man nicht außerhalb der Funktion das GPIO Setup machen und die Variable "Motion" als Parameter der Funktion set_gpio_status() mitgeben?

    So könnte doch auch außerhalb der Funktion mit dem Motion-Sensor gearbeitet werden und man bräuchte kein "global"?

    Stimmt das oder bin ich auf dem Holzweg?

  • Du bist auf dem Holzweg. Die Funktion wird aus bottle heraus aufgerufen. Und das kennt keine GPIOs oder anderen Zustand, den man da magisch anknüppern könnte. Was ginge wäre ein Objekt zu erzeugen, und dessen “bound methods” an Routen zu binden.


    Ich finde persönlich aber, dass man hier die Kirche auch mal im Dorf lassen muss. GPIOs sind per Definition eine globale Resource. Das irgendwie umständlich zu umgehen hätte bestenfalls wert, wenn man unit tests mit Mocks schreiben will. Aber auch dafür gibt es Lösungen. Und hier macht das eh keiner.

    Kein Support per PN! Von Hilfestellung sollen alle profitieren!