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:

@frabron und snafu: Danke, die Dokumentation wurde seit einigen releases etwas vernachlässigt :oops: Da muss ich mich noch mal ein Wochenende dran setzen.
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
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.

Code: Alles auswählen

BaseTemplate.globals['key'] = value
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

Code: Alles auswählen

from bottle import tplns
tplns.key = value
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

Code: Alles auswählen

template(key=value)
oder

Code: Alles auswählen

tpl = SimpleTemplate(filename='bla.tpl')
tpl.render(key=value)
Bottle: Micro Web Framework + Development Blog
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Hi,

kann ich mal kurz eine Frage dazwischen werfen? Ich bin zu blöd für RegEx Regeln wie mir scheint :oops:

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
enlightenment
User
Beiträge: 5
Registriert: Dienstag 13. Februar 2007, 17:05

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 ;-)
Benutzeravatar
snafu
User
Beiträge: 6832
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@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.
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

frabron hat geschrieben:

Code: Alles auswählen

@bottle.route('/static/(?P<filename>[a-zA-Z0-9/])')
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:.+:')
Bottle: Micro Web Framework + Development Blog
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Ah, danke für die Hinweise, werde ich gleich mal testen.

Diese

Code: Alles auswählen

@bottle.route('/static/:filename#.*#')
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 ... :roll: :D

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 ...
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

OK, ich habs jetzt mit der URL "/static/js/OpenLayers-2.8/OpenLayers.js" probiert, sowohl

Code: Alles auswählen

@bottle.route('/static/:filename#.*#')
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 :roll:
Manchmal macht man es sich aber auch schon echt selber schwer :D
nemomuk
User
Beiträge: 862
Registriert: Dienstag 6. November 2007, 21:49

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.
este
User
Beiträge: 1
Registriert: Mittwoch 4. November 2009, 13:55

request.POST ist wohl auch Opfer von dem bytes Problem.
Würde mich sehr freuen, wenn das behoben werden würde.
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

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.
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
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:

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.
Bottle: Micro Web Framework + Development Blog
Dauerbaustelle
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`)

Code: Alles auswählen

def callback():
  pass

router.add('foo', callback)
Gruß
Benutzeravatar
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
nemomuk
User
Beiträge: 862
Registriert: Dienstag 6. November 2007, 21:49

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...?
Benutzeravatar
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
nemomuk
User
Beiträge: 862
Registriert: Dienstag 6. November 2007, 21:49

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.
Benutzeravatar
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:

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.
Bottle: Micro Web Framework + Development Blog
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

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

Code: Alles auswählen

return placemarks[0].name
>>> Trois Rivières
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 :)
Leonidas
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
Antworten