Bottle: Micro Web Framework

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Donnerstag 2. Juli 2009, 17:39

Nach etwa 4 Tagen und unzähligen Tassen Kaffee kann ich euch endlich mein eigenes Micro Web Framework präsentieren :D

bottle.py

( Der Name hat einen Sinn! Ich muss nur noch herausfinden, welchen ;) )

Ich werde es in Zukunft für meine eigenen Web-Projekte verwenden und auf diese Weise ausgiebig testen und weiter verbessern. Das nächste Update wird also nicht lange auf sich warten lassen. Das ganze steht unter einer MIT Lizenz und ist demnach ziemlich offen. Über Feedback, Betatester und Forks würde ich mich freuen ;)


Und wer zu faul ist, auf den Link da oben zu klicken, kann sich eine kleine Beispiel-Applikation auch gleich hier ansehen:

Code: Alles auswählen

from bottle import route, run, request, response, send_file, abort

@route('/')
def hello_world():
    return 'Hello World!'

@route('/hello/:name')
def hello_name(name):
    return 'Hello %s!' % name

@route('/hello', method='POST')
def hello_post():
    name = request.POST['name']
    return 'Hello %s!' % name

@route('/static/:filename#.*#')
def static_file(filename):
    send_file(filename, root='/path/to/static/files/')

@route('/counter')
def counter():
    old = request.COOKIE.get('counter',0)
    new = int(old) + 1
    response.COOKIE['counter'] = new
    return "You viewed this page %d times!" % new

@route('/private')
def private():
    if request.POST.get('password','') != 'secret':
        abort(401, 'Go away!')
    return "Welcome!"

run(host='localhost', port=80)
PS: Doppelpost zu diesem Beitrag. Sorry dafür, aber ich denke das passt besser in einen neuen thread.
stuhlbein
User
Beiträge: 89
Registriert: Freitag 9. Januar 2009, 16:08

Freitag 3. Juli 2009, 00:02

gefällt mir gut das projekt!

da ich bei dem thema WSGI ebenfalls neu bin, werd ich mir das die tage auf jedenfall mal genauer anschauen, das gebrachte beispiel sieht jedenfalls gut aus ;)
audax
User
Beiträge: 830
Registriert: Mittwoch 19. Dezember 2007, 10:38

Freitag 3. Juli 2009, 06:07

Mag es sein, dass das Ding nicht Threadsafe ist?
Kommt mir so vor, weil der Request nicht direkt den Funktionen übergeben wird und dadurich ne Race-Condition entsteht.
Benutzeravatar
snafu
User
Beiträge: 6331
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Freitag 3. Juli 2009, 07:04

Ich glaube, es wäre einfacher, wenn man das OPTIMIZER-Flag per Keyword-Argument ändern könnte anstatt es im Quelltext umstellen zu müssen.

Und außerdem sollte es bei den Errorseiten IMHO möglich sein, den vorgegebenen HTML-Code bei Bedarf ändern zu können.

Aber an sich finde ich die API schon ziemlich cool. :) Leider nur 2 Buchstaben zu viel, um als echtes Microframework gelten zu können. ;P
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Freitag 3. Juli 2009, 08:31

@Bitfish: Danke :) Bei Fragen, frag.

@audax: Die Modul-Variablen request und response sind beides Instanzen von Request bzw. Response die wiederum von threading.local erben und bei jeder Anfrage mit den gerade aktuellen Daten gefüttert werden. Das hat sich als performanter und auch angenehmer erwiesen, als die Objekte bei jeder Anfrage frisch zu erschaffen und an die Handler zu übergeben. Ist also alles Thread-Save :)
snafu hat geschrieben:Ich glaube, es wäre einfacher, wenn man das OPTIMIZER-Flag per Keyword-Argument ändern könnte anstatt es im Quelltext umstellen zu müssen.
Jup, wäre es. Ist in der aktuellen Version (0.3.3) geändert. Außerdem ist es nun per default aus geschaltet, da das umsortieren von Routen zu komischen Ergebnissen führen kann, wenn sich Routen überschneiden (/hello/world und /hello/:name) und der Benutzer nichts vom Optimierer weis.
snafu hat geschrieben: Und außerdem sollte es bei den Errorseiten IMHO möglich sein, den vorgegebenen HTML-Code bei Bedarf ändern zu können.
Du hast mit eigenen error-handlern volle Kontrolle über den output, also das, was an den Browser zurück gesendet wird. Alle Nicht-HTTP-Exceptions resultieren in einem 500er Fehler. Wenn man sein eigenes Debugging-Framework an die 500 bindet, kann man eigentlich alles machen, was man will :)
snafu hat geschrieben: Aber an sich finde ich die API schon ziemlich cool. :) Leider nur 2 Buchstaben zu viel, um als echtes Microframework gelten zu können. ;P
botl.py ;)
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
snafu
User
Beiträge: 6331
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Freitag 3. Juli 2009, 09:40

Oder BTTL. ;)
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Freitag 3. Juli 2009, 10:06

Buffered Transistor Transistor Logic? ;)

Version 0.3.4 enthält nun auch ein etwas umfassenderes Beispiel
Version 0.3.5 behebt einen Fehler im POST Daten Parser und verfügt über etwas aussagekräftigere Fehlermeldungen im DEBUG Modus.
Bottle: Micro Web Framework + Development Blog
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Samstag 4. Juli 2009, 08:42

Nicht schlecht. Wäre dein Ziel Minimalismus, würde ich die ##-Syntax bei @route weglassen. Ich sehe nicht, warum ich noch eine Alternative zu regulären Ausdrücken brauche. Den Namen muss man sowieso als Funktionsparameter in der nächste Zeile wiederholen. Nett fände ich einen Weg, bei einem Parameter gleich anzugeben, ob das z.B. ein int sein muss. Andernfalls soll das Rahmenwerk gleich ein 400 werfen. Allgemein fände ich es hilfreich, wenn Parameter von Formularen (ähnlich wie es Rails automatisch macht) vorverarbeitet werden. Den threadlokalen Request finde ich gut. Das hält die Funktionsparameterliste übersichtlich. Das Tauschen von Matchern in der Liste der URLs mit einer Wahrscheinlichkeit von 1:1000 finde ich nach wie vor komisch.

Hast du dir auch eine Anbindung an Template-Engines überlegt oder willst du eine Micro-Templating-Engine integrieren?
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Samstag 4. Juli 2009, 14:59

sma hat geschrieben:Wäre dein Ziel Minimalismus, würde ich die ##-Syntax bei @route weglassen.
Im Quelltext von Bottle ist das eine einzige Zeile und ich persönlich nutze es eigentlich recht gerne. Ich mag den "(?P<name>...)" Symtax nicht und nicht jeder kennt ihn überhaupt. Optional ist er natürlich trotzdem erlaubt.
sma hat geschrieben:Nett fände ich einen Weg, bei einem Parameter gleich anzugeben, ob das z.B. ein int sein muss. Andernfalls soll das Rahmenwerk gleich ein 400 werfen.
Das ist einfacher mit einem neuen Dekorator zu lösen, aber ne gute Idee.
sma hat geschrieben:Allgemein fände ich es hilfreich, wenn Parameter von Formularen (ähnlich wie es Rails automatisch macht) vorverarbeitet werden.
Autocast zu int oder float für alles, was wie ne Zahl aus sieht, mache ich absichtlich nicht. Das gehört in die Kategorie 'magic features' die eher nicht in so ein Framework gehören. Man muss eh prüfen, ob man vom Framework nen int oder nen string bekommt. Dann kann man auch gleich das Casting selbst machen.
sma hat geschrieben:Den threadlokalen Request finde ich gut. Das hält die Funktionsparameterliste übersichtlich.
Das war der Hauptgrund für diese Entscheidung :)
sma hat geschrieben:Das Tauschen von Matchern in der Liste der URLs mit einer Wahrscheinlichkeit von 1:1000 finde ich nach wie vor komisch.
Und per default aus geschaltet. Ich hab aber Tests gemacht: Schon bei 20 Routen macht die Position der Route in der Prüfliste einen Unterschied von bis zu 100 Requests/Sekunde (bei 400-500#/s im Durchschnitt, also 20-25%). Schließlich ist das Routing mit das rechenintensivste, das überhaupt im Framework passiert.
sma hat geschrieben:Hast du dir auch eine Anbindung an Template-Engines überlegt oder willst du eine Micro-Templating-Engine integrieren?
Ich wollte genau wie bei den WSGIServer auch für die verschiedenen Template Engines einheitliche Adapter basteln, damit man sie jederzeit nach belieben austauschen kann, ohne den App-Code zu ändern. Der erste Adapter wird wohl mako, weil ich das selbst sehr gerne verwende.
Bottle: Micro Web Framework + Development Blog
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Samstag 4. Juli 2009, 15:07

Defnull hat geschrieben:
sma hat geschrieben:Nett fände ich einen Weg, bei einem Parameter gleich anzugeben, ob das z.B. ein int sein muss. Andernfalls soll das Rahmenwerk gleich ein 400 werfen.
Das ist einfacher mit einem neuen Dekorator zu lösen
Nein, wie soll denn der Dekorator aussehen? Bedenke, dass du innerhalb einer URL mehrere verschiedene Typen haben möchtest.
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Samstag 4. Juli 2009, 17:11

Dauerbaustelle hat geschrieben:
Defnull hat geschrieben:
sma hat geschrieben:Nett fände ich einen Weg, bei einem Parameter gleich anzugeben, ob das z.B. ein int sein muss. Andernfalls soll das Rahmenwerk gleich ein 400 werfen.
Das ist einfacher mit einem neuen Dekorator zu lösen
Nein, wie soll denn der Dekorator aussehen? Bedenke, dass du innerhalb einer URL mehrere verschiedene Typen haben möchtest.
Ungetestetes Beispiel

Code: Alles auswählen

def validate(**vkargs):
    def decorator(func):
        def wrapper(**kargs):
            for key in vkargs:
                if key not in kargs:
                    abort(400, 'Missing parameter: %s' % key)
                try:
                    kargs[key] = vkargs[key](kargs[key])
                except ValueError, e:
                    abort(400, 'Wrong parameter format for: %s' % key)
            return func(**kargs)
        return wrapper
    return decorator

@route('/move/:id/:x/:y')
@validate(id=int, x=float, y=float)
def move(id, x, y):
    pass
Erklärung: Man übergibt validate() für jeden Parameter, den man testen möchte, eine Callable. Diese sollte die Eingabe wie gewünscht umwandeln und zurück geben oder ValueError werfen, wenn die Umwandlung nicht möglich ist. Alternativ kann man im Callable auch gleich HTTPError werfen oder (besser) abort() benutzen um dem User ne individuelle Fehlermeldung um die Ohren zu hauen :)

Konvertierung in alle Python Standard-Typen (int, float, ...) sind so direkt möglich. Mit eigenen Callables kann man aber noch viel mehr tun, zum Beispiel vollwertige Form-Validation. Für def-faule gibts ja auch noch lambda ;)

Code: Alles auswählen

@route('/add_list/:csv')
@validate(cvs=lambda x: map(int, x.strip().split(',')))
def add_list(cvs):
    ''' Adds a list of IDs (separated by ',') '''
    pass
Hmm ich glaub den Dekorator bau ich genau so ein ;)
Zuletzt geändert von Defnull am Samstag 4. Juli 2009, 17:24, insgesamt 1-mal geändert.
Bottle: Micro Web Framework + Development Blog
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Samstag 4. Juli 2009, 17:16

Warum ein Extra-Validation-Element, wenn man die Validation auch gleich in die URL-Parameter reinmachen könnte?
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Samstag 4. Juli 2009, 17:27

Dauerbaustelle hat geschrieben:Warum ein Extra-Validation-Element, wenn man die Validation auch gleich in die URL-Parameter reinmachen könnte?
Weil das den URL-Syntax zu kompliziert macht und mit nem Dekorator um einiges übersichtlicher ist. (Meine Meinung) Außerdem ist es (rein prinzipiell) nicht Aufgabe der Routen, die Parameter zu verändern.

Edit: Version 0.3.7 enthält den neuen decorator :)
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
snafu
User
Beiträge: 6331
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sonntag 5. Juli 2009, 06:37

Defnull hat geschrieben:
sma hat geschrieben:Wäre dein Ziel Minimalismus, würde ich die ##-Syntax bei @route weglassen.
Im Quelltext von Bottle ist das eine einzige Zeile und ich persönlich nutze es eigentlich recht gerne. Ich mag den "(?P<name>...)" Symtax nicht und nicht jeder kennt ihn überhaupt. Optional ist er natürlich trotzdem erlaubt.
Der Meinung bin ich übrigens auch. Gerade dieser Punkt ist mir sehr positiv ins Auge gesprungen. Es gibt bestimmt einige Leute (ich bin einer davon), die soetwas tausendmal lieber benutzen als Regexes.
Defnull hat geschrieben:
Dauerbaustelle hat geschrieben:Warum ein Extra-Validation-Element, wenn man die Validation auch gleich in die URL-Parameter reinmachen könnte?
Weil das den URL-Syntax zu kompliziert macht und mit nem Dekorator um einiges übersichtlicher ist.
Man könnte zusätzlich zum Dekorator noch `validate` als Keywordargument anbieten, welches ein Dictionary erwartet:

Code: Alles auswählen

@route('/move/:id/:x/:y', validate=dict(id=int, x=float, y=float))
def move(id, x, y):
    pass
EDIT: Oder die Typen in einer Liste übergeben, ähnlich wie ctypes das bei den Argumenten macht. Ist vielleicht noch etwas besser.
Zuletzt geändert von snafu am Sonntag 5. Juli 2009, 06:54, insgesamt 2-mal geändert.
nemomuk
User
Beiträge: 862
Registriert: Dienstag 6. November 2007, 21:49

Sonntag 5. Juli 2009, 06:45

das mit dem validate Dekorator gefällt mir irgendwie nicht... finde es besser und einfacher direkt in der URL-Rule.
Antworten