Ein kleines Nagare-Tutorial

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Teil 1: Installation

Nagare benötigt Stackless-Python, welches man sich außer unter Windows selbst kompilieren muss. Ich beschreibe hier, wie es unter Ubuntu 8.04 funktioniert.

Ich musste (glaube ich) fünf Pakete nachinstallieren: libc6-dev mit linux-libc-dev, libzlib1g-dev, libxml2-dev und libxslt1-dev. Ohne die ersten beiden Pakete geht gar nichts. Das nächste wird für die Installation von "setuptools" benötigt. Die letzten beiden werden später benötigt, um "lxml" zu kompilieren und zu installieren.

Die Pakete installiert man (gefahrlos, falls sie schon da sind) so:

Code: Alles auswählen

$ sudo apt-get install libc6-dev linux-libc-dev zlib1g-dev libxml2-dev libxslt1-dev
Als nächstes lädt man stackless-252-export.tar.bz2, packt das Archiv aus und übersetzt es so, das es im eigenen HOME-Verzeichnis lebt und nicht mit einem installierten Python in Konflikt gerät:

Code: Alles auswählen

$ cd $HOME
$ wget http://www.stackless.com/binaries/stackless-252-export.tar.bz2
$ tar xfj stackless-252-export.tar.bz2
$ cd stackless-2.5.2-*
$ ./configure --prefix=$HOME/stackless
$ make
$ make install
Für den nächsten Schritt ist es wichtig, dass das eigene Python im PATH for dem normalen Python steht. Andernfalls klappt die Installation von "easy_install" nicht:

Code: Alles auswählen

$ export PATH=$HOME/stackless/bin:$PATH
Prüfe nun, ob es geklappt hat:

Code: Alles auswählen

$ python
Python 2.5.2 Stackless 3.1b3 060516 (release25-maint, Sep 28 2008, 17:22:40) 
[GCC 4.2.3 (Ubuntu 4.2.3-2ubuntu7)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 
Beim normalen Python fehlt der Hinweis auf "Stackless" in der Ausgabe.

Lade nun setuptools-0.6c9-py2.5.egg und installiere es, um das "easy_install"-Kommando zu bekommen:

Code: Alles auswählen

$ cd $HOME
$ wget http://pypi.python.org/packages/2.5/s/setuptools/setuptools-0.6c9-py2.5.egg#md5=fe67c3e5a17b12c0e7c541b7ea43a8e6
$ sh setuptools-0.6c9-py2.5.egg
Unter $HOME/stackless/bin/ sollte jetzt ein easy_install-Skript zu sehen sein, was man mit "which easy_install" prüfen kann. Es sollte im PATH sein, der weiter oben angepasst wurde.

Optional kann man jetzt "virtualenv" installieren. Mir scheint es nicht ganz so wichtig, weil man ja eh mit einer eigenen Version von Python unterwegs ist, aber andererseits schadet es auch nicht:

Code: Alles auswählen

$ cd $HOME
$ easy_install virtualenv
$ virtualenv nagare
$ cd nagare
$ source bin/activate
Nun kann man Nagare mit all seinen Abhänigkeiten installieren, was eine ganze Zeit lang dauert. Abhängig davon, ob man ein virtualenv gesetzt hat oder nicht, wird das "easy_install" aus $HOME/stackless/bin oder $HOME/nagare/bin benutzt:

Code: Alles auswählen

$ easy_install nagare[full]
Es sollte jetzt ein neues Kommando "nagare-admin" geben, was man mit "which" prüfen kann.

Teil 2 - Die erste Anwendung

Erzeuge nun eine neue Nagare-Anwendung:

Code: Alles auswählen

$ cd $HOME
$ nagare-admin create-app myapp
$ cd myapp
$ python setup.py develop
Zum Starten benutzt man am besten eine zweite Shell (in der man dann ebenfalls das aktuelle virtualenv aktivieren muss bzw. wenigstens den PATH auf die Stackless-Version von Python setzen muss). Danach gibt man ein:

Code: Alles auswählen

$ nagare-admin serve --debug --reload myapp
Nun kann man auf [url=localhost:8080/myapp/]localhost:8080/myapp/[/url] die Standardwillkommensmeldung sehen. Der Server kann weiterlaufen und lädt automatisch alle veränderten Dateien nach. Er stürzt nur bei Syntaxfehlern im Python ab und muss dann neu gestartet werden.

Der Programmcode der angezeigten Seite befindet sich im Verzeichnis "myapp" in der gleichnamigen Datei "myapp.py", in der eine Klasse "Myapp" definiert ist. Die Variable "app" enthält diese Klasse und ist die Verbindung nach außen, der "Entrypoint". In "setup.py" ist festgelegt, dass "myapp.myapp::app" die Stelle ist, wo Nagare die Anwendung findet. Eigentlich müsste man dort auch direkt Myapp eintragen können und kann sich dann die Variable sparen.

Die Beispielanwendung besteht neben der leeren Klasse "Myapp" nur noch aus einer "render"-Methode, die über "@presentation.render_for(Myapp)" mit "Myapp" verknüpft wird.

Bauen wir diese Anwendung nun zu einer Anwendung um, die nacheinander zwei Zahlen erfragt und dann die Summe dieser Zahlen ausgibt. Dazu benötigen wir eine Komponente, die mit einem Eingabefeld nach der Zahl fragt und eine Schaltfläche "Weiter" enthält.

Dies ist das Modell für meinen Dialog. Es speichert einen Fragetext und den anzuzeigenden Wert:

Code: Alles auswählen

class Input(object):
    def __init__(self, prompt, value=None):
        self.prompt, self.value = prompt, value
        
    def set_value(self, value):
        self.value = value
Dazu kommt die "render"-Methode:

Code: Alles auswählen

@presentation.render_for(Input)
def render(self, h, comp, *args):
    with h.form:
        h << self.prompt
        h << h.input(value=self.value).action(self.set_value)
        h << h.input(type='submit', value='Weiter')
    return h.root
In "render" von "Myapp" kann ich nun meine Komponente anzeigen. Fügen wir dazu hinter "Your application is running" folgendes ein:

Code: Alles auswählen

            ...
            h << h.h1('Your application is running')
            h << component.Component(Input("Erste Zahl:", 3))
            h << component.Component(Input("Zweite Zahl:", 4))
"component" muss genau wie "presentation" von "nagare" importiert werden.

Hier ist eine zweite Komponente, die etwas anzeigen kann:

Code: Alles auswählen

class Display(object):
    def __init__(self, value):
        self.value = value

@presentation.render_for(Display)
def render(self, h, comp, *args):
    with h.p:
        h << self.value
    return h.root
Auch diese können wir testweise als Komponente in render() von Myapp eintragen.

Ich möchte nun aber nicht, dass beide Eingabefelder gleichzeitig angezeigt werden, sondern dass dies nacheinander geschieht. Dazu braucht man einen "Task" mit einer go()-Methode:

Code: Alles auswählen

class Add(component.Task):
    def go(self, comp):
        comp.call(Input("Erste Zahl:", 3))
        comp.call(Input("Zweite Zahl:", 4))
        comp.call(Display(7))
Um diese Komponente jetzt an der Stelle einzubauen, wo vorher die beiden Eingabefelder waren, kann man nicht einfach immer neue Komponenten erzeugen, sondern muss eine einmal initialisierte Komponente wiederverwenden:

Code: Alles auswählen

class Myapp(object);
    def __init__(self):
        self.add = component.Component(Add())

            ...
            h << h.h1('Your application is running')
            h << self.add
Noch wird aber nichts berechnet. Die call()-Funktionen rufen eine andere Komponente wie eine Subroutine auf. Aus so einer Subroutine kann man mit answer() ein Ergebnis zurückliefern. Dazu muss dem Weiter-Button eine action() gesetzt werden:

Code: Alles auswählen

class Input...
    ...input(type='submit', value='Weiter').action(lambda: comp.answer(self.value))

class Add(component.Task):
    def go(self, comp):
        a = int(comp.call(Input("Erste Zahl:", 3)))
        b = int(comp.call(Input("Zweite Zahl:", 4)))
        comp.call(Display(a + b))
Ich überlasse es dem Leser, dafür zu sorgen, eine Fehlermeldung auszugeben, wenn keine Zahl eingegeben wurde.

Stefan
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

Danke für das Tutorial, werde ich, wenn ich Zeit habe, mal ausprobieren.

Was mich ein bisschen wundert ist, dass es anscheinend nicht vorgesehen ist, die Klassen direkt von component.Component erben zu lassen. Damit sollte man sich ja dann auch diese seltsamen Decorator-Rendermethoden sparen können.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Darii hat geschrieben:Was mich ein bisschen wundert ist, dass es anscheinend nicht vorgesehen ist, die Klassen direkt von component.Component erben zu lassen. Damit sollte man sich ja dann auch diese seltsamen Decorator-Rendermethoden sparen können.
Möglicherweise gibt es einen Grund, doch ich sehe ihn (noch) nicht. Seaside kommt mit einfacher Vererbung von einer WAComponent-Klasse aus. Die mögliche Mehrfachvererbung von Python sollte die Sache eher noch einfacher machen.

Stefan
Antworten