Seite 14 von 30
Verfasst: Montag 2. November 2009, 13:24
von Defnull
@frabron und snafu: Danke, die Dokumentation wurde seit einigen releases etwas vernachlässigt

Da muss ich mich noch mal ein Wochenende dran setzen.
Verfasst: Montag 2. November 2009, 15:52
von Defnull
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.
Code: Alles auswählen
tpl = SimpleTemplate(filename='bla.tpl')
tpl.default(key=value)
Setze Defaults für den aktuellen Request Circle
Wird zu Beginn jedes Requests geleert.
Code: Alles auswählen
tpl = SimpleTemplate(filename='bla.tpl')
tpl.key = value
oder
Setze Werte für eine einziges Template rendering
Wird direkt nach dem rendern verworfen. Das nutzen wohl die meisten.
Code: Alles auswählen
tpl = SimpleTemplate(filename='bla.tpl')
tpl.render(key=value)
oder
oder
Code: Alles auswählen
tpl = SimpleTemplate(filename='bla.tpl')
tpl.render(key=value)
Verfasst: Montag 2. November 2009, 16:39
von frabron
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:
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'))
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
Verfasst: Montag 2. November 2009, 17:30
von enlightenment
Defnull hat geschrieben: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='/')
ah, ok da muss man erstmal drauf kommen - klar ein Fall für die Doku

Verfasst: Montag 2. November 2009, 17:57
von snafu
@frabron:
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
...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.
Verfasst: Montag 2. November 2009, 18:27
von Defnull
Probier mal eins der folgenden:
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:.+:')
Verfasst: Montag 2. November 2009, 18:43
von frabron
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 ...
Code: Alles auswählen
tpl = SimpleTemplate(filename='bla.tpl')
tpl.key = value
tpl = SimpleTemplate(filename='bla.tpl')
tpl.render(key=value)
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 ...
Verfasst: Dienstag 3. November 2009, 08:51
von frabron
OK, ich habs jetzt mit der URL "/static/js/OpenLayers-2.8/OpenLayers.js" probiert, sowohl
als auch
Code: Alles auswählen
@bottle.route('/static/(?P<filename>[a-zA-Z0-9\./\-]+)')
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

Verfasst: Dienstag 3. November 2009, 09:13
von nemomuk
Normalerweise sollte man auf dem Server die statischen Dateien auch mit dem Webserver ausliefern und nicht mit Bottle das ist wohl eher für den Development-Prozess gedacht... bei Apache zB einen Alias setzen.
request.POST mit Python 3
Verfasst: Mittwoch 4. November 2009, 14:17
von este
request.POST ist wohl auch Opfer von dem bytes Problem.
Würde mich sehr freuen, wenn das behoben werden würde.
Re: request.POST mit Python 3
Verfasst: Donnerstag 5. November 2009, 12:03
von Defnull
este hat geschrieben:request.POST ist wohl auch Opfer von dem bytes Problem.
Würde mich sehr freuen, wenn das behoben werden würde.
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.
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.
Verfasst: Freitag 27. November 2009, 21:06
von Defnull
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:
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)
Dieser kann jetzt auch (wie häufig gewünscht) benannte Routen umkehren.
Code: Alles auswählen
>>> r.add('/:test/:name#[a-z]+#/', '...', name='testroute')
>>> r.build('testroute', test='hello', name='world')
'/hello/world/'
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.
Verfasst: Sonntag 29. November 2009, 16:55
von Dauerbaustelle
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ß
Verfasst: Sonntag 29. November 2009, 22:08
von Defnull
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.
Verfasst: Mittwoch 9. Dezember 2009, 22:17
von nemomuk
Habe gerade versucht einen Basic-Auth Dekorator zu erstellen:
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'
und bekomme immer folgenden Fehler:
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'
wobei die environ ja schon vor Aufruf der Funktion an Request gebunden wird...?
Verfasst: Donnerstag 10. Dezember 2009, 00:31
von Defnull
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.
Verfasst: Donnerstag 10. Dezember 2009, 08:57
von nemomuk
Das hatte ich auch schon versucht...
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'
wobei ich hier nun immer 'bsd' bekomme... sobald ich aber das ganze in die Funktion selbst einbaue, funktionierts...
@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'
Verstehe nicht ganz warum.
Verfasst: Donnerstag 10. Dezember 2009, 09:14
von Defnull
So, jetzt bin ich auch wach und kann wieder denken
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
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:
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'
Ich werde allerdings Support für Basic-Auth bald sowieso in Bottle einbauen. Der Umweg über Werkzeug ist ja auch extrem umständlich.
Unicode im Template
Verfasst: Donnerstag 10. Dezember 2009, 16:41
von frabron
Hi,
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)')
Meine Daten kommen aus der Datenbank bereits als Unicode Strings heraus. Das habe ich überprüft:
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)
gibt in der Konsole
<type 'unicode'>
Schmeisse ich nun meine Daten ins Template
%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)
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

Verfasst: Donnerstag 10. Dezember 2009, 17:33
von Leonidas
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.