Wann sollte eine Konfigurationsdatei erstellt werden?

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.
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Für mein Projekt möchte ich jetzt mit einem ``.conf``-File arbeiten. Dazu "missbrauche" ich das entsprechende Tool aus der Standardbibliothek:

Code: Alles auswählen

try:
    import configparser
except ImportError:
    # Python 2.x
    import ConfigParser as configparser
[...]
def read_configuration(filename='~/.launchit/launchit.conf'):
    filename = os.path.expanduser(filename)
    if not os.path.isfile(filename):
        return None
    with open(filename) as config_file:
        # Fake a section to allow "flat" parsing
        fake_file = StringIO.StringIO('[dummy]\n' + config_file.read())
    parser = configparser.SafeConfigParser()
    parser.readfp(fake_file)
    return dict(parser.items('dummy'))
Eine ``launchit.conf`` kann dann z.B. so aussehen (viel gibt's noch nicht):

Code: Alles auswählen

encoding: utf-8
helper: gnome-open
Das Ergebnis nach dem Parsen sieht dann so aus:

Code: Alles auswählen

In [1]: import core

In [2]: core.read_configuration()
Out[2]: {'encoding': 'utf-8', 'helper': 'gnome-open'}
Nun zu meiner Frage. Wann sollte so eine Datei eigentlich angelegt werden: Beim ersten Aufruf des Programms? Beim Ausführen der ``setup.py`` (also Installation)? Ich bin bisher offen gestanden noch nicht bis an den Punkt beim Programmieren gekommen, wo ein Projekt sowas gebraucht hätte.
lunar

@snafu: Bei der Installation legt man allenfalls systemweite Konfigurationsdateien an, aber nicht die einzelner Nutzer. Wie sollte das auch funktionieren?

Im Regelfall legen Programme ihre Konfigurationsdateien im Benutzerverzeichnis gar nicht selbst an, es sei denn, es gibt im Programm selbst die Möglichkeit, die Konfigurationseinstellungen zu bearbeiten.
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wird das Verzeichnis denn angelegt und geschieht dies alles durch den Benutzer?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Auch Benutzer, es sei denn das Programm hortet da selbst Daten.

Die FreeDesktop-Standards `~/.config` und `~/.local` kennst du nicht? Oder ist das eine bewusste Entscheidung?
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

snafu hat geschrieben:Wird das Verzeichnis denn angelegt und geschieht dies alles durch den Benutzer?
Sollte das Programm die Konfiguration nicht ändern, in wessen Fall man sich an die Freedesktop Standards halten sollte, macht dass alles der Nutzer. Wer auch sonst?
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ah, mkay. ``~/.config`` ist wohl der bessere Ort dafür.

Eine Frage noch. Wie geht man üblicherweise mit falsch definierten Schlüsseln um (z.B. Buchstabendreher): Ignorieren (und damit natürlich auch den gesetzten Wert) oder Fehler werfen?
lunar

@snafu: Im Falle von Schlüsseln bleibt Dir gar keine andere sinnvolle Möglichkeit als Ignorieren. Oder möchtest Du wirklich alle möglichen Kombinationen von Schreibfehlern für alle möglichen Schlüssel prüfen?!

Bevor Du von "~/.config/" lädst, solltest Du in jedem Fall noch "$XDG_CONFIG_HOME" auswerten. Das führt dann in etwa zu folgender Funktion zur Bestimmung des Konfigurationsverzeichnisses:

Code: Alles auswählen

def get_configuration_directory():
    xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
    if not xdg_config_home:
        xdg_config_home = os.path.expandvars(os.path.join('$HOME', '.config'))
    return os.path.join(xdg_config_home, 'launchit')
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich benutze sowieso inzwischen PyXDG als Abhängigkeit, um die Icon von Menüeinträgen auszuwerten (icongetter.py). Die Bibliothek bietet auch irgendwo Funktionen, die exakt das tun dürften, was du gezeigt hast. Hätte ich mich eigentlich auch vorher mal mit beschäftigen können. :mrgreen:

Das mit den Tippfehlern war übrigens nicht so gemeint, dass ich da irgendwelche Verbesserungen durch Erraten des richtigen Eintrags machen will. Es ging nur darum, ob man meckert oder ob man es einfach überspringt.
lunar

@snafu: Die meisten Programme ignorieren unbekannte Schlüssel einfach, im Zweifelsfall kannst Du natürlich eine Art Warnung ausgeben.

pyxdg sollte die Base Directory Spec, in der das Konfigurationsverzeichnis definiert wird, implementieren, denn dieser Standard ist Voraussetzung für die meisten anderen Freedesktop-Standards. Ob ich allerdings pyxdg nutzen würde, weiß ich nicht. Die Bibliothek scheint mir von eher schlechter Qualität zu sein.

Eine Anmerkung zu icongetter.py. Nutze entweder "from launchit._stringutils import foo" oder "from ._stringutils import foo" (letzteres benötigt "absolute_imports" aus "__future__"). Diese Formen sind im Gegensatz zur aktuellen Form völlig eindeutig, und kompatibel zu Python 3.
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ja, die Bibliothek ist kaum dokumentiert, der Code ist an einigen Stellen auch nicht so prickelnd und naja, was man zum Nutzen bestimmter Funktionalität für Verrenkungen machen muss, siehst du ja an meinem Modul. Auf der anderen Seite scheint mir das unter Linux eine gute umgebungsübergreifende Möglichkeit zu sein, um Dinge wie Menüeinträge auszulesen. Die Bibliothek bietet ja noch einiges mehr, wie man an diesen Beispielen sehen kann. Da es wohl keine Alternative in diesem Umfang gibt und sie vieles zu bieten scheint, was ich für spätere Launchit-Versionen noch benötigen könnte ("erweiterte" Mimetype-Erkennung, Auslesen der Beschreibung von Menüeinträgen usw.) würde ich schon gern dabei bleiben.
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Noch eine Frage sozusagen zu den Prioritäten: Ich habe ja über die Module verteilt diverse Konstanten definiert. Die sollen eigentlich auch erhalten bleiben. Wenn nun ein bestimmter Wert durch vorhandenen Schlüssel in der Konfigurationsdatei definiert wird, dann sollte natürlich dieser die höhere Priorität besitzen. Was ist nun besser: Inhalt der Konstanten bestehen lassen und erst in der Funktion entscheiden, ob der Wert aus der Konstanten oder aus der ``.conf``-Datei (eben bei vorhandenem Schlüssel) verwendet wird oder sollten die Konstanten bei der Initialisierung (d.h. Modul-Import) ggf überschrieben werden (natürlich würde dies in der Doku erwähnt werden)?
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Du solltest default Werte in der Konfiguration verwenden und keine Konstanten.
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hm, bei C-Programmen, die zur Laufzeit nicht mehr geändert werden können, verstehe ich da ja, aber ist das bei Python genau so? Sollte der Anwender also alles über Anlegen einer Konfigurationsdatei regeln müssen? Wobei, bei genauerem Überlegen macht das durchaus Sinn. Ist sauberer als den Quelltext zu verändern. Und gerade das ist ja eigentlich auch den entscheidende Grund, wieso man solche Dateien verwendet. Hm, bin glaube ich überzeugt. ^^
lunar

@snafu: Der Import eines Moduls sollte sowieso keine Seiteneffekte haben. Folglich ist es auch nicht empfehlenswert, eine Konfigurationsdatei beim Import zu laden. Du solltest allenfalls die Standardwerte für die Konfiguration als Konstanten definieren. Falls die Konstanten allerdings nur als Standardwerte dienen, ist es fast sinnvoller, eine einzige Konstante zu definieren, welche die Standardkonfiguration als Wörterbuch enthalten.

Beim Laden kannst Du dann etwas wie "return dict(DEFAULT_CONFIGURATION).update(loaded_configuration)" nutzen, um die Standardkonfiguration mit den in der Konfigurationsdatei gespeicherten Werte zu aktualisieren.

Vielleicht ist an dieser Stelle auch "configobj" einen Blick wert. Dieses Modul erlaubt Dir, ein Konfigurationsschema zu erstellen, welches für jeden Konfigurationsschlüssel Typen und Standardwerte definiert. Die Konfiguration kannst Du dann gegen dieses Schema validieren, wobei die Werte automatisch in die angegebenen Typen konvertiert werden. Im Falle fehlender Werte werden automatisch die Standardwerte übernommen. Außerdem hat dieses Modul die schönere API als "configparser".
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Eine zusätzliche Abhängigkeit wegen des Parsens einer Konfigurationsdatei möchte ich eigentlich vermeiden, wobei ich folgendes schon echt hart finde:
Configuration files may include comments, prefixed by specific characters (# and ; ). Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names. In the latter case, they need to be preceded by a whitespace character to be recognized as a comment. (For backwards compatibility, only ; starts an inline comment, while # does not.)
Also, ich weiß ja nicht, was sich der Autor des Moduls so gedacht bzw nicht gedacht hat: Zunächst einmal ist die Unterscheidung zwischen `RawConfigParser` und `ConfigParser` etwas, das man eigentlich auch als Option bei der Initialisierung hätte implementieren können. Dann gibt mit `SafeConfigParser` noch etwas, das `ConfigParser` gegenüber bevorzugt werden soll, ohne dass eindeutig gesagt wird, worin die Verbesserung bei der Interpolation genau besteht. Und zu guter Letzt noch das o.g. "Feature", welches IMHO entgegen dem läuft, was man als Anwender erwarten würde. Dafür gibt es dann aber keine neue Klasse. :roll:
BlackJack

@snafu: Die Aufregung über das Feature verstehe ich nicht. Die INI-Dateien aus der Windowswelt benutzen ';' als Kommentarzeichen. '#' ist eine Erweiterung. Wenn also ein Benutzer das Originalformat gewohnt ist und eine Datei mit der Zeile ``ham=spam # eggs`` parst, wäre der sehr überrascht wenn ein Teil seines Wertes plötzlich als Kommentar interpretiert wird und nicht auslesbar ist.
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Nagut, das wusste ich nicht. Dann macht es wohl Sinn, auch wenn es mir ungewohnt erscheint.
deets

Ein Beispiel sind die Konfigurationsdateien von Paster - da werden setuptools-entry-points angegeben, in der Form

app = SomeEgg#entry_point_name
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Habe jetzt mein eigenes kleines Format entwickelt: https://github.com/seblin/launchit/blob ... ettings.py
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

lunar hat geschrieben:Eine Anmerkung zu icongetter.py. Nutze entweder "from launchit._stringutils import foo" oder "from ._stringutils import foo" (letzteres benötigt "absolute_imports" aus "__future__"). Diese Formen sind im Gegensatz zur aktuellen Form völlig eindeutig, und kompatibel zu Python 3.
Diese Möglichkeit ist mir bekannt. Ich hatte sie allerdings verworfen, weil bei einem direkten Import von bspw. `core` dann ein Fehler geworfen wird ("Attempted relative import in non-package"). Sollte man etwa davon ausgehen, dass der Benutzer so etwas nicht tun wird? Ich empfinde es ja eher als einschränkend.
Antworten