Bottle: Micro Web Framework

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

@snafu So ein Dekorator macht durchaus Sinn, allerdings sollte es Teil des Routing Systems sein. Eine Route /archive/:year/:month/:day/ soll z.B. /archive/2010/10/03 matchen aber nicht /archive/2010/15/123/ oder gar /archive/foo/bar/baz, da soll ein 404 bei rum kommen ohne dass ich mich da drum weiter kümmern muss.
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Der @validate dekorator ist in der neuen Doku schon undokumentiert und in zukünftigen Versionen wohl auch bald nicht mehr drin. Er tut nichts, was man nicht besser innerhalb der Applikation lösen könnte und so gut wie niemand nutzt ihn.

Die Idee von DasIch ist witzig, aber wohl etwas magisch.
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
snafu
User
Beiträge: 6830
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Defnull hat geschrieben:Er tut nichts, was man nicht besser innerhalb der Applikation lösen könnte
Genau das.
Defnull hat geschrieben:Die Idee von DasIch ist witzig, aber wohl etwas magisch.
Naja, Datumsparsen nimmt vielleicht nochmal einen Sonderfall ein. Wäre doch witzig, wenn beim gezeigten Beispiel direkt ein (eben mit strptime() geparstes) Datetime-Objekt an die verarbeitende Funktion übergeben würde. kA wie oft das in der Praxis gebraucht wird. Dafür sollte man sich aber besser was für innerhalb der Route-Syntax ausdenken. Ich glaube, so war das auch von DasIch gemeint.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Defnull hat geschrieben:Die Idee von DasIch ist witzig, aber wohl etwas magisch.
Man bräuchte natürlich eine andere Syntax wie sie z.B. werkzeug.routing hat.
Benutzeravatar
noisefloor
User
Beiträge: 4149
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Er tut nichts, was man nicht besser innerhalb der Applikation lösen könnte
Wohl war. Ich nutze ihn auch nicht - wenn ich gezielt was prüfen will, dann schreibe ich dafür lieber einen eigene kleine Funktion im Programm. Außerdem ist es so, dass man @validate eigentlich nur sinnvoll mit einem @error(403) nutzen kann - sonst wirft's eine Fehlermeldung, und der Nutzer ist auch nicht schlauer.
Erschwerend kommt dann noch dazu, dass @validate bestimmte Sachen konvertiert...
und so gut wie niemand nutzt ihn
Ist das so? ;-)

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

noisefloor hat geschrieben:
und so gut wie niemand nutzt ihn
Ist das so? ;-)
Ich schaue mir eigentlich jedes Projekt, jedes Code Fragment und jede Frage in den Newsgroups und Mailinglisten, die Bottle betreffen, sehr genau an und versuche zu verstehen, wie Bottle benutzt wird und was die Anwender wollen oder brauchen. @validate wird fast nie und wenn dann falsch genutzt. Außerdem sagt niemand "Bottle ist cool, schau dir den @validate dekorator an". Daher meine Vermutung, das es überflüssig ist :)

@validate() gehört zu den Features, die nie perfekt sein werden. In allen einfachen Szenarien kann man ähnliche oder bessere Lösungen ohne großen Aufwand selbst implementieren. In den meisten komplexen Szenarien ist das Feature zu eingeschränkt oder umständlich, um es sinnvoll einsetzen zu können. Es wirkt wie gewollt aber nicht gekonnt. Ähnlich wie die Datenbank-Implementierung in frühen Bottle-Versionen. So etwas hat keinen Platz in Bottle und fliegt irgendwann raus :) Dafür bin ich zu sehr Perfektionist ;)
Bottle: Micro Web Framework + Development Blog
Benutzeravatar
snafu
User
Beiträge: 6830
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ein Vorschlag, der mir noch eingefallen ist, zur Annahme/Validierung bestimmter Typen ohne den Code unnötig aufzublähen (wie es ja durch @validate leider passiert): Man könnte den Typen wie ähnlich wie beim String-Formatting integrieren:

Code: Alles auswählen

@route('/articles/%s/part%02d.html')
Passt etwas nicht, gibt es logischerweise einen Fehler. Die Argumente werden dabei in der gleichen Reihenfolge "anonym" an die Handler-Funktion übergeben, welche sich eben selbst um die Benennung kümmert. Man müsste sich natürlich überlegen, ob das beispielhaft genannte %s grundsätzlich alles annimmt und in seiner String-Form belässt oder ob seine Verwendung impliziert, dass an dieser Stelle keine Zahlen erlaubt sind. Insgesamt könnte das unter Umständen ganz schön komplizierte Ausmaße annehmen, womit sich wieder die Frage stellt, ob man das wirklich einbauen sollte. Die Sache mit dem Datetime-Objekt finde ich aber nach wie vor gut. :)
Benutzeravatar
snafu
User
Beiträge: 6830
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Andererseits: Routen könnten Bezeichner *und* Datumsteile haben, so dass ein einfaches Weitereichen an strptime() wieder schwierig wird. Die mögliche Lösung: Auslagerung des kompletten Codes, der den Routen-String parst, in eine eigene Funktion, dann `parser` als Keyword-Argument für @route, welcher standardmäßig auf `bottle.route` eingestellt ist. Möchte man also individuelles Parsen, so weist man `parser` eine eigene Funktion zu. Nehmen wir also an, man hat ein Wiki und dort die formatierten Artikel und den zugehörigen Wikitext abgelegt. Dann könnte ich mir sowas vorstellen:

Code: Alles auswählen

def wikiparse(route_string):
    date, title = bottle.parse(route_string)
    struct = strptime(date, '%Y%m%d')
    return struct.tm_year, struct.tm_mon, struct.tm_mday, title
Es wird also zunächst alles ermittelt, was der Bottle-Parser versteht und dann das eigene Parsing angewendet. Die zurückgegebenen Objekte bekommt dann wiederum die Handler-Funktion übergeben, so wie es auch normalerweise ist. Ich hab's nicht getestet, aber rein theoretisch sollte dann sowas gehen:

Code: Alles auswählen

wikiroute = partial(bottle.route, parser=wikiparse)

@wikiroute('/articles/:date/:title/'):
def article(year, month, day, title):
[...]

@wikiroute('/wikitext/:date/:title/'):
def wikitext(year, month, day, title):
[...]
Neben der Wiederverwendbarkeit hätte man damit auch den Parser-Code seperat und könnte die Funktion nur mit "internetspezifischem" Kram füllen. Das mag auf dem ersten Blick kompliziert erscheinen, aber ist andererseits IMHO sehr flexibel, erspart dir das Nachdenken über spezielle Syntax mitsamt dem Handling und bei "normalen" Routen merkt der Programmierer ja eh nichts davon.

Die Meinung von DasIch, dass ein Framework automatisch seine Routen auf Typen prüfen soll, teile ich nämlich an sich nicht. Man sieht ja, welche Probleme da bei der Implementierung entstehen können. Auch wenn es natürlich erstmal toll klingt, dass Eingaben wie Objekte behandelt werden und Teile mit Zahlen z.B. direkt als Zahlen übergeben werden. Hier könnte man - wenn's denn unbedingt sein soll - höchstens eine rudimentäre Auto-Erkennung einbauen, die meintetwegen reine "Zahl-Verzeichnisse" erkennt und umwandelt.

Naja, das sind so meine Einwürfe. Du bist ja zum Glück relativ offen, was neue Ideen angeht. Und vielleicht gibt es ja noch weitere Meinungen dazu... :)
nemomuk
User
Beiträge: 862
Registriert: Dienstag 6. November 2007, 21:49

Ich finde nicht, dass eine automatische Typumwandlung direkt in die URL-patterns gehört. Das macht die weitere Verarbeitung der Daten in einer view-Funktion sehr undurchsichtig und teilweise auch fehleranfällig, wie ich finde.

Bei snafus wikiparse frage ich mich, wo der Vorteil dabei sein soll? Das macht das ganze ziemlich undurchsichtig und kompliziert. Außerdem erspart es einem auch nicht wirklich Arbeit...

Code: Alles auswählen

@route('/articles/:date/:title/')
def article(date, title):
    struct = strptime(date, '%Y%m%d')
    [...]
Lasse mich natürlich auch gerne vom Gegenteil überzeugen, aber dieses Beispiel ist dafür irgendwie nicht sonderlich passend und konnte mir auch keine geeignete Situation dafür vorstellen.

@defnull: habe den reloader mal etwas verbessert, siehe Pull-Request
Benutzeravatar
snafu
User
Beiträge: 6830
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Na, sagen wir mal du hast ein Nachrichtenportal und ordnest dort nach Rubriken. Will ich den heutigen Artikel vom Sport lesen, erreiche ich ihn mit `/articles/sport/20100409/`, den zu Stars & Sternchen mit `/articles/vip/20100409/`, den für Computerthemen mit `/articles/it/20100409/`, usw. Hier würde man doch vermutlich in jeder Handler-Funktion den Prozess des Datumsparsens und die Zerlegung in `year`, `month`, `day` durchlaufen, oder nicht? Selbst, wenn man das in eine eigene Funktion packt, müsste diese Funktion jedes Mal am Anfang aufgerufen werden.

Wenn man es aber - wie gezeigt - mittels `functools.partial()` an einen wiederverwendbaren Namen bindet, der als Dekorator benutzt werden kann, dann hat man zwar einmal die Mehraufwand, kann sich aber bei seinen 15 Rubriken darauf verlassen, dass bei Verwendung des eigenen Dekorators, der immer wieder gebrauchte Validierungs-/Umwandlungsprozess automatisch durchgeführt wird. Ich gebe zu, die Beispiele sind etwas utopisch. Vielleicht braucht man sowas auch überhaupt nicht. Das war gestern nur so ein Gedankenblitz. Und ich betone, dass das als eine Art syntaktischer Zucker für Sonderfälle gemeint ist. Eben Fälle, bei denen man seine Routen wie beschrieben strukturieren will.
nemomuk
User
Beiträge: 862
Registriert: Dienstag 6. November 2007, 21:49

Ja, das ist alles Geschmackssache, wobei in deinem neuen Fall, die URL sowieso so besser strukturiert wäre:

Code: Alles auswählen

/articles/computer/2010/04/09/
und sich damit die Funktion wieder erübrigt.

Grundsätzlich denke ich, dass URLs nie so kompliziert sind um sie in extra Funktionen auseinandernehmen zu müssen - wenn, dann denke ich hat man da etwas falsch designt.
BlackJack

@snafu: Was meinst Du mit "jeder Handlerfunktion"? Ich hoffe doch mal es gibt nur eine Handlerfunktion für alle Kategorien und nicht für jede eine einzelne. Worin würden die sich denn Unterscheiden? Schlechtes Beispiel IMHO.
Benutzeravatar
snafu
User
Beiträge: 6830
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich denke, das war so ein Fall, wo man eine fixe Idee hat und krampfhaft (auch für sich) versucht, zu begründen, warum die Idee sinnvoll ist. Ich glaube aber, ihr habt Recht. ^^
Benutzeravatar
webwurst
User
Beiträge: 7
Registriert: Sonntag 7. Februar 2010, 02:23

Bottle ist noch nicht auf https://www.ohloh.net/ eingetragen. Fänd ich aber gut! ;)
Benutzeravatar
Defnull
User
Beiträge: 778
Registriert: Donnerstag 18. Juni 2009, 22:09
Wohnort: Göttingen
Kontaktdaten:

Ich habe den Sinn von ohloh noch nicht ganz begriffen, aber ich hab mich (und bottle) mal angemeldet. Solange ohloh keine Arbeit macht oder Leute erwarten, das ich da besonders aktiv bin, kann das auch so bleiben :)

(bottle ist $ 31,904 wert? aha O.o)
Bottle: Micro Web Framework + Development Blog
lunar

Defnull hat geschrieben:(bottle ist $ 31,904 wert? aha O.o)
Da hast Du denn Sinn von Ohloh: Du erfährst allerlei Nutzloses über Deinen Quelltext, was Du im Regelfall eh vorher bereits wusstest („mostly written in Python“) oder mit "sloccount" (was Dir btw $ 71,957 für bottle bezahlen würde) selbst hättest herausfinden können :)
uKev
User
Beiträge: 15
Registriert: Mittwoch 9. Dezember 2009, 13:43

Es gibt ja jetzt die (bisher undokumentierte) Möglichkeit auf basic auth zuzugreifen.

Benutzen würde ich das so:

Code: Alles auswählen

try:
    user, passw = request.auth
    return "user: %s, pass: %s" % (user, passw)
except TypeError:
    return "not authorized"
Entspricht das grob der Intention?

Wie kann ich jetzt einen Login-Prompt triggern?
nemomuk
User
Beiträge: 862
Registriert: Dienstag 6. November 2007, 21:49

hatte dafür mal einen Dekorator gebaut: http://github.com/ahojnnes/bottle/blob/ ... e.py#L1109
uKev
User
Beiträge: 15
Registriert: Mittwoch 9. Dezember 2009, 13:43

Ah, schön - danke, dann funktioniert mein Beispiel jetzt :)

Hier mein vollständiges Listing:

Code: Alles auswählen

import bottle
from bottle import TornadoServer, route, request, HTTPError

@route('/')
def index():
  return 'no auth required'

@route("/auth")
def auth():
  try:
    (user, password) = request.auth
    return "user: %s, pass: %s" % (user, password)
  except TypeError:
    return HTTPError(401, 'Access denied!', header={'WWW-Authenticate': 'Basic realm="%s"' % "Login erfordert"})

bottle.debug(True)
bottle.run(server=TornadoServer, reloader=True)
Wäre aber schön, wenn das ganze in irgend einer optimalen Form in Bottle integriert wird.

Nächster Schritt:

Wie loggt man sich wieder aus ;)?
Antworten