Seite 3 von 30

Verfasst: Montag 13. Juli 2009, 22:21
von jerch
@cryzed:
Oder Du nimmst mod_wsgi:

Code: Alles auswählen

LoadModule wsgi_module /usr/lib/apache2/modules/mod_wsgi.so
...
WSGIScriptAlias /url/pfad /pfad/zu/adapter.wsgi
...
adapter.wsgi:

Code: Alles auswählen

import sys
sys.path = ['/pfad/zum/projekt'] + sys.path
from example import WSGIHandler

def application(environ, start_response):
    return WSGIHandler(environ, start_response)
example.py:

Code: Alles auswählen

from bottle import ... WSGIHandler
...
# run(...) <--- auskommentieren
Nur mal so auf die Schnelle, kann man alles noch schöner machen...

Verfasst: Dienstag 14. Juli 2009, 06:11
von cryzed
snafu hat geschrieben:Was brauchst du denn für Beispiele? Um die Templates musst du dich schon selber kümmern.
Ja, das ist ja das Problem. Ich habe keine Ahnung wie ich am besten die/das Template(s) aufteile und wie ich das Script an sich organisiere. Benutze ich für jede Route nur eine Python Datei? Oder schreibe ich alle Routen in eine Python Datei?

Benutze ich für jede Route das gleiche Template und generiere innerhalb der Route nur {{content}} welches dann zwischen generiertem Header und Footer der Seite eingefügt wird?

Wie gesagt, ich bin auf dem Gebiet noch relativ neu und weiß nicht wirklich wie ich Code/Templates organisieren soll oder wie ich sie am effektivsten schreibe.

Verfasst: Dienstag 14. Juli 2009, 08:46
von Defnull
cryzed hat geschrieben: Ja, das ist ja das Problem. Ich habe keine Ahnung wie ich am besten die/das Template(s) aufteile und wie ich das Script an sich organisiere. Benutze ich für jede Route nur eine Python Datei? Oder schreibe ich alle Routen in eine Python Datei?
Bei einer kleineren Seite steht (bis auf Templates) alles in einer Datei. Bei einem Größeren Projekt würde ich es wie folgt aufteilen:

Jeder in sich abgeschlossene Teil der Applikation bekommt eine eigene Datei (Controller) oder sogar ein eigenes Modul. Diese Teil-Applikationen registrieren ihre Routen mit @route() oder haben eine Funktion, die das mit add_route() auf einen Rutsch tut, lassen aber die Finger von run().

Eine zentrale Datei bindet all diese Module ein (und ruft eventuell deren register_routes_now() funktion auf) damit alle Routen registriert sind und startet dann den server mit run().

Dinge, die von mehreren Teil-Applikationen benutzt werden, sind wiederum in separaten Modulen.
cryzed hat geschrieben: Benutze ich für jede Route das gleiche Template und generiere innerhalb der Route nur {{content}} welches dann zwischen generiertem Header und Footer der Seite eingefügt wird?
Da die eingebaute Template-Engine keine Template-Vererbung kann, würde ich mit %include arbeiten. Der ganze HTML Header und Foother Kram kommt in separate Templates. Jede "Seite" hat nun ihr eigenes Template und bindet oben und unten die Header- und Foother-Templates ein. Wenn ein Teil-Tenmplate noch variablen braucht, geht das natürlich auch:

Code: Alles auswählen

%include header title="test Titel", include_css=['main.css','layout.css'], ...

Verfasst: Dienstag 14. Juli 2009, 09:52
von snafu
Leonidas hat geschrieben:Achja, und wer ist denn nun dieser Mirco? ;)
Och, der begleitet uns doch nun schon eine ganze Weile... Mir ist er eigentlich sogar sympathischer als dieser Bottle. ;)

Verfasst: Dienstag 14. Juli 2009, 10:57
von Defnull
Bäää *schmoll* ;)

Verfasst: Dienstag 14. Juli 2009, 11:11
von snafu
In deiner Beschreibung auf der github-Seite ist er übrigens auch.

Verfasst: Dienstag 14. Juli 2009, 11:21
von cryzed
%include header title="test Titel", include_css=['main.css','layout.css'], ...
Das macht bei mir Probleme. Ich bekomme den Fehler:
Error (500) on '/': list index out of range

Code: Alles auswählen

#!/usr/bin/env python
from bottle import route, run, request, response, send_file, abort, validate, template, db

@route('/')
def index():
    return template('index')

run(host='localhost', port=8080)
index.tpl
%include header title='Welcome!'
Welcome!
%include footer
header.tpl
<html>
<head>
<title>{{title}}</title>
</head>
<body>
footer.tpl
</body>
</html>
Irgendwelche Ideen?

Verfasst: Dienstag 14. Juli 2009, 12:25
von Defnull
Schau ich mir nach dem >>Vortrag, den ich gleich halten darf, genauer an. Mach mal bitte ein:

Code: Alles auswählen

import bottle
bottle.DEBUG=True
run(...)
um ne genauere Fehlermeldung zu bekommen.

Verfasst: Dienstag 14. Juli 2009, 12:40
von cryzed
Traceback (most recent call last):
File "/home/cryzed/Downloads/bottle/minecraft/bottle.py", line 143, in WSGIHandler
output = handler(**args)
File "minecraft.py", line 7, in index
return template('index')
File "/home/cryzed/Downloads/bottle/minecraft/bottle.py", line 670, in template
TEMPLATES[template] = template_adapter.find(template)
File "/home/cryzed/Downloads/bottle/minecraft/bottle.py", line 588, in find
return cls(filename = path % name)
File "/home/cryzed/Downloads/bottle/minecraft/bottle.py", line 579, in __init__
self.parse(template)
File "/home/cryzed/Downloads/bottle/minecraft/bottle.py", line 655, in parse
self.co = compile("".join(self.translate(template)), self.source, 'exec')
File "/home/cryzed/Downloads/bottle/minecraft/bottle.py", line 633, in translate
name = tmp[0]
IndexError: list index out of range

Verfasst: Dienstag 14. Juli 2009, 15:00
von Defnull
Bug gefunden und korrigiert. Seltsam, das es mit meinem Test-Template immer funktioniert hat O.o

Versuch mal

Code: Alles auswählen

easy_install -U bottle
für die neueste Version.

(Wird Zeit, das ich selbst mal was umfangreicheres mit bottle schreibe und meine Fehler selbst finde. Is ja voll peinlich wenn andere über meine Fehler stolpern >.<)

Verfasst: Freitag 17. Juli 2009, 16:20
von Dauerbaustelle
Warum sind die requests und responses eigentlich einmalig und global? oO

Verfasst: Freitag 17. Juli 2009, 16:39
von Defnull
Komische frage. Warum denn nicht?

Sie sind Modul-Variablen (modul != global), damit man sie überall in seinem App-Code verwenden kann, ohne sie jedesmal als Parameter durchschleifen zu müssen. Und sie sind einmalig, weil es (in einem Thread*) zu jedem Zeitpunkt nur genau einen Request und eine Response gibt.

* Request und Response erben beide von threading.local. Das bedeutet, das in jedem Thread eine separate Version von request und response vorhanden ist, die mit den jeweils aktuell relevanten Daten gefüllt werden. Das Ganze ist also Thread-Save, keine Sorge :)

Verfasst: Freitag 17. Juli 2009, 16:42
von Dauerbaustelle
Defnull hat geschrieben:* Request und Response erben beide von threading.local. Das bedeutet, das in jedem Thread eine separate Version von request und response vorhanden ist, die mit den jeweils aktuell relevanten Daten gefüllt werden. Das Ganze ist also Thread-Save, keine Sorge :)
Hm, aber wenn ich ein Modul habe und da auf modul.x irgendwas lege und das jedesmal überschreibe, dann müsste da doch immer nur ein Objekt rumliegen können?

Achja, und nochwas: Kann ich mit der URL-Funktion auch Reguläre Ausdrücke wie (?P<action>new|edit) ausdrücken?

Gruß

Verfasst: Freitag 17. Juli 2009, 17:04
von Defnull
Dieses eine Objekt, das in bottle.request liegt, wird jedes mal mit neuen Daten gefüllt (was übrigens schneller geht als das Objekt komplett neu zu erstellen). Da es von threading.local erbt, können die Daten für jeden Thread verschieden sein. So bleibt die variable Threrad-Save, obwohl sie eine Modulvariable ist. In der Variable sind gleichzeitig für jeden Thread unterschiedliche Daten.

Und ja, man kann auch direkt reguläre Ausdrücke (mit benannten Gruppen) verwenden. Im Hintergrund passiert auch nix weiter, als das ":action" in "(?P<action>[^/]+)" übersetzt wird.

Verfasst: Freitag 17. Juli 2009, 17:06
von Dauerbaustelle
Defnull hat geschrieben:Und ja, man kann auch direkt reguläre Ausdrücke (mit benannten Gruppen) verwenden. Im Hintergrund passiert auch nix weiter, als das ":action" in "(?P<action>[^/]+)" übersetzt wird.
Das Ganze wird dann aber nicht als Keyword-Argument aufgerufen, finde ich wenig doof. Ich meinte aber nicht wegen der Benennung, sondern wegen x|y.

Verfasst: Freitag 17. Juli 2009, 17:37
von Defnull
Dauerbaustelle hat geschrieben:
Defnull hat geschrieben:Und ja, man kann auch direkt reguläre Ausdrücke (mit benannten Gruppen) verwenden. Im Hintergrund passiert auch nix weiter, als das ":action" in "(?P<action>[^/]+)" übersetzt wird.
Das Ganze wird dann aber nicht als Keyword-Argument aufgerufen, finde ich wenig doof. Ich meinte aber nicht wegen der Benennung, sondern wegen x|y.
Doch, wird es. Eine Route wie "/(?P<action>new|edit)" reagiert und "/new" und "/edit" und übergibt entweder "new" oder "edit" als "action" Parameter an die Handler-Funktion.

Verfasst: Freitag 17. Juli 2009, 17:48
von Dauerbaustelle
Oh, du hast Recht. Wenn du mir jetzt noch erklärst, was es mit den lambdas in den Validatoren auf sich hat... :-)

Verfasst: Freitag 17. Juli 2009, 18:30
von Defnull
Dem valiator() dekorator kannst du funktions-objekte übergeben. Er filtert jeden URL-Parameter durch die entsprechende funktion, bevor er an den Request-Handler weiter gereicht wird. "int", "float" u.s.w. sind in Python nicht nur Typen, sondern eben auch Funktionen (z.B. "int()"), die Strings akzeptieren und einen entsprechenden Typ zurück geben.

Eine so genutzte Funktion muss also einen String als einzigen Parameter entgegen nehmen und eine umgewandelte, bereinigte und eventuell validierte Version zurück liefern. Wirft die Funktion eine Exception, schlägt auch die Validierung fehl und der User bekommt ne Fehlerseite angezeigt.

Mit Lambda kann man einfache Funktionen on-the-fly erstellen. Für komplexere Filter lohnt sich aber ein eigener "def" Block. Beispiel:

Code: Alles auswählen

def test(x):
    return int(x) + 5

@route('/:x')
@validate(x=test)
def handler(x):
    # x ist int

# Ist das gleiche wie:

@route('/:x')
@validate(x=lambda x: int(x)+5)
def handler(x):
    ...

Verfasst: Freitag 17. Juli 2009, 19:28
von Dauerbaustelle
Lambda kenne ich, danke. Du solltest vielleicht noch drauf hinweisen, dass du nur `ValueErrors` abfängst - gerade ist sowas hier möglich:

Code: Alles auswählen

In [16]: @bottle.route('/:bar/')
   ....: @bottle.validate(bar=lambda x:x[1])
   ....: def foo(bar):
   ....:     print bar

In [20]: f, a = bottle.match_url('/1/')

In [21]: f(**a)

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)

/home/jonas/<ipython console> in <module>()

/usr/local/lib/python2.6/dist-packages/bottle-0.4.13-py2.6.egg/bottle.pyc in wrapper(**kargs)
    440                     abort(403, 'Missing parameter: %s' % key)
    441                 try:
--> 442                     kargs[key] = vkargs[key](kargs[key])
    443                 except ValueError, e:
    444                     abort(403, 'Wrong parameter format for: %s' % key)

/home/jonas/<ipython console> in <lambda>(x)

IndexError: string index out of range

Verfasst: Freitag 17. Juli 2009, 21:34
von Defnull
Ich darf prinzipiell nur ValueError abfangen, da ich bei allen anderen Fehlern nicht zwischen "URL enthält mist" und "Validator-Code tut mist" unterscheiden kann. Selbst ValueError ist da nicht 100%ig klar. Wenn mann es ganz genau nimmt sollte deine Validator-Funktion den IndexError abfangen und ValueError werfen, wenn die Funktion falsch formatierte Daten bekommt. Das ist in lambda natürlich blöd, aber alles andere wäre äußerst unsauber. Einfacher wäre es natürlich, bereits in der Route sicher zu stellen, das der Parameter mindestens zwei Zeichen lang ist.

Wenn dir der Unterschied zwischen 403 und 500 egal ist (den meisten Usern sagt das sowieso nix) kannst du auch drauf verzichten und warten, bis etwas später (im WSGIHandler) eh jede beliebige Exception abgefangen und in ne schöne 500er Fehlerseite umgewandelt wird.

Eigentlich will ich sagen: Das der IndexError in dein Beispiel durch rasselt und später nen 500er wirft, ist gewollt. Schließlich ist hier ein Fehler in der Validator-Funktion. Das ValueErrors abgefangen und als Indikator für "In der URL steht Mist" genutzt werden können, wäre allerdings einen Eintrag in der Doku wert, da hast du recht.