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:

snafu hat geschrieben: 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
Da finde ich den Decorator übersichtlicher. Aber wenn gewünscht, kann ich mir was direkt für den route() decorator überlegen. Direkt im Syntax ist das wie gesagt nicht so einfach, da re.match() nun mal keine Typen-Umwandlung macht.
snafu hat geschrieben:Oder die Typen in einer Liste übergeben, ähnlich wie ctypes das bei den Argumenten macht. Ist vielleicht noch etwas besser.
Das wiederum geht nicht, da die Platzhalter keine wirklich eindeutige Ordnung haben: "/archive/(:year/:month/:day|/:month/:year)"
Es muss schon nen dict sein.
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Defnull hat geschrieben:"/archive/(:year/:month/:day|/:month/:year)"
Wie wäre es dann mit einer eigenen Syntax?

Code: Alles auswählen

"/archive/(:year/:month/:day|/:month/:year){int}"

"/move/:id{int}/:x{float}/:y{float}"
Will man nicht auf Typen prüfen, lässt man es bei dem entsprechenden Argument weg (also keine Klammern).

Keine Ahnung, inwiefern sich das im regelmäßigen Gebrauch als praktikabel erweisen würde und man muss natürlich aufpassen, dass sich Regex-Syntax und eigene Syntax nicht beißen.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Vieleicht ist es doch Zeit für Python 3.x:

Code: Alles auswählen

#!/usr/bin/env python3.1

def typecheck(f):
    def wrapper(*args):
        args = [f.__annotations__[n](a)
            if n in f.__annotations__ else a
            for a, n in zip(args, f.__code__.co_varnames)]
        return f(*args)
    return wrapper

@typecheck
def test(a: int):
    print(a)

test(42)
test("Hallo, Welt")
Stefan
lunar

Man kann sich das Routing auch von Werkzeug abschauen ... dessen Routing-Syntax erlaubt Typangaben in Regeln, ist aber trotzdem nicht komplex.

Die Validierung vom Routing zu trennen, mag zwar konzeptionell elegant sein, hat aber praktische Nachteile. Man kann beispielsweise nicht nach Typen routen. Außerdem zwingt es zu imho unnötigen Duplizierung von Informationen. Die Argumente eines Views müssen so an drei verschiedenen Stellen angegeben werden: In der Funktionssignatur, in der Route und im Validierungsdekorator.
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

lunar hat geschrieben:Man kann sich das Routing auch von Werkzeug abschauen ... dessen Routing-Syntax erlaubt Typangaben in Regeln, ist aber trotzdem nicht komplex.
Die Routing-Komponente von Werkzeug ist aber auch 2 mal so groß wie mein ganzes Framework ;)

Ich hab länger drüber nach gedacht und werde den Routing-Syntax voraussichtlich nicht um Typen erweitern. Aus folgenden Gründen:

Aus Prinzip: Routen routen. Das ist ihre Aufgabe. Was nicht zu ihren Aufgaben gehört ist das Verändern und Prüfen von Objekten. In URLs gibt es nämlich keine Objekte, sondern lediglich Strings. Für mich gehört das Validieren und Umwandeln von Parametern nicht in die Routen, sondern in dien Logik-Code. Da, wo man auch sonst die Verarbeitung von Parametern erwarten würde.

Aus technischen Gründen: Momentan basiert das komplette Routing auf re.match() und match.groupdict(). Beide sind sehr schnell und unkompliziert, unterstützen aber nativ keine Typen-Umwandlung. Ein eigener Syntax für Typen würde den Routing-Teil des Frameworks daher um nicht wenige Zeilen Code erweitern, ohne einen wirklichen Vorteil zu bringen.

Weil es überflüssig wäre: Alles, was man mit dem erweiterten Syntax erreichen könnte, ist jetzt schon mit dem validiate() dekorator möglich. Dieser ist bereits enthaltenen und löst das Problem eigentlich sehr einfach und übersichtlich. Außerdem erlaubt er sogar noch das Prüfen und Verändern komplexer Parameter durch lambda oder eigene callbacks. Lambda in den Routing-Syntax einzubauen wäre sicher nicht mehr ganz so einfach.


Aber ich mach folgende Kompromisse:
Man wird dem validator sagen können, das er, statt einen 400er zu werfen, einfach die entsprechende Route ignorieren soll. Außerdem wird der route() dekorator um as validate-Argument erweitert, wenn man zu faul ist, die dekoratoren selbst aneinander zu ketten.

Beides in der nächsten Version (voraussichtlich noch heute)
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
kbr
User
Beiträge: 1506
Registriert: Mittwoch 15. Oktober 2008, 09:27

Defnull hat geschrieben:Nach etwa 4 Tagen und unzähligen Tassen Kaffee kann ich euch endlich mein eigenes Micro Web Framework präsentieren :D
Sehr schön - ein eigenes kleines Framework schreiben macht Spaß. Kennst Du bobo schon? Jim Fulton hat es noch einmal neu aufgelegt :)
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

OK, der Validator muss warten, heute hab ich erst einmal Support für sehr einfache Templates hinzu gefügt (+150 Zeilen, langsam wird mein 'micro' Framework etwas groß...)

Einen extra-thread dazu gibts hier

Beispiel:

Code: Alles auswählen

@route('/test')
def template_test():
    items = ['Bottle is nice!', 2, 0.09]
    render('example', items=items)

Code: Alles auswählen

%header = 'Test Template'
<html>
  <head>
    <title>{{header.title()}}</title>
  </head>
  <body>
    <h1>{{header.title()}}</h1>
    <p>Items in list: {{len(items)}}</p>
    <ul>
    %for item in items:
      <li>
      %if isinstance(item, int):
        Zahl: {{item}}
      %else:
        %try:
        Other type: ({{type(item).__name__}}) {{repr(item)}}
        %except:
        Error: Item has no string representation.
        %end try-block (yes, you may add comments here)
      %end
      </li>
    %end
    </ul>
  </body>
</html>
Der Template-Syntax ist extrem simpel:
1) Ein '%' markiert eine Zeile mit Python-Code. Um die richtige Einrückung muss man sich nicht kümmern. Die wird vom Template Parser automatisch berechnet.
2) Ein '% end' beendet einen Python Block. Das ist notwendig, damit der Parser die Blöcke richtig erkennt.
3) '{{...}}' wird im Text durch den darin enthaltenen Python-Ausdruck ersetzt.

Diese Template-Engine ist zwar recht simpel, kann aber praktisch alles, was Python auch kann und ist extrem schnell dabei. Das Beispiel rendert mit der Bottle-eigenen Engine zwischen 7 und 10 mal schneller als ein vergleichbares Template mit Mako, obwohl Mako von sich selbst behauptet, 'Insanely Fast' zu sein ;)

In den nächsten paar Minuten wird trotzdem ein Wrapper für Mako-Templates dazu kommen. Bottle möchte sich ja nicht aufdrängen, sondern nur gute Alternativen bieten, falls keine externe Template-Engine zur Verfügung steht.


Edit: @kbr Auf den ersten Blick sieht mir bobo etwas zu minimal aus. Da fehlt einiges an Features, bis es (für mich) wirklich nützlich wird.
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Bottle: Micro Web Framework + Development Blog
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Planst du eigentlich auch eine kleine Datenbankanbindung für die Zukunft? :)
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Eigentlich nicht. Es gibt da sehr gute Angebote (SQLAlchemy, Elixir, ...) und jede hat seine Vor- und Nachteile. Man kann da nie das Richtige machen, da eh jeder seine eigenen Vorstellungen und Vorlieben hat. Außerdem hab ich nur noch 250 Zeilen Platz und ORMs sind extrem komplex. Das wird nix.

Ich werde aber wohl noch eine kleine Key-Value Datenbank einbauen, die man für Sessions, Multi-Process-Persistenz und Caching nutzen kann. Mit anydbm als Persistenz-Layer und Memcache (wenn vorhanden) als cache. Das ist nämlich sehr nützlich und gar nicht mal so trivial für den Anwender selbst zu basteln (trigger db.close() on request exit; create new dbs on demand, open dbs on demand, ...)
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

So, fertig. Bottle bietet nun persistente key/value Datenbanken an (siehe doku). Die Version 0.4.6 ist schon im git Repository, aber noch nicht im pypi, da ich noch auf die Antwort von SMA tum Thema Templates warte.
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
/me
User
Beiträge: 3561
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Defnull hat geschrieben:So, fertig. Bottle bietet nun persistente key/value Datenbanken an (siehe doku).
Es ist klein, schön und handlich. Achte bitte darauf, dass du nicht anfängst das Projekt zu einem Monster aufzublasen. ;-)

Gruß,
Matthias
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Ich bleibe unter 1000 Zeilen :) Und mir fällt auch gerade kein fehlendes Feature mehr ein. Nun wird nur noch optimiert ;)
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Vielleicht mache ich ja etwas falsch, aber bei mir führt `from bottle import db` zu einem ImportError. Installiert habe ich deine aktuellste Version mittels `python setup.py install`. Ich würde die Datenbankfähigkeit sehr gerne ausprobieren, da ich noch ganz neu im Bereich der Webframeworks bin und mich die Einfachheit von Bottle ziemlich überzeugt. Werden die Einträge in der Datenbank eigentlich dauerhaft in eine Datei gespeichert oder gilt das immer nur für eine Serversitzung?
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Das ist garkein Extra-Modul, das DB-Zeug. Jedenfalls gerade nicht.
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

snafu hat geschrieben:Vielleicht mache ich ja etwas falsch, aber bei mir führt `from bottle import db` zu einem ImportError. Installiert habe ich deine aktuellste Version mittels `python setup.py install`. Ich würde die Datenbankfähigkeit sehr gerne ausprobieren, da ich noch ganz neu im Bereich der Webframeworks bin und mich die Einfachheit von Bottle ziemlich überzeugt. Werden die Einträge in der Datenbank eigentlich dauerhaft in eine Datei gespeichert oder gilt das immer nur für eine Serversitzung?
Hast du Version 0.4.6? (erst heute nachmittag erschienen)
Mach mal ein: easy_install -U bottle
Dann sollte das gehen.

Code: Alles auswählen

>>> from bottle import db
>>> db
<bottle.BottleDB object at 0x95baf2c>
>>> db.newdb.key1 = "value1"
>>> db.newdb.key1
'value1'
>>> db.save()
Wobei ich grad nen Fehler gefunden habe und 0.4.7 veröffentliche, Moment bitte.

Edit: Done. Der DB-Kram ist aber noch Verbesserungswürdig. Ich lasse BottleBucket z.B. von dict erben, implementiere aber nicht alle Methoden. Das ist alles noch etwas unsauber, aber es funktioniert.
Zuletzt geändert von Defnull am Freitag 10. Juli 2009, 10:32, insgesamt 1-mal geändert.
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Über easy_install hatte ich noch 0.4.3 drauf, jetzt - laut der Meldung - 0.4.6. Import geht auf jeden Fall, Details probiere ich später aus. :)
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Es gibt noch genau ein Feature, das ich früher oder später noch umsetzen möchte: Umgekehrte routen (Generierung von URLs aus parametrisierten benannten Routen und passenden Parametern).

Ansonsten ist Bottle (was die Features an geht) fertig!

In den folgenden Tagen werde ich noch ein paar TestCases schreiben und auf Käferjagd gehen. Dann wird nur noch optimiert und 2to3 kompatibel gemacht. Eventuell lasse ich mich davon überzeugen, noch ein paar zusätzliche WSGI-Gateways oder Template Engines mit Adaptern zu versehen, aber eigentlich ist alles wichtige drin.

Damit wandle ich diesen Thread nun offiziell in einen Bottle Support und Feedback Thread um :)

Zum Abschluss noch ein paar Links:

GIT Repository
Dokumentation und WIKI
PyPi Eintrag
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
cryzed
User
Beiträge: 82
Registriert: Samstag 28. März 2009, 15:53

Hallo Defnull. Ich bin relativ neu und unerfahren was Webframeworks und Web-Programming mit Python angeht. Ehrlich gesagt mit Server-Geschichten allgemein.

Ich habe mir neulich einen vServer gemietet und dort MySQL sowie Apache2 richtig eingerichtet und konfiguriert und hoste jetzt unter cryzed.de meinen Wordpress Blog. Mit einem Virtualhost in der Apache2 config habe ich mir außerdem eine subdomain nach /var/www/minecraft/ geschaltet, eine kleine Wiki für ein Indie Spiel welches ich Spiele und auch einen Server dafür betreibe.

Gibt es eine Möglichtkeit Bottle, oder viel eher Apache2 zu sagen das wenn ich die subdomain minecraft.cryzed.de aufrufe er automatisch den port 8080 (oder einen anders definierten) Port ansprechen soll, so das er auf dem eigenen Webserver von Bottle landet? Ich wollte nämlich nicht gleich den Apache2 und somit auch den MySQL Server aufgeben - ergo meinen Block nur weil ich ein kleines Projekt mit Bottle schreiben/austesten wollte. Ich wäre sehr froh über eine Antwort.

PS: Es wäre klasse falls das mit den Virtualhosts geht wenn mir jemand eine leicht angepasste default-config hier posten könnte, wie gesagt ich bin leider noch ziemlich neu auf den Gebiet habe aber ehrlich gesagt keine Lust mich jetzt 1-2 Stunden mit Apache2 zu beschäftigen bevor ich meine ersten Schritte mit Bottle machen kann, obwohl das wahrscheinlich die intelligentere Lösung wäre - aber wie man sieht gehts auch (fast) ohne lesen - sonst ständ mein Blog nicht :P.
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Ich nutze selbst lighttpd und kein Apache, aber Onkel Google spuckt mir folgendes aus:

Code: Alles auswählen

<VirtualHost *>
    ServerName python.example.com

    ProxyPass / http://localhost:8080/ retry=5
    ProxyPassReverse / http://localhost:8080/
    ProxyPreserveHost On
    <Proxy *>
        Order deny,allow
        Allow from all
    </Proxy>
</VirtualHost>
Bottle: Micro Web Framework + Development Blog
Antworten