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:

Die SimpleTemplate Engine interpretiert so gut wie nichts. %def wird direkt als python-Keyword übernommen und definiert eine Funktion, wie man es von Python kennt. Das von dir gezeigte Beispiel sollte eigentlich so funktionieren, wie man es erwartet*. Wobei ich mit Variablen-Namen, die Buildins überschreiben ('id'), vorsichtig wäre.

*Allerdings erst ab commit 8668a763 auch über %rebase oder %include Grenzen hinweg. Im Release 0.6.3 können Funktionen nur innerhalb des selben Templates benutzt werden, in dem sie definiert wurden, da bei der Übergabe des Funktions-Objektes als Parameter bei %include oder $rebase der Kontext verloren ging. Die aktuelle GitHub Version behebt den Fehler.

Was das escapen an geht: '%' wird nur als Befehl erkannt, wenn es das erste nicht-Whitepace-Zeichen der Zeile ist. Mitten im Code kannst du '%' ohne Probleme verwenden. '{{}}' sollte eigentlich relativ selten in HTML Code vor kommen, daher hab ich dafür keine spezielle Escape-Möglichkeit ein gebaut. {{'%'}} und {{'{''{'}} funktioniert natürlich, auch wenn es nicht so schön aus sieht.

@Jija2: Das wird teil von 0.6.4, aber das kann noch ne wocher oder zwei dauern. Nimm die http://github.com/defnull/bottle/raw/master/bottle.py version.
Bottle: Micro Web Framework + Development Blog
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Ich war mal so frei für die Git Version von bottle ein PKGBUILD zu schreiben und ins AUR zu schieben. Arch User können jetzt die Git Version mit ``yaourt -S python-bottle-git`` installieren, ein python-bottle gibt es ja erfreulicherweise schon seit einiger Zeit.
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Sehr cool :) Danke!

Ich hab inzwischen die Test Code Abdeckung auf 85% hoch bekommen. Das sind immer noch nicht 100%, aber der Reloader lässt sich nun mal nur sehr schwer testen (da er neue Prozesse auf macht), genau wie die meisten ServerAdapter noch nicht abgedeckt sind. Der Kern von Bottle ist aber inzwischen fast vollständig durch Tests abgedeckt.

Neu ist auch das automatische Unicode-Encoding. Besonders für Python3 oder Template-Engines, die unicode zurück geben, erspart das Einiges an Kopfschmerzen. UTF8 ist default, aber sobald man Content-Type neu setzt und dort einen charset an gibt oder bottle.response.charset direkt editiert, wird ein anderer default angewendet.

Was gibts noch neues? Achja: HEAD Requests werden jetzt auf GET umgemünzt, wenn es keine passende HEAD-Route gibt. In jedem Fall wird die Ausgabe vom HTTP-Body unterdrückt, damit HEAD auch klappt, wenn der ServerAdapter mist baut (wie es die meisten tun). Man kann das mit default_app().clearhead=False auch wieder aus schalten (z.B. wenn man gzip-middleware benutzt, die Content-Length neu berechnen muss).
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Etwas ganz banales (Version 0.6.3):

bottle.py Zeilen 489 ff. Funktion "send_file"

os.path.abspath erzeugt auf Windowsmaschinen Pfadangaben (Zeichenkette) mit Backslashes (\). Das Anhängen eines Slashes (/) führt dazu, dass die Bedingung 'if not filename.startswith(root):' niemals False wird und die Anfrage von statischen Dateien erfolglos bleibt (Access Denied).

Ich habe das jetzt insoweit umgangen, als dass '/' einfach nicht mehr angehangen wird. Zumal dies für das Erzeugen von 'filename' mittels 'os.path.join' nicht benötigt wird.

Ausschnitt:

Code: Alles auswählen

def send_file(filename, root, guessmime = True, mimetype = None):
    """ Aborts execution and sends a static files as response. """
    root = os.path.abspath(root)
    print('[DBG] send_file, root = {0}'.format(root))
    
    filename = os.path.abspath(os.path.join(root, filename.strip('/')))
    print('[DBG] send_file, filename = {0}'.format(filename))

    # (...)
Grüße... Heiko
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Ohne das Anhängen von '/' wäre folgendes möglich:

Code: Alles auswählen

>>> root = '/save/path/'
>>> filename = '../path2/unsave'
>>> root = os.path.abspath(root)
>>> filename = os.path.abspath(os.path.join(root, filename.strip('/')))
>>> root
'/save/path'
>>> filename
'/save/path2/unsave'
>>> filename.startswith(root)
True
Ich hab es jetzt in so weit Windows kompatibel gemacht, das statt '/' os.sep angefügt wird und das strip in der nächsten Zeile auch Backslashes entfernt.
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Defnull hat geschrieben:Ohne das Anhängen von '/' wäre folgendes möglich:

(...)
Soweit habe ich noch nicht geschaut/gedacht. Mir war erstmal wichtig, dass ich überhaupt statischen Inhalt servieren konnte. :roll:

(os.sep .. wieder was gelernt)

Weiterhin viel Erfolg, tolles Projekt.

Grüße... Heiko
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Gerade ist ein neuer Release draußen. Da die meisten Änderungen Bugfixes sind, empfehle ich das Update eigentlich jedem, der momentan mit 0.6.3 arbeitet. Wie bei jedem beta-Projekt ist natürlich weiterhin Vorsicht geboten, sollte man damit was produktives realisieren wollen :)
Bottle: Micro Web Framework + Development Blog
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Hab mal das mit dem Parent-Child-Terminate-Killen implementiert. Github spinnt grade, konnte nicht forken. Hier ist der Patch http://paste.pocoo.org/show/146426/ und in dauerbaustelle/bottle-temp ist meine Version.

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

Ich hab ausgehend von deiner Idee den Reloader nochmal komplett umstrukturiert. Er funktioniert jetzt so:

Der Hauptprozess startet keinen Server, sondern erschafft einen Kindprozess (mit identischen Komandozeilen-Argumenten), der diese Aufgabe übernimmt. Dann beginnt der Hauptprozess, die Dateien der geladenen Module auf Änderungen zu kontrollieren. Sobald sich etwas ändert, wird der Kindprozess getötet und ein Neuer gestartet. Das hat gegenüber der alten Lösung den Vorteil, das keine Threads benutzt werden müssen, was regelmäßig zu Problemen geführt hat.

Beendet sich der Kindprozess selbst, tut der Hauptprozess das gleiche. Stirbt der Hauptprozess unerwartet (SIGTERM), wir der Kindprozess mitgerissen. So bleiben keine Prozess-Leichen zurück. Auch das war vorher ein Problem.

Auf Systemen, die es unterstützen, wir der Kindprozess mit SIGINT beendet, was einem KeyboardInterrupt gleich kommt. Auf allen anderen Plattformen wird SIGTERM benutzt. Das ist zwar nicht besonders nett, aber ich sehe keinen anderen Weg (ausser IPC, aber das wird zu kompliziert) den Kindprozess sauber zu beenden und dabei auch Windows zu unterstützen.

Den neuen Ansatz findet man im 'shutdown' branch.
Bottle: Micro Web Framework + Development Blog
lunar

Wieso denn unbedingt SIGINT? SIGTERM wäre eigentlich das korrekte Signal.
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

SIGINT produziert einen KeyboardInterrupt, welcher theoretisch abgefangen und verarbeitet werden kann, während SIGTERM den Programmfluss sofort unterbricht und alle finally-Blöcke und exit-Handler komplett ignoriert.

Bottle soll einfach sein. Man kann denke ich nicht erwarten, das jeder Bottle-Benutzer daran denkt, SIGTERM mit signal.signal() abzufangen, um seine File Objekte zu schließen. Da ist SIGINT die nettere Variante, finde ich. Und da SIGINT auch der normale Weg ist, einen Bottle Server zu beenden, sollte das auch zu einem sauberen Shutdown führen.
Bottle: Micro Web Framework + Development Blog
enlightenment
User
Beiträge: 5
Registriert: Dienstag 13. Februar 2007, 17:05

Hallo zusammen,

ich beschäftige mich mich schon eine Weile mit Django und Werkzeug und verfolge den Thread schon eine Weile. Nun dachte ich spiele auch ein wenig mit bottle rum - vorab: der minimalistische Ansatz gefällt mir bisher sehr gut.

Aber nun etwas konstruktive Kritik ;-)

Als Template-Engine verwende ich zur Zeit Jinja2 und ich würde gerne den decorator jinja2_view verwenden. Wenn ich nun den kompletten Pfad angebe dann funktioniert das auch (z.B. @jinja2_view("/sehr/langer/pfad/templates/index.html)).

Allerdings wollte ich die Übersichtlichkeit etwas erhöhen indem ich per bottle.TEMPLATE_PATH.append('/sehr/langer/pfad/templates/') meinen eigenen Template-Pfad hinzufügen und dann @jinja2_view("index.html") aufrufe. Zur Zeit ist das allerdings so nicht möglich. Nach Studium des Quellcodes ist mir die Hardcodierung von .tpl aufgefallen, die schon mal meine index.html aus der automatischen Ergänzung der Pfade ausschließt, da immer dateiname + ".tpl" geprüft wird. Also habe ich zum Testen die Datei mal in index.tpl umbenannt. Ein @jinja2_view("index.tpl") folgt auch zum Fehler, da nun die automatische Pfadergänzung nicht mehr greift. Also @jinja2_view("index") versucht, das klappt endlich - allerdings gibt es nun ein Problem mit dem vererbten Template. Also das {% extends "base.tpl" %} in ein {% extends "base" %} geändert und es lieft.

Insgesamt habe ich es also zum Laufen bekommen, allerdings fand ich das vorgehen etwas unituitiv. Ich finde es eigenlich logischer irgendwo einen grundlegenden Template-Pfad festzulegen und dann per Dateinamen auf die entsprechenden templates zuzugreifen. Auch das feste .tpl finde ich nicht so optimal gelöst, aber ist wohl eher Geschmackssache.

Des Weiteren wäre eine etwas ausführlichere Fehlermeldung wenn das template nicht gefunden wird wünschenswert, d.h. also welche Pfade probiert wurden um das Template zu finden...
enlightenment
User
Beiträge: 5
Registriert: Dienstag 13. Februar 2007, 17:05

Gibt es eine Möglichkeit einen Wert zwischen unterschiedlichen Requests zu übergeben? Eigentlich hatte ich gedacht, dass es mit Cookies möglich sein sollte, aber so wie ich das sehe sind die cookies nur für den selben request abrufbar oder habe ich etwas übersehen?
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

enlightenment hat geschrieben:aber so wie ich das sehe sind die cookies nur für den selben request abrufbar oder habe ich etwas übersehen?
Cookies speicherst du beim Client, d.h. die "Übergabe" des Wertes hängt davon ab, was der Client mit dem Cookie macht; im schlimmsten Fall ignoriert er es einfach und dein Wertübergeben funktioniert nicht.
Zuletzt geändert von Dauerbaustelle am Sonntag 1. November 2009, 14:04, insgesamt 1-mal geändert.
enlightenment
User
Beiträge: 5
Registriert: Dienstag 13. Februar 2007, 17:05

ja, generell ist mir der Unterschied zwischen Cookie und Session schon klar. Und mir ist auch klar, dass es generell an den Nutzereinstellungen liegt ob Cookies erlaubt sind oder nicht.

Generell will ich einfach den eingeloggten User im Cookie zwischen speichern und dann auf die Unterseiten als eingeloggter User zugreifen.

Bei Werkzeug konnte ich für die gesamte Webseite auf den Cookie zugreifen konnte, d.h. nicht nur von dem Request von dem ich das Cookie gesetzt habe, deshalb frage ich mich wie man das idealerweise bei bottle realisieren kann.
nemomuk
User
Beiträge: 862
Registriert: Dienstag 6. November 2007, 21:49

Indem du den Inhalt des Cookies ausliest, analog zu Werkzeug.
enlightenment
User
Beiträge: 5
Registriert: Dienstag 13. Februar 2007, 17:05

hm, vielleicht stehe ich ja gerade auf dem Schlauch - also hier mal ein einfaches Beispiel.

Code: Alles auswählen

@route("/")
def main():
    print request.COOKIES.get("username", "")

@route("/user/login")
@route("/user/login", method="POST")
def login():
    ...
    if request.method == "POST":
         response.COOKIES['username'] = request.POST["username"]
    else:
         print request.COOKIES.get("username", "")
    ...

Mein Problem ist nun, wenn ich mich über login eingelogged habe, dann kann ich den username zwar beim erneuten Aufrufen von login anzeigen lassen, aber beim Aufruf von main ist das cookie-dictionary leer.
Benutzeravatar
snafu
User
Beiträge: 6831
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

`bottle.redirect()` ist nicht dokumentiert. Zumindest finde ich es nicht auf http://bottle.paws.de/page/docs . Keine Support-Anfrage, sondern ein Hinweis. ;)
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

enlightenment hat geschrieben:Mein Problem ist nun, wenn ich mich über login eingelogged habe, dann kann ich den username zwar beim erneuten Aufrufen von login anzeigen lassen, aber beim Aufruf von main ist das cookie-dictionary leer.
Cookies, die ohne 'path' Angabe gesetzt werden, gelten nur für das aktuelle und die darunter liegenden Verzeichnisse.

Code: Alles auswählen

bottle.response.COOKIES['username'] = 'defnull'
bottle.response.COOKIES['username']['path'] = '/'
# oder besser
bottle.response.set_cookie('username', 'defnull', path='/')
Bottle: Micro Web Framework + Development Blog
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Hi,

ich teste auch grade deine Flasche ;)

Dabei ist mir aufgefallen, dass die Doku zu Apache mod_wsgi einen Fehler enthält. Die ursprüngliche Anweisung

Code: Alles auswählen

os.chdir(os.path.dirname(__FILE__))
wirft einen NameError ( NameError: name '__FILE__' is not defined ). Wenn ich es so

Code: Alles auswählen

os.chdir(os.path.dirname(__file__))
schreibe (also klein), dann läuft es durch.

Gruß
Frank
Antworten