Einblick in web.py
Verfasst: Freitag 4. Januar 2008, 16:16
Ich habe mir eben web.py angeschaut.
Mir gefällt, wie simpel ein einfaches "Hallo Welt" ist. Mir gefällt auch, dass es hinreichend komplett mit einem eigenen Server, einer Template-Lösung einer simplen Datenbank-Abstraktion daher kommt. Eine Session-Verwaltung fehlt allerdings bereits.
Es gibt zwar (ein sogar deutschsprachiges) Tutorial, aber ansonsten ist die Dokumentation außerordentlich schlecht. Nicht nur, dass es kaum irgendwelche Informationen auf der Webseite gibt, der Quelltext ist schwer verdaulich, weil kaum kommentiert und mit äußerst kurzen Variablen oder Funktionsnamen gesegnet. Ich erwarte als Doku außerdem nicht nur das API - das könnte ich im Quelltext nachlesen - sondern die Art und Weise, wie man das Ganze einsetzen soll.
Dennoch, vielleicht als Hilfe für andere, hier mein simples Beispiel in Tutorial-Form.
Das obligatorisches "Hallo Welt" zeigt, wie URLs auf Controller abgebildet werden und wie die Antwort per PRINT erzeugt wird. Die letzte Zeile startet einen Server auf Port 8080.
Ich habe einen Wiki gebaut, der Sqlite als Datenbank nutzt. Die Datenbank "code.db" habe ich so angelegt (gibt es überhaupt einen Unterschied zwischen `string` und `text`? Egal...):
Diese Zeile verbindet web.py mit meiner Datenbank:
Meine URLs sehen so aus:
Zugriff auf das web.py-Template-System habe ich mit diesem Objekt, dem ich sage, wo sich meine Templates befinden - nämlich in einem Unterverzeichnis `templates`:
Auf "/" erfolgt ein Redirect auf die "index"-Seite:
Wikiseiten zeigt dieser Controller an:
Dazu einige Erklärungen.
Eine Seite laden macht die folgende Funktion. Hier bin ich das erste Mal ob der mangelhaften Dokumentation verzweifelt. Vielleicht gibt es einen besseren Weg, ein Objekt (web.py nutzt hier spezielle dicts) aus der Datenbank zu laden.
Mir hätte ja ein `where=("name=?", name)` als Syntax besser gefallen. Vielleicht baue ich mir ja mein eigenes sma(rt)web.py, da mache ich das dann so ;)
Meine Templates bestehen aus einem gemeinsamen Basis-Template und je einem View-spezifischen. Ich habe mir das so aus einem Beispiel abgeschaut, Vererbung muss man explizit im Controller machen. Nun, ja. "render" hat jetzt für jede Datei ein magisches Attribut, darin eine Funktion, der ich die im Template definierten Parameter übergebe. Oh, man beachte das ":" vor body, was definiert, dass dieser Wert nicht automatisch escaped werden soll - das ansonsten automatisch < und & escaped wird, finde ich sehr gut.
Analog der Controller, der die Webeite zum Editieren einer Wikiseite baut:
Ich glaube, man kann auch irgendwie Formulare generieren lassen, aber außer das es "form.py" gibt kann ich da nix sagen. Ist aber in meinem Fall auch so einfach genug. Doch es wäre schon schön, wenn Validierung, Userverwaltung, usw. auch mit nur 2 Zeilen hinzugefügt werden könnten.
So weit, so gut. Nicht so schick der - wegen des kruden DB-Zugriffs - der verbleibende Controller, der eine Seite speichert:
Man beachte, diesmal wird "POST" statt "GET" als Methode benutzt. Mit "web.input" kommt man an die Request-Daten (ieek) und in meiner Verzweiflung, zwischen einem Update und eine Save unterscheiden zu müssen, mache ich auch noch ein "get_page". Da web.py davon ausgeht, dass eine Tabelle eine "id"-Spalte mit Autoinkrement hat - meine jedoch nicht die Erwartungen von web.py erfüllt - muss ich diesen Automatismus mit "seqname=False" abschalten (wieso False, wieso nicht None?!).
Ok, aber ihr habt jetzt meinen Wiki gesehen, er funktioniert und wenn man noch dies macht, ist die Entwicklung auch nicht ganz so schmerzhaft (die Debug-Seite stammt übrigens von Django ;):
Für den Produktivbetrieb sollte man wohl das "cache=False" in "render" entfernen.
Ergo: Leichtgewichtig, vielleicht zu leicht, denn alles was jetzt schnell geht, könnte sich bei umfangreicheren Projekten rächen. Der DB-Zugriff ist IMHO die schwächste Komponente von web.py. Das Template-Rahmenwerk scheint interessant, ist aber noch nicht fertig. Die Debug-Seite könnte mehr eigene Identität vertragen und es wäre nett, wenn man - wie bei Werkzeug - eine kleine Kommandozeile hätte. Formularverarbeitung braucht Dokumentation genau wie der URL-Dispatch und was und wie das mit der Middleware geht.
Stefan
Mir gefällt, wie simpel ein einfaches "Hallo Welt" ist. Mir gefällt auch, dass es hinreichend komplett mit einem eigenen Server, einer Template-Lösung einer simplen Datenbank-Abstraktion daher kommt. Eine Session-Verwaltung fehlt allerdings bereits.
Es gibt zwar (ein sogar deutschsprachiges) Tutorial, aber ansonsten ist die Dokumentation außerordentlich schlecht. Nicht nur, dass es kaum irgendwelche Informationen auf der Webseite gibt, der Quelltext ist schwer verdaulich, weil kaum kommentiert und mit äußerst kurzen Variablen oder Funktionsnamen gesegnet. Ich erwarte als Doku außerdem nicht nur das API - das könnte ich im Quelltext nachlesen - sondern die Art und Weise, wie man das Ganze einsetzen soll.
Dennoch, vielleicht als Hilfe für andere, hier mein simples Beispiel in Tutorial-Form.
Das obligatorisches "Hallo Welt" zeigt, wie URLs auf Controller abgebildet werden und wie die Antwort per PRINT erzeugt wird. Die letzte Zeile startet einen Server auf Port 8080.
Code: Alles auswählen
import web
urls = ('/', 'index')
class index:
def GET():
print "Hallo, Welt"
web.run(urls, globals())
Code: Alles auswählen
create table pages (name string primary key, title string, content text);
insert into pages (name, title, content) values ('index', 'The Index', 'This is the index');
Code: Alles auswählen
web.config.db_parameters = dict(dbn='sqlite', db='code.db')
Code: Alles auswählen
urls = (
'/', 'index',
'/(\w+)', 'page',
'/(\w+)/edit', 'edit',
'/(\w+)/save', 'save',
)
Code: Alles auswählen
render = web.template.render('templates/', cache=False)
Code: Alles auswählen
class index:
def GET(self):
web.redirect('/index')
Code: Alles auswählen
class page:
def GET(self, name):
p = get_page(name)
if p:
print render.base(p.title, render.view(p))
else:
web.redirect('/%s/edit' % name)
Eine Seite laden macht die folgende Funktion. Hier bin ich das erste Mal ob der mangelhaften Dokumentation verzweifelt. Vielleicht gibt es einen besseren Weg, ein Objekt (web.py nutzt hier spezielle dicts) aus der Datenbank zu laden.
Code: Alles auswählen
def get_page(name):
for p in web.select('pages', where="name=$name", vars=locals(), limit=1):
return p
return None
Meine Templates bestehen aus einem gemeinsamen Basis-Template und je einem View-spezifischen. Ich habe mir das so aus einem Beispiel abgeschaut, Vererbung muss man explizit im Controller machen. Nun, ja. "render" hat jetzt für jede Datei ein magisches Attribut, darin eine Funktion, der ich die im Template definierten Parameter übergebe. Oh, man beachte das ":" vor body, was definiert, dass dieser Wert nicht automatisch escaped werden soll - das ansonsten automatisch < und & escaped wird, finde ich sehr gut.
Code: Alles auswählen
def with (title, body) "base.html"
<html>
<head>
<title>$title</title>
<style type="text/css" media="screen">
pre { margin: 1em; background: #eee; padding: .5em; }
</style>
</head>
<body>
<h1>Wiki</h1>
<div>$:body</div>
<h6>© 2007 by me</h6>
</body>
Code: Alles auswählen
$def with (page) "view.html"
<h2>$page.title</h2>
<div>$page.content</div>
<p><a href="/$page.name/edit">Edit this page</a></p>
Code: Alles auswählen
class edit:
def GET(self, name):
p = get_page(name)
if not p:
p = web.Storage(name=name, title=name, content="")
print render.base(p.title, render.edit(p))
Code: Alles auswählen
$def with (page) "edit.html"
<h2>Edit $page.title</h2>
<form method="post" action="/$page.name/save">
<input type="text" name="title" value="$page.title"/>
<br/>
<textarea cols="60" rows="20" name="content">$page.content</textarea>
<br/>
<input type="submit" value="Save"> or <a href="/$page.name">Cancel</a>
</form>
So weit, so gut. Nicht so schick der - wegen des kruden DB-Zugriffs - der verbleibende Controller, der eine Seite speichert:
Code: Alles auswählen
class save:
def POST(self, name):
i = web.input()
if get_page(name):
web.update('pages',
title=i.title, content=i.content,
where='name=$name', vars=locals())
else:
web.insert('pages',
seqname=False,
name=name, title=i.title, content=i.content)
web.redirect("/%s" % name)
Ok, aber ihr habt jetzt meinen Wiki gesehen, er funktioniert und wenn man noch dies macht, ist die Entwicklung auch nicht ganz so schmerzhaft (die Debug-Seite stammt übrigens von Django ;):
Code: Alles auswählen
web.webapi.internalerror = web.debugerror
web.run(urls, globals(), web.reloader)
Ergo: Leichtgewichtig, vielleicht zu leicht, denn alles was jetzt schnell geht, könnte sich bei umfangreicheren Projekten rächen. Der DB-Zugriff ist IMHO die schwächste Komponente von web.py. Das Template-Rahmenwerk scheint interessant, ist aber noch nicht fertig. Die Debug-Seite könnte mehr eigene Identität vertragen und es wäre nett, wenn man - wie bei Werkzeug - eine kleine Kommandozeile hätte. Formularverarbeitung braucht Dokumentation genau wie der URL-Dispatch und was und wie das mit der Middleware geht.
Stefan