PyKConfigCompiler

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Benutzeravatar
Jerry
User
Beiträge: 13
Registriert: Freitag 29. Oktober 2010, 15:00

Moin,

Da ich noch nicht soooooo viel Erfarung mit Python habe (ist auch mein erstes öffentliches Projekt), brauche ich mal die Einschätzung der Gurus hier. Bei der Software handelt es sich um einen Quelltext Generator für das KConfigXT framework von KDE. Mein Ziel ist letztendlich, das mein Programm offizieller Bestandteil von PyKDE wird, wenn KDE 4.7 rauskommt.

Momentan ist das alles noch im Alpha Stadium, es fehlen hier und da noch ein paar kleine Features, die API kann sich noch leicht ändern etc. Leider bin ich noch nicht dazu gekommen, meinen Quelltext zu kommentieren... (steht aber auf meiner TODO Liste) Ich wäre euch sehr dankbar wenn ihr mal drüberschauen könntet :)

http://gitorious.org/pykconfigcompiler/ ... ees/master

Es sind übrigens nur knappe 700 Zeilen Code (Leerzeilen, Lizenzinfos miteingerechnet)
Nur weil ich paranoid bin, heisst das noch lange nicht, dass ich nicht verfolgt werde!
lunar

Die Vorlagen zur Quelltexterzeugung würde ich in separate Dateien auslagern. Das erhöht die Übersicht, und hat auch den Vorteil, dass man die Vorlagen wie normale Python-Dateien mit Syntaxhervorhebung usw. bearbeiten kann. Die ganzen "if .. elif .. else"-Kaskaden würde ich durch ein Wörterbuch mit Callback-Funktionen ersetzen, beispielsweise statt:

Code: Alles auswählen

def _read_kcfg(self, document):
    for element in document.getroot():
        if element.tag == NS + 'include':
            self._read_include(element)
        elif element.tag == NS + 'kcfgfile':
            self._read_kcfg_file(element)
        elif element.tag == NS + 'signal':
            self._read_signal(element)
        elif element.tag == NS + 'group':
           self._read_group(element)
eher:

Code: Alles auswählen

ELEMENT_HANDLERS = {
    NS+'include': _read_include,
    NS+'kcfgfile': _read_kcfg_file,
    NS+'signal': _read_signal,
    NS+'group': _read_group}

def _read_kcfg(self, document):
    for element in document.getroot():
        handler = self.ELEMENT_HANDLERS.get(element.tag)
        if handler:
            handler(self, element)
        else:
            # emit some kind of warning about unknown tag
So oder ähnlich geht das an manchen anderen Stellen auch. Die Fehlerbehandlung sollte auch verbessert werden, "SystemExit" auszulösen, ist nicht unbedingt die beste aller Möglichkeiten. Es mag anderes geben, was man auch noch verbessern könnte, so genau habe ich mir den Quelltext nicht angesehen.

Ich halte das Projekt als solches für überflüssig. Sorry, KConfigXT in Ehren, aber Python ist halt nun mal nicht C++. XML ist schon keine sinnvolle Wahl (siehe Python is not Java), aber sei es in diesem Fall der Interoperabilität geschuldet. Ein Quelltextgenerator dagegen ist auf jeden Fall immer der falsche Ansatz, ein Problem in Python zu lösen. Python ist keine Sprache, in der man kompilieren muss, denn sie ist so dynamisch, dass man fast alles genauso gut zur Laufzeit durchführen kann. Konkret könnte man das Konfigurationsobjekt also einfach zur Laufzeit direkt aus der Konfigurationsbeschreibung erzeugen, ganz ohne irgendwelchen Python-Quelltext zu erzeugen. Das wäre der richtige Ansatz, KConfigXT und KCFG in Python zu implementieren.

Aber auch KConfigXT selbst halte ich in Python für eher überflüssig, oder zumindest für nicht ganz so wichtig wie in C++. KCoreConfigSkeleton und KConfigDialogManager sind in aktuellen Versionen von PyKDE4 so fehlerhaft (wie Du ja den Kommentaren in Deinem Quelltext nach zu urteilen selbst festgestellt hast), dass man sie nicht sinnvoll nutzen kann, zumindest nicht für alles, was über fertige Steuerelemente und primitive Typen hinausgeht. Und das zentrale Feature von KConfigXT, nämlich die automatische Verwaltung der Steuerelemente im Konfigurationsdialog, ist in Python im Gegensatz zu C++ ziemlich trivial zu implementieren, ohne das man sich mit XML oder Quelltextgeneratoren abgeben müsste (was dann im Übrigen auch ganz ohne KDE nur in Qt umzusetzen ist). Ich kann bei Interesse gerne den Quelltext aus einem meiner Projekte zeigen und erklären, mit dem ich die Konfiguration einer KDE-Anwendung verwalte.
Benutzeravatar
Jerry
User
Beiträge: 13
Registriert: Freitag 29. Oktober 2010, 15:00

lunar hat geschrieben:Die Vorlagen zur Quelltexterzeugung würde ich in separate Dateien auslagern. Das erhöht die Übersicht, und hat auch den Vorteil, dass man die Vorlagen wie normale Python-Dateien mit Syntaxhervorhebung usw. bearbeiten kann. Die ganzen "if .. elif .. else"-Kaskaden würde ich durch ein Wörterbuch mit Callback-Funktionen ersetzen...
Ok, das lässt sich einrichten
lunar hat geschrieben: So oder ähnlich geht das an manchen anderen Stellen auch. Die Fehlerbehandlung sollte auch verbessert werden, "SystemExit" auszulösen, ist nicht unbedingt die beste aller Möglichkeiten.
Wie gesagt, es ist noch in Entwicklung, SystemExit ist nur ein temporärer Notbehelf, der verschwindet demnächst (genauso wie die "post_processing" Funktion)
lunar hat geschrieben:Ich halte das Projekt als solches für überflüssig.
:cry: :cry: :cry:
lunar hat geschrieben:Konkret könnte man das Konfigurationsobjekt also einfach zur Laufzeit direkt aus der Konfigurationsbeschreibung erzeugen, ganz ohne irgendwelchen Python-Quelltext zu erzeugen. Das wäre der richtige Ansatz, KConfigXT und KCFG in Python zu implementieren.
Öhm, also die Möglichkeit das dynamisch zur Laufzeit zu erzeugen besteht, in der __init__.py gibts eine Funktion "load_kconfig" die genau das macht.
lunar hat geschrieben:Und das zentrale Feature von KConfigXT, nämlich die automatische Verwaltung der Steuerelemente im Konfigurationsdialog, ist in Python im Gegensatz zu C++ ziemlich trivial zu implementieren, ohne das man sich mit XML oder Quelltextgeneratoren abgeben müsste (was dann im Übrigen auch ganz ohne KDE nur in Qt umzusetzen ist). Ich kann bei Interesse gerne den Quelltext aus einem meiner Projekte zeigen und erklären, mit dem ich die Konfiguration einer KDE-Anwendung verwalte.
Bin da sehr interessiert!

Vielen Dank!
Nur weil ich paranoid bin, heisst das noch lange nicht, dass ich nicht verfolgt werde!
BlackJack

@Jerry: Die Möglichkeit das "dynamisch" zu erzeugen ist doch aber das eben zu Laufzeit Quelltext zu erzeugt und übersetzt wird. Wenn da irgendwo generierter Quelltext und `compile()` oder ``exec`` vorkommt, dann ist das nicht wirklich dynamisch.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Was ist denn das? So ein Konfigurationsfenster-Generator auf Basis einer Konfigurationsdefinition?
lunar

@Dauerbaustelle: KCFG definiert ein Schema für Konfigurationsdateien, aus dem man dann ein Konfigurationsklasse für die Konfigurationsdatei kompilieren kann. Lädt man die Konfigurationsdatei mithilfe dieser erzeugten Klasse, so wird die Konfigurationsdatei automatisch verifiziert, und Werte werden automatisch in die richtigen Typen konvertiert. Außerdem kann diese Konfigurationsklasse mit einem Steuerelement verbunden werden, so dass die Konfigurationswerte zwischen Konfiguration und Anzeige ausgetauscht werden.

Einen Konfigurationsfenster automatisch generieren kann man damit aber nicht.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Inwiefern mit der Anzeige verbinden?

Und was der OP schreiben will ist dann quasi die Lib nochmal neu in Python (hab gesehen dass das Original C++ ist)? Oder welchen Teil davon?
Benutzeravatar
Jerry
User
Beiträge: 13
Registriert: Freitag 29. Oktober 2010, 15:00

Das einzige was ich neu schreibe ist der source code generator, der aus einer Konfigurationsdatei Python Quelltext generiert. Bis jetzt muss man die Konfigurationsklasse immer selbst schreiben (was bei größeren Sachen durchaus 100 zeilen aufwärts darstellt) Ich wollte ja zuerst den kconfigcompiler (das ist die C++ Variante) erweitern, so dass der auch Python Quelltext erzeugen kann, allerdings ist das ein über 2000 zeilen langes Monster, da war neu schreiben die bessere Wahl imho.
http://api.kde.org/4.x-api/kdelibs-apid ... ource.html
Nur weil ich paranoid bin, heisst das noch lange nicht, dass ich nicht verfolgt werde!
lunar

@Dauerbaustelle: Der OP schreibt nicht die Bibliothek neu, sondern nur das Frontend, welches aus dem XML-Schema der Konfiguration eine nutzbare Klasse erzeugt. Wie gesagt, in C++-Programmen ist das ein sinnvoller Ansatz, in Python eher nicht.

"Mit der Anzeige verbinden" meint, die Werte aus der Konfigurationsdatei in der Oberfläche anzuzeigen, und die Konfigurationsdatei mit den Werten aus der Oberfläche zu aktualisieren, wenn der Benutzer auf "Ok" oder "Anwenden" klickt. Das schließt auch Trivia wie die Deaktivierung des "Anwenden"-Knopfs, wenn die Oberfläche gegenüber der Konfigurationsdatei keine Änderungen aufweist, ein.

@Jerry: Bei „größeren Sachen“ kann auch die XML-Beschreibung der Konfiguration über 100 Zeilen lang sein, und XML ist nun auch nicht sonderlich komfortabel. In C++ wird XML nur deswegen verwendet, weil C++ selbst noch mehr syntaktischen Overhead hat, und oft genug auch einfach zu statisch ist, um bestimmte Dinge kurz und prägnant zu formulieren. Wenn die XML-Beschreibung der Konfiguration kürzer ist als eine Python-Implementierung der Konfigurationsklasse, dann liegt das – mit Verlaub – eher an Deinen Pythonkenntnissen als an der Kürze und Prägnanz von XML.

Müsste ich ein KConfigSkeleton in Python befüllen, würde ich jedenfalls einfach ein Wörterbuch mit allen Elementen und bisschen Quelltext zum Erzeugen der Item-Objekte aus diesem Wörterbuch schreiben (ungetestetes Beispiel), bevor ich auf die Idee käme, KCFG zu bemühen, sonderlich wenn ich auch noch eine Quelltextgenerator bräuchte, um das Schema dann zu benutzen. Das ist nicht viel länger als die XML-Beschreibung, und erlaubt mir, das Konfigurationsschema schon knapp in Form eines Python-Wörterbuchs zu formulieren, anstatt mit XML zu erschlagen.
lunar

Wie versprochen eine kurze Erklärung, wie man die Konfiguration ohne KConfigXT verwalten kann.Die zentrale Funktionalität befindet sich in der Klasse ConfigurationWidgetMixin. Eine Anwendung des Mixins findet sich in TouchpadManagementWidget. Die Konfiguration wird in dieser Anwendung in Objekten abgelegt, die dieselbe Schnittstelle haben wie "dict()" (auf Basis von "collections.MutableMapping").

Letztlich muss ConfigurationWidgetMixin nun nur zwei Dinge tun. Zuerst wäre da das Laden der Konfiguration in die Steuerelemente und umgekehrt. Das geschieht in ".load_configuration()" und ".apply_configuration()". Erstere muss dann bei der Anzeige des Dialogs aufgerufen werden, zweitere dient als Slot für den "Anwenden"-Knopf des Dialogs. Diese sind im Konfigurationsfenster nicht enthalten, damit es auch an Stellen genutzt werden kann, an dem diese Knöpfe von außen vorgegeben werden (prominentes Beispiel sind Module für die Systemeinstellungen von KDE).

In diesen Methoden wird zuerst "._find_configuration_widgets()" aufgerufen, um alle Steuerelemente zu finden, die einem Konfigurationsschlüssel zugeordnet sind. Diese Zuordnung wird genau wie in KConfigXT über ein bestimmtes Präfix im Objektnamen hergestellt, wobei der Name minus Präfix dann auch gleich der Konfigurationsschlüssel ist. Anschließend muss der Inhalt dieser Steuerelemente nun mit dem Inhalt des Konfigurationsobjekts aktualisiert werden.

Der Inhalt von Steuerelementen kann man über die Qt-Eigenschaften der Objekte setzen., allerdings stellt sich die Frage, welche Eigenschaft den Inhalt eines Steuerelements enthält, schließlich ist das je nach Steuerelement eine andere (bei Checkboxen beispielsweise "toggled", bei Eingabefeldern dagegen "text"). Man könnte einfach die "user property" heranziehen, die ist dafür eigentlich da, wenn denn alle KDE-Steuerelemente auch eine solche hätten. Natürlich ist das nicht der Fall, also behelfe ich mir mit einer manuellen Abbildung von Klassennamen auf den Namen derjenigen Eigenschaft, welche den Inhalt des Steuerelements trägt (siehe z.B. "TouchpadManagementWidget"). Damit erhält man dann die Eigenschaft, die man setzen muss, um den Inhalt des Steuerelements zu ändern. Dieser Eigenschaft muss ich dann nur den entsprechenden Wert aus dem Konfigurationsobjekt zuweisen, und dabei natürlich zwischen Python- und Qt-Typ konvertieren.

Das Speichern der Konfiguration verläuft nahezu identisch, nur dass eben die Eigenschaft ausgelesen und in einem "dict()" gespeichert wird (siehe "apply_configuration()").

Die zweite Sache, die man bewerkstelligen muss, ist sich zu merken, welche Steuerelemente aktualisiert wurden, und ein Signal auslösen, wenn das der Fall ist. Dazu verbindet man beim Erzeugen des Steuerelements (siehe "._setup()") einfach die Signale der Steuerelemente, die diese auslösen, wenn sich ihr Inhalt ändert, mit einem Slot, der Buch über diejenigen Steuerelemente führt, deren Inhalt vom Inhalt des Konfigurationsobjekts abweicht. Auch hier ist das größte Problem wieder, das richtige Signal für ein Steuerelement zu finden. Man könnte einfach das "notify"-Signal der "user property" heranziehen, aber es gilt wieder das oben gesagte über "user properties", und somit ist die Lösung wieder eine manuelle Abbildung, um das richtige Signal zu finden. Der Slot für diese Änderungssignale ist "._check_for_changes()". Dieser Slot prüft einfach nur, ob der neue Wert des Steuerelements dem Wert in der Konfiguration gleicht. Falls nein, dann wird das Steuerelement zur Liste der geänderten Steuerelemente hinzugefügt, andernfalls entfernt. Ist diese Liste leer, so zeigen alle Steuerelemente den Inhalt der Konfiguration an, und wurden nicht verändert. Zum Schluss löst der Slot noch ein Signal aus, welches dem Dialog dann dazu dient, den "Anwenden"-Knopf entsprechend zu aktivieren oder zu deaktivieren.

Diese Implementierung ist aufgrund der beschriebenen Workarounds für Bugs in KDE und aufgrund der Tatsache, dass sie gegen Version 1 der QVariant-API geschrieben wurde, ein bisschen aufwendiger als nötig.

Der Vorteil dieses Ansatzes ist wie gesagt, dass er auch in reinen Qt-Anwendungen ohne KDE-Abhängigkeiten funktioniert, und das man nicht nur Konfigurationsdaten, sondern eigentlich jedes beliebige Objekt, welches die "Mapping"-Schnittstelle implementiert, so verwalten kann. Die Anwendung macht davon auch Gebrauch, den beide Konfigurationsklassen repräsentieren eigentlich nicht direkt Konfigurationsdateien, sondern vielmehr die aktuellen Zustand bestimmter Objekte. Das ist in dieser Anwendung deswegen nötig, weil der Zustand dieser Objekte bei laufender Anwendung von außen geändert werden kann.

Sollten noch Fragen zum Quelltext bestehen, oder Bedarf nach noch detaillierten Erklärungen, so möge man sich melden.
Antworten