Funktionsparameter näher beschreiben in 2.3?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
henning
User
Beiträge: 274
Registriert: Dienstag 26. Juli 2005, 18:37

Moin!
Folgendes Problemchen:
"Umgebung" des Problems:
Ich arbeite mit mod_python und cheetah, das ganze Ding ist relativ gut modularisiert, und natürlich auch objektorientiert.
Der grobe Aufbau ist der, dass jedes Modul eine Klasse gleichen Namens enthält, von der mein handler ein Objekt erzeugt und je nach übergebenen GET- oder POST-Parametern
sich eine Funktion davon raussucht, und diese aufruft.
Danach wird novh eine Funtion namens body von dem Objekt aufgerufen, die bis dahin (meist von der vorher aufgerufenen Funktion) definiert sein sollte.

Auf diese Weise kann ich schön mit nem cheetah-template
von der Klasse ableiten, mit cheetah alle möglichen Ausgabe-Funktionen definieren, und in den Funktionen, die was tun, schreibe ich dann ein self.body = self.ausgabefun
ktion hin, und alle sin glücklich.

Diesen 2-Schritt mache ich bewusst um Datenvorbereitung (also SQL-Abfragen etc...) und ihre Prsäsentation getrennt zu halten.
So kann man leicht neue Templates für das Ding schreiben.

Nähere Umgebung des Problems:
Jetzt bin ich auf den Trichter gekommen, dass es blöd ist, wenn sich jede Funktion dauern selbst die Parameter aus dem field-objekt pulen und vor allem selbst auf Korrek
theit testen muss.

Darum hab ich mir für Paramterabfrage und -entgegennahme auch wieder Klassen geschrieben, die folgendes leisten:
- Eine "Abfragefunktion" (gibt also z.B. <input name="$name" ...> aus)
- Eine Ausgabefunktion (gibt den Wert als lesbaren string aus)
- Eine Funktion, die zunächst alles aus dem field-objekt holt, was zu dem Parameter passt
- Eine Validierungsfunktion, die guckt, ob die übergebenen Werte Sinn machen und sie in ein nützliches Format umwandelt (z.B. datetime.date für ein Datum)

Problem:
soweit, so gut.
Jetzt hätte ich aber gerne, dass ich irgendwo Objekte von meinen Klassen erzeuge, so dass mein handler die Funktionen gleich schön elgant mit "echten" argumenten aufrufe
n kann.
Also bis jetzt würde ich das so machen:

Code: Alles auswählen

class Modul(Modulbasis):
  def __init__(self):
      register_function(self.callme, PText("surname", "Nachname", maxlen=20), PTime("dinner", "Abendessen"))

  def callme(self, surname, dinner):
     # do something with already clean surname and dinner
Das gefällt mir aber deswegen nicht, weil meine Module u.U. auch mal ein bisschen länger werden können, ich hätte
das gerne in der Nähe der Methodendefinition.
Diese lustigen @-dinger (wie heissen die gleich, deskriptoren?) kann ich leider nicht verwenden, in meinem mod_python ist ein 2.3.5 verbaut, selbst ein neues compilen ko
mmt nicht in die Tüte :-/

Ich hatte auch schon sowas überlegt:

Code: Alles auswählen

class Modul(ModulBase):
   register_function(callme, ...)
   def callme(self, surname, dinner):
       # ...
Da finde ich aber auch nicht so richtig gefallen dran, weil ich wie im obigen Beispiel den Umstand gar nicht ausnutze, dass ich schon vor aufruf des Moduls weiß, welche
Funktion aufgerufen werden soll.
Wenn ichs so wie hier mache lege ich ja ne ganze Menge Parameter-Parse-Objekte sinnlos an :-/

Halbwegs sympatisch ist mir noch die Variante:

Code: Alles auswählen

class Modul(ModulBase):
   # ...
   def callme(self, surname, dinner):
   """ Das hier ist die callme-Funktion.
        Sie tut eigentlich gar nichts.
        | surname = PText("Nachname", maxlen=20)
        | dinner = PTime("Abendessen")
   """
      # ....
...sprich, die Argumentdeklarationen im docsting vornehmen und vor dem Aufrufen den docstring der entspr. Funktion ausparsen.
(Ist nicht sonderlich schwer, eine regex drüberjagen und das ganze nach ein bisschen kosmetik in ein eval() stecken).

Aber im Endeffekt kommt mir nichts von alledem wirklich elegant vor.
Hat jemand eventuell ne "schönere" Idee? (Beautiful is better than ugly)
Kann jemand von euch abschätzen, wie sich welche Lösung auf die Performance auswirkt? (Performance ist kein Problem, aber ich mag den Gedanken einfach nicht, bei jedem r
equest zu viel unnötige Arbeit zu tun)

Danke schonmal!
Henning
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Die @-Dinger, wie du sie nennst sind Dekoratoren (Decorators) auch wenn sie warscheinlich eher unesthetisch sind.

Die @-Syntax ist nur Syntactic Sugar, der in PEP 318 beschrieben wird. Dort steht auch, wie man das auch ohne die @-Syntax machen kann (also in den meisten anderen Python-Versionen).
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

henning hat geschrieben:Der grobe Aufbau ist der, dass jedes Modul eine Klasse gleichen Namens enthält, von der mein handler ein Objekt erzeugt und je nach übergebenen GET- oder POST-Parametern sich eine Funktion davon raussucht, und diese aufruft.
´
Das ist ja Lustig. Diese Funktionalität macht der Modul-Manager von PyLucid auch... Wobei ich es so mache, das im "Kopf" der Klasse ein Dict definiert, welche Methoden welche rechte hat (muß der User eingelogt sein, muß er Admin. sein) und welche methode gestartet werden soll, wenn CGI-Daten XY vorhanden sind...

Wobei ich gerade dabei bin das Konzept weiter auszubauen. Sodas halt direkt CGI-POST/GET Daten zur Methode "geschickt" werden. (Deswegen auch die Frage Zum identifizieren der Argument-Namen einer Methode)

Wobei ich die (so wie ich es nenne) "CGI_dependent_actions" wieder abschaffen will (Das ist das Aufrufen von bestimmten Methoden je nach vorhandensein von CGI-Daten).
Denn irgendwie stellt sich raus, das es das Debugging erschwert: Warum wird Methode XY nicht aufgerufen?
Mein neuer Ansatz ist es, das man den Ablaufplan direkt in den Methoden selbst festlegt... Wenn dann irgendwelche CGI-Daten fehlen, kann man das wenigstens als brauchbaren Fehler anzeigen lassen...

Naja, so viel zu meinem gelaber ;)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
henning
User
Beiträge: 274
Registriert: Dienstag 26. Juli 2005, 18:37

Leonidas hat geschrieben:Die @-Dinger, wie du sie nennst sind Dekoratoren (Decorators) auch wenn sie warscheinlich eher unesthetisch sind.
[...] Dort steht auch, wie man das auch ohne die @-Syntax machen kann (also in den meisten anderen Python-Versionen).
Ahh, genau, so hieß das :-)
Ja, dass man das auch ohne diese Notation machen kann ist klar, das Problem dabei ist nur, dass die Methode da schon definiert sein muss.
Ich hätte gerne der Lesbarkeit halber etwas in der Nähe des Methodenkopfes, so dass man gleich bei der Parameterdeklaration die "Typen" sehen kann, daher meine Idee mit den docstrings.

@jens: Hmm, ich weiß ja nicht, was genau dein PyLucid ist/macht, aber bevor wir beide unabhängig voneinander dasselbe machen, könnten wir ja eventuell ein bisschen mergen oder?
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

henning hat geschrieben:@jens: Hmm, ich weiß ja nicht, was genau dein PyLucid ist/macht, aber bevor wir beide unabhängig voneinander dasselbe machen, könnten wir ja eventuell ein bisschen mergen oder?
Also PyLucid ist ein CMS: http://www.pylucid.de
Ein bischen Info's und der Sourcecode zum Module Manager: http://www.pylucid.org/index.py?p=/Doku/module+manager
Ein Beispiel für ein Modul: http://www.jensdiemer.de/Programmieren/ ... ource+Code

Aber ich dachte eigentlich du willst nur Funktionen von Frameworks wie cheetah nutzten und keinen Modul/Plugin Manager selber schreiben, oder hab ich das was falsch verstanden?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
henning
User
Beiträge: 274
Registriert: Dienstag 26. Juli 2005, 18:37

Hmm, also wies aussieht ist dein PyLucid echt ne Nummer größer angelegt .-)
Aber wenn ich mal n bisschen Zeit habe kann ich ja mal ein paar source-snippets raussuchen und ein paar Zeilen dazu schreiben...

Zurück zum Topic:
Was mir auch noch eingefallen ist wäre sowas:

Code: Alles auswählen

class Modul(ModulBase):
  # ...
  def callme(self, surname=PText("Nachname", maxlen=20), dinner=PTime("Abendessen"))
     # ...
Was zwar die Parameterbeschreibung direkt bei den Parametern passieren lässt, aber auch wieder den Nachteil hat, dass bei jedem Request alle möglichen PText, PTime, usw... - Objekte erzeugt werden (vor allem von den ganzen Funktionen die eigentlich nicht aufgerufen werden!).
Und ausserdem ist es mißvertändlich, weil ich die Default-Parameter ja nicht im eigentlichen Sinne von default-Parametern nutzen würde, sondern als einzige Wertbelegung der Parameter.

Also am liebsten ist mir immernoch die docstring-variante, ich frage mich nur, ob die mit ihrer parserei noch "pythonic" ist ,-)
henning
User
Beiträge: 274
Registriert: Dienstag 26. Juli 2005, 18:37

Ich will meinen (fast) Monolog noch mal ein bisschen anfachen:

Meint ihr, das ist überhaupt sinnvoll, die Parameter so in Klassen zusammenzufassen? (Also sprich: Die Darstellung des [z.B] Textfeldes zusammen mit dem Validator etc... in eine Klasse zu packen)

Oder meint ihr es wäre sinnvoller, das aufzuteilen in Funktionen/Klassen von denen eine die Daten auswertet (bzw. validiert) und eine andere, die nach den Daten fragt?

Ich brauche diese beiden Sachen ja genaugenommen nie wirklich gleichzeitig (ausser in Ausnahmesituationen in denen die Eingabe mist war, dann frag' ich nochmal nach).

Ich kriege dieses Stil-Problem irgendwie nicht gelöst! Hat nicht irgendjemand schon mal vor dem Dilemma gestanden?
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Wie bist du denn jetzt verblieben???

Ich bin gerade dabei meinen Modul-Manager, der komplett neu geschrieben ist, neu zu überdenken...

Meine bisherige Vorgehensweise ist ja die, das ich in einem Dict festhalte, welche Argumente eine Methode braucht... Mit Typ... Hier mal ein Ausschnitt der Konfiguration:

Code: Alles auswählen

        "internal_page" : {
            "CGI_dependent_actions" : {
                "save_internal_page"        : {
                    "get_CGI_data"  : {"internal_page_name": str, "content": str, "description": str, "markup": int},
                },
            }
        },
Das sagt dem Modul-Manager, das die Methode "save_internal_page" die CGI-Daten "internal_page_name", "content" und "description" als String und "markup" als Zahl braucht... Das sieht dann so aus:

Code: Alles auswählen

    def save_internal_page(self, internal_page_name, content, description, markup):
        """ Speichert einen editierte interne Seite """
        ...
Ich frage mich nun, ab es möglich ist, nicht direkt im Methoden-Kopf nachzusehen, welche Daten gebraucht werden...

Also müßte auch ich sowas wie Meta-Information einfügen...

Egal was es so neues gibt, es muß auch mit Python v2.2.1 funktionieren...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
henning
User
Beiträge: 274
Registriert: Dienstag 26. Juli 2005, 18:37

Ich denke noch aus anderen Gründen über einen mittelgroßen reqrite nach (z.B. auf WSGI umzusatteln). Am meisten gefällt mir bis jetzt sowas hier:

Code: Alles auswählen


class MeinModul(BasisModul):
  add_info = (PText("name"), PText("surname"))
  def add(self, name, surname):
      # ...

  # Neue Idee:
  # Viele Funktionalitäten setzen sich aus mehreren aufrufen
  # Zusammen (man denke an eine Bestellung in einem shop-system
  # Also erfinden wir Funktionsgruppen:

  order_info = (PNumeric("step"),)
  def _G_order(self, step):
     step0_info = (PText("product"), PNumeric("count"))
     def step0(self, product, count):
         # ...
     #...
     return ( (step0_info, step0), (step1_info, step1), ...)
Was da an funktionalität zu nötig ist kann man halt in Basisklassen bzw. dem handler-Modul einbauen.

Mit dieser Methode habe ich zumindest bei mod_python den Vorteil, dass so sachen wie order_info nicht bei jedem request ausgewertet werden müssen.

Weiß zufällig jemand, ob das bei einem WSGI-Adapter erhalten bleiben würde? (Wenn ja dann würde der WSGI-Adapter sich ja unterschiedlich auf mod_ python und z.B. CGI "anfühlen", oder?)
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Was ist denn PText und PNumeric ??? Woher kommen die?

Ich denke ich muß generell bei meinem eigenen Module-Manager bleiben, weil er auch die Rechteverwaltung übernimmt (Darf der User die Methode des Plugins überhaupt ausführen?)... Oder gibt es da auch schon was fertiges???

Aber vielleicht kann ich ja halt, die "Syntax" mit PText und PNumeric auch übernehmen und kommte vom Module-Manager aus auch auf diese "Erwarteten-Argumente"...

Wie sieht eigentlich das Handling aus, mit Argumenten die erwartet werden, aber nicht da sind???
Ein Problem sind z.B. HTML-Checkboxen und Textareas... Wenn man die Checkbox nicht ausfüllt oder die HTML-Textarea leer ist, dannn tauchen die nicht in den CGI-Daten mit None oder ="" auf, sondern sind überhaupt nicht da... Wie wird das geregelt???

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
henning
User
Beiträge: 274
Registriert: Dienstag 26. Juli 2005, 18:37

jens hat geschrieben:Was ist denn PText und PNumeric ??? Woher kommen die?
Die habe ich selbst geschrieben, weil ich noch die Möglichkeit haben will, z.B. die Länge oder den Wertebereich mit keywords festzulegen, was bei "reinem" str oder int ja nicht ginge.
Ich denke ich muß generell bei meinem eigenen Module-Manager bleiben, weil er auch die Rechteverwaltung übernimmt (Darf der User die Methode des Plugins überhaupt ausführen?)... Oder gibt es da auch schon was fertiges???
Ob es da was fertiges gibt, kann ich dir nicht sagen, aber bei mir mache ich es so, dass es eine Tabelle gibt mit Nutzergruppen und welche Funktionen aus welchen Modulen sie ausführen dürfen.
Mein handler (entspricht ja etwa deinem Modulmanager) guckt das da nach, noch bevor er das Modul lädt.
Das is mir lieber, als die recht hardzucoden. Dafür habe ich mir btw. auch die Funktionsgruppen ausgedacht, weil es halt oft Funktionen gibt, die zusammengehören und dadurch auch die selben permissions haben.
Wie sieht eigentlich das Handling aus, mit Argumenten die erwartet werden, aber nicht da sind???
Kann ich bei mir von Fall zu Fall unterscheiden. Meine Paramerdefs (als PText, PNumeric, etc...) sind alle von einer Klasse namens Param abgeleitet und der kann man ein keyword "invalid übergeben. Mal ein Beispiel (ich lasse mal die ganzen imports etc... weg und nehme an, das alle meine Funktionen und klassen direkt verfügbar sind[wie in allen meinen Beispielen])

Code: Alles auswählen

# ... irgendwo in einer Klasse in einem Modul ...
  fun_info = (PText("name", invalid=RAISE), PText("extra_info", invalid=USE_DEFAULT, default="No extra information given")
  def fun(self, name, extra_info):
      # ...
Wobei man dazusagen muss, dass ich nicht unterscheide zwischen "Wert wurde nicht übergeben" und "Wert ist gleich dem leeren String", da das zumindest der Firefox auch nicht zu machen tut.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Wie mir scheint arbeiten wir echt genau an den selben Dingen, was??? Ist dein Quellentext online verfügbar??? Für was machst du das ganze denn überhaupt? Bei mir ist es (natürlich) PyLucid (und das schreibe ich nicht weil es etwas neues für euch ist, sondern damit ich einmal mehr meinen Link unterbringen kann :lol:)
henning hat geschrieben:
jens hat geschrieben:Was ist denn PText und PNumeric ??? Woher kommen die?
Die habe ich selbst geschrieben, weil ich noch die Möglichkeit haben will, z.B. die Länge oder den Wertebereich mit keywords festzulegen, was bei "reinem" str oder int ja nicht ginge.
Das ist bei mir eigentlich so unbedingt notwendig. Es reicht eigentlich eine str oder int Angabe. Es wird dann vom ModulManager eine konvertierung mit str() oder int() vorgenommen...
henning hat geschrieben:Ob es da was fertiges gibt, kann ich dir nicht sagen, aber bei mir mache ich es so, dass es eine Tabelle gibt mit Nutzergruppen und welche Funktionen aus welchen Modulen sie ausführen dürfen.
Mein handler (entspricht ja etwa deinem Modulmanager) guckt das da nach, noch bevor er das Modul lädt.
Das is mir lieber, als die recht hardzucoden. Dafür habe ich mir btw. auch die Funktionsgruppen ausgedacht, weil es halt oft Funktionen gibt, die zusammengehören und dadurch auch die selben permissions haben.
Hm! Bei meiner Rechteverwaltung bin ich noch nicht weiter gekommen. Bisher gibt es nach wie vor nur die Unterscheidung zwischen Nicht-eingeloggt, eingeloggt als Normaler User und eingeloggt als Administrator...
Die Fehlermeldung bei einem Aufruf einer Methode die nur für eingeloggte User erlaubt ist, kann man z.B. hier sehen:
http://www.pylucid.org/?command=pageadm ... =edit_page
Wobei ich es auch später über Usergruppen machen wollte. Die Tabellen dafür existieren schon, aber die Auswertung noch nicht...
henning hat geschrieben:Kann ich bei mir von Fall zu Fall unterscheiden. Meine Paramerdefs (als PText, PNumeric, etc...) sind alle von einer Klasse namens Param abgeleitet und der kann man ein keyword "invalid übergeben.
Interessant... Ich mach es jetzt nun so, das man einfach einen Default-Wert, wie es sonst auch üblich ist, definiert:

Code: Alles auswählen

def Beipeiel(self, id1, txt=""):
In dem Fall muß id1 da sein, aber txt ist optional...
Generell ist es immer so, das der Modul-Manager ein Dict mit **parameter übergibt...
henning hat geschrieben:Mal ein Beispiel

Code: Alles auswählen

# ... irgendwo in einer Klasse in einem Modul ...
  fun_info = (PText("name", invalid=RAISE), PText("extra_info", invalid=USE_DEFAULT, default="No extra information given")
  def fun(self, name, extra_info):
      # ...
Hm!
Wie wäre es denn mit dieser Idee:

Code: Alles auswählen

class Modul(ModulBase):
  # ...
  def callme(self, PText("Nachname", maxlen=20), PNumeric("id"=0))
     # ...
Übergeben wird dann sowas wie {"Nachname":"Noname", id: 2} Wobei id nicht vorhanden sein muß...
Allerdings wüste ich dabei nicht, wie man das von außerhalb (also vom ModulManager) "abfragen" kann... Wobei wenn PText eine Funktion ist, die entweder den Nachnamen zurück liefert oder eine Exception auslöst?!?!

Ich meine nur, bei meiner und deiner jetztigen Variante finde ich es doof, das die Variablen-Namen zweimal auftauchen... Einmal im "Regelsatz" zu der Methode, und in der Methode selber...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Wenn ich es mir recht überlege, ist deine Variante doch garnicht so verkehrt... Es hätte halt den Vorteil, das man auch einfach im Modul alle Parameter in einem Dict erwarten kann! Also so:

Code: Alles auswählen

  fun_info = (PText("name", maxlen=20), PText("extra_info"))
  def fun(self, **kwargs):
Weil ein dict, kann ich direkt für ein SQL UPDATE mit meinem DB-Wrapper gebrauchen ;)


Wobei mir deine "Syntax" nicht ganz optimal erscheint... Es reicht doch eigentlich das:

Code: Alles auswählen

  fun_info = (PText("name", maxlen=20), PText("extra_info"))
  def fun(self, name, extra_info="No extra information given"):
Also wenn name nicht da ist, wird spätestens beim "aufruf" der Methode eine Exception auftreten... Bei extra_info ist hingegen der default, so wie immer gesetzt...
Andererseits funktioniert das default-"setzten" nicht, wenn man **kwargs verwendet :( Also es doch in PText() machen? Also so:

Code: Alles auswählen

  fun_info = (PText("name", maxlen=20), PText("extra_info", default="No extra information given"))
  def fun(self, **kwargs):
Beide Varianten sind aufgeräumter, oder?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
henning
User
Beiträge: 274
Registriert: Dienstag 26. Juli 2005, 18:37

Stimmt, deine Versionen sind ein bisschen ordentlicher.
Ich hatte ursprünglich alles über das Parameter-Objekt gesetzt, weil ich noch nicht wusste, wie ich die übergebe. (Hatte auch erst an ein zentrales dict gedacht)

Als default-werte für die Argumente möchte ich meine Parameterobjekte nicht übergeben, weil das verwirrend ist (Die Funktion kriegt ja im Endeffekt nicht das Paramterobjekt mit seinen validierungsfunktionen und co. sondern die validierten Daten, die dieses Objekt erzeugt.)

Das ganze ist ein Projekt für das Institut in dem ich arbeite und soll Aufgaben übernehmen wie die Verwaltung von Studenten & Mitarbeiten, Hausarbeiten, Klausuren, Workstations etc...

Momentan ist der code noch nicht irgendwo abrufbar, er ist derzeit auch noch nicht in einem Zustand in dem ich ihn "an die Öffentlichkeit" lassen möchte, aber wenn ich mein Paramter-kram konsequent drin habe und es ansonsten eine etwas einheitlichere Struktur hat ,-)
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

(Btw. Komisch ich dachte ich hätte schon eine Antwort geschrieben?!?!?)

Also die Übergabe mittels dict, klappt wunderbar. Anders geht's glaube ich auch mit einer Liste, aber dann müssen die Positionen genau übereinstimmen. Da find ich das Dict besser ;)

Kannst du wenigstens mal den Source-Code von PText und PNumeric posten? Würd mich mal interessieren wie es im Detail aussieht...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
henning
User
Beiträge: 274
Registriert: Dienstag 26. Juli 2005, 18:37

Also, das ganze arbeitet noch mit Cheetah zusammen, das sollte man aber sehr leicht ändern können bzw. auf eine andere Template-Engine übertragbar sein.
Ausserdem hat mich mein Gedächtnis ausgetrickst, uch hatte die Klasse PDecimal genannt und nicht PNumeric.

Zunächst mal die Basisklasse Param:

Code: Alles auswählen

import exceptions
from Cheetah.Template import Template

# What to do when validate fails
RAISE = 0        # Raise an exception
USE_DEFAULT = 1  # Use default value
ROBUST = 2       # Try to read what you can,
                 # even if some of the data

class ParamError(exceptions.TypeError):
  def __init__(self, p, s=None):
    exceptions.TypeError.__init__(self)
    self.p = p
    self.s = s
  def __str__(self):
    return "Parameter '%s' ist nicht vom Typ '%s'%s." % (
        self.arg.display, self.arg.typeinfo,
        self.s and (": " + s) or "")

class UncleanError(exceptions.TypeError):
  def __init__(self):
    exceptions.TypeError.__init__(self)
  def __str__(self):
    return "Parameter unvalidiert benutzt!"
    
class Param(Template):
  """
    Encapsulates all functions and data related
    to a parameter.
    A parameter is something that is entered by a user
    in a form and then used by a pice of code.
  """
  def __init__(self, display, internal, value="", default=None, invalid=RAISE, *args, **kwargs):
    Template.__init__(self, *args, **kwargs)
    self.display = display
    self.internal = internal
    self.value = value
    self.invalid = invalid
    self.default = default
    if self.default == None:
      self.default = self.value
    self.typeinfo = "Parameter"

    self.is_clean = False
    
  def validate(self):
    """Validate self.value, react as wanted on failure.
    Override _clean() to validate your way."""
    if not self._clean():
      if self.invalid == RAISE:
        raise ParamError(self)
      else: # self.invalid in (ROBUST, USE_DEFAULT)
        self.value = self.default
    self.is_clean = True

  def __str__(self):
    """Display the value in a nice form.
    (Override _output() to make you own form)"""
    if not self.is_clean:
      #raise UncleanError(self)
      # Be kind, validate now instead of complaining
      self.validate()
    return self._output()
    
  # TODO: Move this to _input,
  # And build a wrapper around it with a nice name
  def input(self):
    """
      Thats how the frontend may ask for self.value.
      Note that its perfectly possible to choose
      special in-/output-Methods for specific params
      or classes at the frontend side.
    """
    return """<input type="text" name="%s" value="%s" />""" % (self.internal, self.value)
    
  def acquire(self, field):
    """
      Read a "dirty" value from Field f.
    """
    self.is_clean = False
    _getfromfield(self, field)

  def acquire_direct(self, v):
    """
      Read a "dirty" value through argument v.
    """
    self.is_clean = False
    self._getdirect(v)
  
  #
  #
  #
    
  def _clean(self):
    """
      If needed transform self.value to a type
      pleasent for your Param-Class,
      Return True if that went okay and self.value afterwards contains
      some useable Data of your favourite type,
      else return False.
    """
    self.value = str(self.value)
    return True

  def _output(self):
    """Thats how the frontend may display self.value.
      This function is meant to be overridden in subclasses."""
    return str(self.value)

  def _getfromfield(self, f):
    """
      Override this method if you have
      a different way of reading data.

      Note that the type of value may change later in
      clean(), just read here as far as you can get
      WITHOUT error checking.
      (i.e. dont raise an error here if the user
       supplied something wrong or nothing or to much
       or what ever can go wrong with the data.)
    """
    self._getdirect(f.getfirst(self.internal))
    
  def _getdirect(self, v):
    """
      Override this method if you have
      a different way of reading data directly.
      You may call this from getfromfield.

      Note that the type of value may change later in
      clean(), just read here as far as you can get
      WITHOUT error checking.
      (i.e. dont raise an error here if the user
       supplied something wrong or nothing or to much
       or what ever can go wrong with the data.)
    """
    self.value = v
Davon abgeleitet ist z.B. PText.
Die heißt jetzt bei mir PTextBase weil ich von jeder Param-Klasse nochmal eine Cheetah-Klasse ableite, die hier z.B. PText heissen würde, wie gesagt, den cheetah-kram kann man vermeiden wenn man will.

Code: Alles auswählen

from Param import Param
from mod_python import apache
    
class PTextBase(Param):
  """
    A parameter class for simple and short text.
  """
  def __init__(self, display, internal, value="", maxlen=0, *args, **kwargs):

    Param.__init__(self, display, internal, value, *args, **kwargs)
    self.maxlen = maxlen
    self.typeinfo = "Text" + (maxlen and (" der Länge %d" % maxlen) or "")

  def _clean(self):
    return (not self.maxlen) or len(self.value) <= self.maxlen
Hier die Cheetah-Ableitung.
Ist zwar ein bisschen overhead, aber ich finds schöner als das in strings zu quoten.
(Ausserdem ist es damit als teil des Frontends austauschbar). Ich denke, der cheetah-code erklärt sich von selbst, wenn man python kann.

Code: Alles auswählen

#extends PTextBase

#def input
  #set $sz = $maxlen and (" size=%d" % $maxlen) or ""
  <input type="text" name="$internal" value="$value"$sz/>
#end def
PDecimal:

Code: Alles auswählen

from Param import Param

class PDecimalBase(PText):
  def __init__(self, display, internal, value="", digits=0, *args, **kwargs):
    PTextBase.__init__(self, display, internal, value, *args, **kwargs)
    self.typeinfo = "Dezimalzahl" + digits and (" mit %d Stellen" % digits) or "")
    self.digits = digits
    
  def _clean(self):
    if (not self.digits) or len(self.value) == self.digits:
      try:
        self.value = int(self.value)
        return True
      except ValueError:
        pass
    return False
Hier nochmal was interessanteres, PList für Combo-Boxen:

Code: Alles auswählen

from Param import Param, ParamError

class PListBase(Param):
  """
    Encapsulates a List.
    This means: The user can select one or more
    items from a given list (choices).
    The selection is the value.
  """
  def __init__(self, display, internal, value="",
      choices=(), multiple=False, *args, **kwargs):
    Param.__init__(self, display, internal, value,
        *args, **kwargs)
    self.choices = choices
    self.multiple = multiple
    self.typeinfo = "Auswahl aus den Werten %s%s" % (
        str(self.choices), (self.multiple and " (mehrere erlaubt)" or ""))

  def _clean(self):
    l = len(self.choices)
    v = {}
    if self.multiple:
      v = {}
      # self.value still is a list of
      # (hopefully) indexes to choices
      for x in self.value:
        if type(x) == int and x in range(l):
          v[x] = True # This is slightly ROBUST, but wont harm too much
        else:
          if not self.invalid == ROBUST:
            return False
      # Now self.values is something like
      # {1: True, 4: True, 5: True}
      # with all indices in range(l)
      return True
      
    else: # value should be an index for choices
      try: self.value = int(self.value)
      except: return False
      return self.value in range(l)

  def _output(self):
    s = comma = ""
    if self.multiple:
      # Return a simple, comma-separated list. (Thats read- and parseable)
      for i in range(len(self.choices)):
        if i in self.value:
          s += comma + self.choices[i]
          comma = ", "
      return s

    else:
      # Just return our value
      return Param._output(self)
  
  def _getfromfield(self, f):
    if self.multiple: # We should receive a list
      self.value = f.getlist(self.internal, [])

    else: # Just get the value
      super(PListBase, self).getfromfield(self, f)

Code: Alles auswählen

#extends PListBase

#def input
  #set $ml = $multiple and ' multiple="multiple"' or ""
  <select$ml>
  #for $i in $range($len($choices))
    #if $multiple
      #set $sel = $value.has_key($i) and ' selected="selected"' or ""
    #else
      #set $sel = ($i == $value) and ' selected="selected"' or ""
    #end if
    <option value="$i"$sel>$choices[$i]</option>
  #end for
  </select>
#end def
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Und hier wieder die obligatorische WSGI Lösung :-)
http://trac.pocoo.org/browser/wsgi/trun ... alidate.py
TUFKAB – the user formerly known as blackbird
henning
User
Beiträge: 274
Registriert: Dienstag 26. Juli 2005, 18:37

n1.
Wenn ich meinen Kram mal komplett nach WSGI transformiere, werd ich das sicher noch um einiges erweitern (+mir vielleicht erlauben, da eine Objektorientierung nach dem Vorbild von meiner jetzigen Lösung reinzusetzen)
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

So. Hab jetzt meine WSGI Lösung so abgeändert, dass sie den Application Output Parst und in die Inputfelder die Werte wieder einsetzt. Somit übernimmt die jetzt sämtliche Aufgaben. Wenn man nichts zurückschreiben lassen will reicht ein 'environ['wsgi.middleware.validate.autoinsert'] = False' und er wird damit aufhören :-)
TUFKAB – the user formerly known as blackbird
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Argh. Weil mich das jetzt etwas aufgehalten hat, hab ich ich jetzt trotzdem eine Beispielanwendung für die ValidationMiddleware geschrieben.

Den Code sieht man hier, den aktuellen Snapshot Code herunterladen kann man hier.
TUFKAB – the user formerly known as blackbird
Antworten