
Bottle: Micro Web Framework
- Defnull
- User
- Beiträge: 778
- Registriert: Donnerstag 18. Juni 2009, 22:09
- Wohnort: Göttingen
- Kontaktdaten:
@frabron und snafu: Danke, die Dokumentation wurde seit einigen releases etwas vernachlässigt
Da muss ich mich noch mal ein Wochenende dran setzen.

Bottle: Micro Web Framework + Development Blog
- Defnull
- User
- Beiträge: 778
- Registriert: Donnerstag 18. Juni 2009, 22:09
- Wohnort: Göttingen
- Kontaktdaten:
Ich bin grade dabei, die Template Namensräume zu überarbeiten. Ich plane folgende Möglichkeiten mit jeweils ein paar Alternativen. Bitte schaut mal drüber und überlegt, ob was fehlt oder was unnötig ist.
Setze Defaults für alle Templates
Nützlich für Helper-Funktionen, die in allen Template vorhanden sein sollen.
Setze Defaults für ein bestimmtes Template
Nützlich für Dinge wie 'page_title' oder so.
Setze Defaults für den aktuellen Request Circle
Wird zu Beginn jedes Requests geleert.
oder
Setze Werte für eine einziges Template rendering
Wird direkt nach dem rendern verworfen. Das nutzen wohl die meisten.
oder
oder
Setze Defaults für alle Templates
Nützlich für Helper-Funktionen, die in allen Template vorhanden sein sollen.
Code: Alles auswählen
BaseTemplate.globals['key'] = value
Nützlich für Dinge wie 'page_title' oder so.
Code: Alles auswählen
tpl = SimpleTemplate(filename='bla.tpl')
tpl.default(key=value)
Wird zu Beginn jedes Requests geleert.
Code: Alles auswählen
tpl = SimpleTemplate(filename='bla.tpl')
tpl.key = value
Code: Alles auswählen
from bottle import tplns
tplns.key = value
Wird direkt nach dem rendern verworfen. Das nutzen wohl die meisten.
Code: Alles auswählen
tpl = SimpleTemplate(filename='bla.tpl')
tpl.render(key=value)
Code: Alles auswählen
template(key=value)
Code: Alles auswählen
tpl = SimpleTemplate(filename='bla.tpl')
tpl.render(key=value)
Bottle: Micro Web Framework + Development Blog
Hi,
kann ich mal kurz eine Frage dazwischen werfen? Ich bin zu blöd für RegEx Regeln wie mir scheint
Ich hab bisher meine Apps mit Pylons ausgeliefert. Da gabs so ein nettes Feature, dass alle Inhalte im Verzeichnis "public" als statischer Inhalt ausgeliefert wurden, ohne dass man extra Routen etc. definieren musste. Das Verhalten versuche ich nun schon den halben Nachmittag mit Bottle nachzustellen. In der Doku hab ich das mit den dynamischen Routen und der send_file Sache gelesen, und war mir eigentlich sicher, dass eine Kombination aus beiden Funktionen mir es ermöglichen o.g. Feature nachzubauen. Aber denkste ...
Scheinbar bin ich nicht in der Lage, eine Regex zu definieren, die mir einfach den variablen Teil der URL nach dem "static" Teil in eine Variable übergibt.
Also sowas:
egal, ob ich Sternchen, diese Rauten, oder was auch sonst für eine Kombo ausprobiert hatte, immer gab's nur 404er. Ich hab auch schon versucht, mir den filename Teil ausgeben zu lassen, aber die URL matcht nie ...
Bevor ich hier noch wirrer schreibe, kann mir bitte jemand einen Schubs in die richtige Richtung weisen?
Danke
Frank
kann ich mal kurz eine Frage dazwischen werfen? Ich bin zu blöd für RegEx Regeln wie mir scheint

Ich hab bisher meine Apps mit Pylons ausgeliefert. Da gabs so ein nettes Feature, dass alle Inhalte im Verzeichnis "public" als statischer Inhalt ausgeliefert wurden, ohne dass man extra Routen etc. definieren musste. Das Verhalten versuche ich nun schon den halben Nachmittag mit Bottle nachzustellen. In der Doku hab ich das mit den dynamischen Routen und der send_file Sache gelesen, und war mir eigentlich sicher, dass eine Kombination aus beiden Funktionen mir es ermöglichen o.g. Feature nachzubauen. Aber denkste ...
Scheinbar bin ich nicht in der Lage, eine Regex zu definieren, die mir einfach den variablen Teil der URL nach dem "static" Teil in eine Variable übergibt.
Also sowas:
Code: Alles auswählen
@bottle.route('/static/(?P<filename>[a-zA-Z0-9/])')
def static(filename):
"""
Route für statische Dateien wie CSS, Bilder, HTML etc.
"""
bottle.send_file(filename, root=os.path.join(app_path,'static'))
Bevor ich hier noch wirrer schreibe, kann mir bitte jemand einen Schubs in die richtige Richtung weisen?
Danke
Frank
-
- User
- Beiträge: 5
- Registriert: Dienstag 13. Februar 2007, 17:05
ah, ok da muss man erstmal drauf kommen - klar ein Fall für die DokuDefnull hat geschrieben:Cookies, die ohne 'path' Angabe gesetzt werden, gelten nur für das aktuelle und die darunter liegenden Verzeichnisse.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.
Code: Alles auswählen
bottle.response.COOKIES['username'] = 'defnull' bottle.response.COOKIES['username']['path'] = '/' # oder besser bottle.response.set_cookie('username', 'defnull', path='/')

@frabron:
Ich bin nicht so gut in regulären Ausdrücken, aber wenn ich Folgendes benutze:
...dann gibt http://127.0.0.1:8080/static/test mir wie gewünscht `test` zurück.
Mit `:filename` sagst du halt, wo es etwas hinspeichern soll und `.*` matcht glaube ich auf beliebige Zeichen, dh. es wird alles gespeichert, was hinter `/static/` steht. Die Rauten zeigen Anfang und Ende des Ausdrucks an. Es ist also nur das `.*` Regex-Syntax, der Rest ist Bottle-Syntax.
Ich bin nicht so gut in regulären Ausdrücken, aber wenn ich Folgendes benutze:
Code: Alles auswählen
@bottle.route('/static/:filename#.*#')
def static(filename):
return filename
Mit `:filename` sagst du halt, wo es etwas hinspeichern soll und `.*` matcht glaube ich auf beliebige Zeichen, dh. es wird alles gespeichert, was hinter `/static/` steht. Die Rauten zeigen Anfang und Ende des Ausdrucks an. Es ist also nur das `.*` Regex-Syntax, der Rest ist Bottle-Syntax.
- Defnull
- User
- Beiträge: 778
- Registriert: Donnerstag 18. Juni 2009, 22:09
- Wohnort: Göttingen
- Kontaktdaten:
Probier mal eins der folgenden:frabron hat geschrieben:Code: Alles auswählen
@bottle.route('/static/(?P<filename>[a-zA-Z0-9/])')
Code: Alles auswählen
@bottle.route('/static/(?P<filename>[a-zA-Z0-9/]+)')
@bottle.route('/static/:filename:[a-zA-Z0-9/]+:')
@bottle.route('/static/:filename:.+:')
Bottle: Micro Web Framework + Development Blog
Ah, danke für die Hinweise, werde ich gleich mal testen.
Diese
Formatierung ist mir im Quellcode wohl auch schon aufgefallen, aber aus altem DOS Reflex sah mir das aus wie ein *.html Pattern aus, also was auf Dateinamen mit Punkt zutrifft. Hätte ich das mal besser probiert ...

Muchas Gracias
Achso, jetzt hab ich ganz vergessen, auf die Templatefragen einzugehen. Mir persönlich gefällt es besser, wenn man die Eigenschaften eines Objektes klassisch setzt, also eher so ...
Generell weiss ich aber nicht, ob ich Defaults (global oder für ein bestimmtes) bei Templates für so nützlich halte. Irgendwie ändert sich doch immer alles, und die paar Dinge, die mir spontan einfallen, die immer gleich bleiben, kann man auch auf die Standardart erledigen - da weiss ich nicht, ob die Defaultwerte eine Arbeitserleichterung sind. Bin da eher für KISS, nichts für ungut ...
Diese
Code: Alles auswählen
@bottle.route('/static/:filename#.*#')


Muchas Gracias
Achso, jetzt hab ich ganz vergessen, auf die Templatefragen einzugehen. Mir persönlich gefällt es besser, wenn man die Eigenschaften eines Objektes klassisch setzt, also eher so ...
Code: Alles auswählen
tpl = SimpleTemplate(filename='bla.tpl')
tpl.key = value
tpl = SimpleTemplate(filename='bla.tpl')
tpl.render(key=value)
OK, ich habs jetzt mit der URL "/static/js/OpenLayers-2.8/OpenLayers.js" probiert, sowohl
als auch
führen zum Erfolg. Ich trau's mich ja fast nicht zu sagen, aber ich hatte zudem noch die Dateien falsch auf den Server kopiert, so dass natürlich immer ein 404 kommen musste 
Manchmal macht man es sich aber auch schon echt selber schwer
Code: Alles auswählen
@bottle.route('/static/:filename#.*#')
Code: Alles auswählen
@bottle.route('/static/(?P<filename>[a-zA-Z0-9\./\-]+)')

Manchmal macht man es sich aber auch schon echt selber schwer

- Defnull
- User
- Beiträge: 778
- Registriert: Donnerstag 18. Juni 2009, 22:09
- Wohnort: Göttingen
- Kontaktdaten:
Leider erst, nachdem WSGI 1.1 oder 2.0 veröffentlicht wird. Vorher kann es keine konsistent Lösung für dieses Problem geben, da die WSGI Spezifikation für Python3 einfach nicht mehr funktioniert.este hat geschrieben:request.POST ist wohl auch Opfer von dem bytes Problem.
Würde mich sehr freuen, wenn das behoben werden würde.
Ich empfehle jedem, bei Python2.x zu bleiben, solange das der Fall ist. Bottle läuft zwar mit Python3.x, aber Teile der stdlib machen immer noch Probleme, die ich in einem Micro-Framework nicht so einfach umgehen kann. Ich müsste cgi.FieldStorage komplett neu implementieren und dabei raten, welche Daten der Server mir schickt, da es wie gesagt keine funktionierende Spezifikation für Python3 gibt.
Bottle: Micro Web Framework + Development Blog
- Defnull
- User
- Beiträge: 778
- Registriert: Donnerstag 18. Juni 2009, 22:09
- Wohnort: Göttingen
- Kontaktdaten:
Ein kleines Update: Ich bin dabei, 0.7 vorzubereiten. Einer der neuen Features ist der neue Route Dispatcher, der sich jetzt auch ohne Bottle verwenden lässt. Den möchte ich hier mal vor stellen:
Dieser kann jetzt auch (wie häufig gewünscht) benannte Routen umkehren.
Außerdem gab es noch einen ordentlichen Performance Boost: Der neue Dispatcher braucht nur noch zwei re.match() Aufrufe pro 100 dynamische Routen und nur einen einzigen dict() lookup für beliebig viele statische Routen. Damit ist er bis zu 50x schneller als der alte Dispatcher, wenn viele dynamische Routen installiert sind.
Weitere neue Features sind:
* File Uploads mit Python 3.x
* 'return static_file()' als Alternative zu 'send_file()' (Exceptions sind teurer als return Werte)
* Signierte (sichere) Cookies und AutoPickle-Support für selbige.
* request.body Attribut.
* RFC konformer Support für HEAD requests.
* Autojson nun auch für list(dict())
* Auto encoding von unicode.
* Error handler nun auch für 500 Fehler, die durch App Exceptions erzeugt wurden.
Das Meiste ist schon implementiert. Nun fehlen nur noch ein paar Tests und eine aktuelle Dokumentation für den Release. Vielleicht hat der Eine oder Andere ja Lust, vorher noch auf Bug-Jagd zu gehen und den GitHub master/HEAD aus zu probieren.
Code: Alles auswählen
>>> r = bottle.Router()
>>> r.add('some/:path', 'handler_func')
>>> r.match('some/test')
('handler_func', {'path': 'test'})
>>> r.add('numbers/:num#[0-9]+#', '...')
>>> r.match('numbers/0815')
('...', {'num': '0815'})
>>> r.match('numbers/abc')
(None, None)
Code: Alles auswählen
>>> r.add('/:test/:name#[a-z]+#/', '...', name='testroute')
>>> r.build('testroute', test='hello', name='world')
'/hello/world/'
Weitere neue Features sind:
* File Uploads mit Python 3.x
* 'return static_file()' als Alternative zu 'send_file()' (Exceptions sind teurer als return Werte)
* Signierte (sichere) Cookies und AutoPickle-Support für selbige.
* request.body Attribut.
* RFC konformer Support für HEAD requests.
* Autojson nun auch für list(dict())
* Auto encoding von unicode.
* Error handler nun auch für 500 Fehler, die durch App Exceptions erzeugt wurden.
Das Meiste ist schon implementiert. Nun fehlen nur noch ein paar Tests und eine aktuelle Dokumentation für den Release. Vielleicht hat der Eine oder Andere ja Lust, vorher noch auf Bug-Jagd zu gehen und den GitHub master/HEAD aus zu probieren.
Bottle: Micro Web Framework + Development Blog
-
- User
- Beiträge: 996
- Registriert: Mittwoch 9. Januar 2008, 13:48
Wow. Hört sich gut an! Wie wäre es, wenn du für den Standalone-Router auch zulassen würdest, dass man statt Strings (`router.add(exp, name)`) gleich Funktionen angibt? (Also sie wie es in Bottle bisher war, nur ohne den `request`)
Gruß
Code: Alles auswählen
def callback():
pass
router.add('foo', callback)
- Defnull
- User
- Beiträge: 778
- Registriert: Donnerstag 18. Juni 2009, 22:09
- Wohnort: Göttingen
- Kontaktdaten:
Das ist auch so. Ich hab nur nen String übergeben, da ich die Beispiele kurz fassen wollte. Man kann theoretisch alles (auch objekte mit __call__()) übergeben. Es wird auch wieder die bekannten Dekoratoren (@route) als Shortcuts geben.
Bottle: Micro Web Framework + Development Blog
Habe gerade versucht einen Basic-Auth Dekorator zu erstellen:
und bekomme immer folgenden Fehler:
wobei die environ ja schon vor Aufruf der Funktion an Request gebunden wird...?
Code: Alles auswählen
def basic_auth(users):
def check_auth(func):
werkzeug_request = Request(request.environ)
auth = werkzeug_request.authorization
if not auth or not (auth.username in users and users[auth.username] == auth.password):
response.add_header('WWW-Authenticate: Basic realm="login required"')
return abort(401, "Sorry, access denied.")
else:
return func
return check_auth
@route('/admin')
@basic_auth({'user':'password'})
def admin():
return 'eingeloggt'
Code: Alles auswählen
Traceback (most recent call last):
File "filebrowser.py", line 73, in <module>
@basic_auth({'hannes':'asdasdasd'})
File "filebrowser.py", line 63, in check_auth
werkzeug_request = Request(request.environ)
AttributeError: 'Request' object has no attribute 'environ'
- Defnull
- User
- Beiträge: 778
- Registriert: Donnerstag 18. Juni 2009, 22:09
- Wohnort: Göttingen
- Kontaktdaten:
Versuch mal getattr(request, 'environ'). bottle.request ist ein persistentes Objekt, dessen Variablen nur überschrieben werden. Kann sein, das da bei Dekoratoren nen Namensraum-Problem auftritt. Bin mir aber nicht ganz sicher.
Bottle: Micro Web Framework + Development Blog
Das hatte ich auch schon versucht...
wobei ich hier nun immer 'bsd' bekomme... sobald ich aber das ganze in die Funktion selbst einbaue, funktionierts...
@route('/admin')
Verstehe nicht ganz warum.
Code: Alles auswählen
def basic_auth(users):
def check_auth(func):
if getattr(request, 'environ', None) is None:
return lambda: 'bsd'
werkzeug_request = Request(request.environ)
auth = werkzeug_request.authorization
if not auth or not (auth.username in users and users[auth.username] == auth.password):
response.add_header('WWW-Authenticate: Basic realm="login required"')
abort(401, "Sorry, access denied.")
else:
return func
return check_auth
@route('/admin')
@basic_auth({'s':'ppp'})
def admin():
return 'asd'
@route('/admin')
Code: Alles auswählen
def admin():
users = {'s':'ppp'}
werkzeug_request = Request(request.environ)
auth = werkzeug_request.authorization
if not auth or not (auth.username in users and users[auth.username] == auth.password):
response.add_header('WWW-Authenticate', 'Basic realm="login required"')
abort(401, "Sorry, access denied.")
else:
return 'asd'
- Defnull
- User
- Beiträge: 778
- Registriert: Donnerstag 18. Juni 2009, 22:09
- Wohnort: Göttingen
- Kontaktdaten:
So, jetzt bin ich auch wach und kann wieder denken 
Du hast etwa folgendes implementiert:
basic_auth ist bei dir ein Dekorator Generator und check_auth der eigentliche Dekorator. Ein Dekorator tut etwas mit einem funktionsobjekt und liefert ein neues funktionsobjekt zurück, und zwar zum Zeitpunkt der Dekoration, also der funktionsdefinition von admin(). Dein Dekorator versucht also, auf request zu zu greifen, wenn admin() definiert und dekoriert wird (was viel zu früh ist) und liefert auch nur in manchen Fällen ein Funktionsobjekt zurück. Du brauchst im Endeffekt eine Wrapper-Ebene mehr:
Ich werde allerdings Support für Basic-Auth bald sowieso in Bottle einbauen. Der Umweg über Werkzeug ist ja auch extrem umständlich.

Du hast etwa folgendes implementiert:
Code: Alles auswählen
def basic_auth(users):
def check_auth(func):
...
if ...:
...
else:
return func
return check_auth
@basic_auth(...)
def admin():
pass
Code: Alles auswählen
def basic_auth(users):
def decorator(func):
def wrapper(**kargs):
if getattr(request, 'environ', None) is None:
return lambda: 'bsd'
werkzeug_request = Request(request.environ)
auth = werkzeug_request.authorization
if not auth or not (auth.username in users and users[auth.username] == auth.password):
response.add_header('WWW-Authenticate: Basic realm="login required"')
abort(401, "Sorry, access denied.")
else:
return func(**kargs)
return wrapper
return decorator
@route('/admin')
@basic_auth({'s':'ppp'})
def admin():
return 'asd'
Bottle: Micro Web Framework + Development Blog
Hi,
ich kämpfe grad mit Unicode Strings, die ich ins Template schicke. Ich bekomme immer einen UnicodeEncodeError geworfen.
Meine Daten kommen aus der Datenbank bereits als Unicode Strings heraus. Das habe ich überprüft:
gibt in der Konsole
kommt erwähnter UnicodeEncodeError.
Ein einfaches
funktioniert tadellos. Deshalb vermute ich, dass das Problem an meiner Unkenntnis des Bottle Templatesystems liegt. Im Unicode-Howto ist ja das Problem beschrieben, nur dachte ich, dass Bottle eh Unicode verwendet und auch erwartet, deshalb verstehe ich das Problem jetzt nicht so genau, bzw. finde keine Lösung. Denn die Meldung kommt ja, wenn man bereits als Unicode vorliegende Zeichen erneut encodieren will - soweit ich das verstehe ...
Für sachdienliche Hinweise wär ich echt dankbar
ich kämpfe grad mit Unicode Strings, die ich ins Template schicke. Ich bekomme immer einen UnicodeEncodeError geworfen.
Code: Alles auswählen
Unhandled Exception: UnicodeEncodeError('ascii', u'Trois Rivi\xe8res', 10, 11, 'ordinal not in range(128)')
Code: Alles auswählen
for layer in session.query(model).all():
try:
geometry = session.scalar(layer.export_as(extension))
placemarks.append(Placemark(layer.name, layer.description, geometry))
print type(layer.name)
except GeoExportError:
bottle.abort(404)
Schmeisse ich nun meine Daten ins Template<type 'unicode'>
%for placemark in placemarks:
<Placemark>
<name>{{ placemark.name }}</name>
<description><![CDATA[ {{ placemark.description }} ]]></description>
<styleUrl>#mtco_line_style</styleUrl>
{{ placemark.geometry }}
</Placemark>
%end
Code: Alles auswählen
return bottle.template('kml_line', document_name=name,
placemarks=placemarks)
Ein einfaches
Code: Alles auswählen
return placemarks[0].name
>>> Trois Rivières
Für sachdienliche Hinweise wär ich echt dankbar

-
- Python-Forum Veteran
- Beiträge: 16025
- Registriert: Freitag 20. Juni 2003, 16:30
- Kontaktdaten:
Sagst du denn dem Template-System welches Encoding am Ende rauskommen soll? Weil wenn nicht, dann versucht es wohl ASCII zu nehmen, was natürlich scheitert.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice