Présentation de Dotconf

Dotconf est un parseur de fichier de configuration avancé pour Python, et accessoirement aussi, un projet personnel sur lequel je travaille depuis quelques semaines.

Il existe un certain nombre de parseur de fichier configuration en Python, ConfigParser, qui est un package intégré à la bibliothèque standard du langage, ConfigObj, qui reprend la syntaxe de ConfigParser (celle des fichiers .ini en fait) et qui apporte les sections imbriquées et la validation à partir d'un schéma, YAML, JSON, ou XML qui sont plus des formats de sérialisation que des formats de configuration. Aucun de ces formats ne me convient vraiment, certains sont trop simples (ConfigParser), ont des syntaxes bizarres (ConfigObj pour l'imbrication), d'autres ont une syntaxe trop restrictive pour de la configuration (JSON), les autres ne me semblent pas assez adaptés à la lecture ou l'écriture par les humains.

Mon format idéal devait rassembler à ceci :

  • Une syntaxe simple, claire, et peu ambigüe
  • Un typage au sein même de la syntaxe ("42" != 42) et un nombre correct de types primitifs (nombre, booléen, chaine de caractères, liste)
  • Des sections imbriquables
  • Un système de validation du fichier de configuration

Ne trouvant pas mon bonheur, je me suis attelé à son développement. Ainsi est né Dotconf.

Pour commencer, voici un exemple de configuration possible avec Dotconf pour un serveur web imaginaire :

daemon = yes
pidfile = '/var/run/myapp.pid'
interface = '10.0.10.0:80', '127.0.0.1:8080'
interface_ssl = '0.0.0.0:443'
http_buffer = 8Ki

host 'example.org' {
    path '/' {
        rate_limit = 30
    }
}

host 'protected.example.org' {
    enable_ssl = yes

    path '/files' {
        enable_auth = yes
        user 'foo' {
            password = 'bar'
        }
        user 'bar' {
           password = 'foo'
        }
    }
}

include "/etc/myserver.d/*.conf"

On remarque que :

  • Les chaînes de caractères utilisent les quotes (ou les double quotes, au choix) comme délimiteur, il est donc possible de différencier une chaine contenant un nombre du nombre en lui-même.
  • Les booléens utilisent les mots clé yes et no.
  • Il est possible d'utiliser des unités pour les nombres (8Ki == 8192)
  • Les listes sont définies par plusieurs éléments séparés par des virgules. Les listes peuvent contenir des nombres, chaines, et booléens (les trois types scalaires primitifs), il n'est par contre pas possible d'imbriquer des listes (mon avis est que les listes de listes sont des structures trop complexes pour de la configuration, il est possible de les émuler de manière plus claire avec des sections, par exemple ici avec les users).
  • Une section est un simple nom suivi d'un bloc délimité par des accolades.
  • Une section peut recevoir une liste de valeurs spéciales que j'ai appelé l'argument, cet argument est défini entre le nom de la section et le début du bloc.
  • Il est très simple d'inclure d'autres fichiers de configuration à l'aide de la directive "include".

La fonctionnalité qui reste la plus intéressante selon moi est la validation d'un fichier de configuration selon un schéma. Celle-ci permet de définir simplement les clés, leur contenu, et l'arborescence de sections que doit contenir votre fichier de configuration.

Voici un petit exemple :

from dotconf.schema import many, once
from dotconf.schema.containers import Section, Value
from dotconf.schema.types import Boolean, Integer, Float, String

# Schema definition:

class UserSection(Section):
    password = Value(String())
    _meta = {'repeat': many, 'unique': True}

class PathSection(Section):
    rate_limit = Value(Float(), default=0)
    enable_auth = Value(Boolean(), default=False)
    user = UserSection()

class VirtualHostSection(Section):
    base_path = Value(String())
    enable_ssl = Value(Boolean(), default=False)
    path = PathSection()
    _meta = {'repeat': many, 'unique': True}

class MyWebserverConfiguration(Section):
    daemon = Value(Boolean()default=False)
    pidfile = Value(String(), default=None)
    interface = Value(String(), default='127.0.0.1:80')
    interface_ssl = Value(String(), default='127.0.0.1:443')
    host = VirtualHostSection()
    http_buffer = Value(Float(), default=1024)

Quelques informations :

  • Chaque schéma de section est définie dans une classe, le fichier de configuration étant lui-même une section.
  • Chaque clé possible est un attribut statique de cette classe.
  • Il y a une distinction entre le container et le type de la valeur. Là où le conteneur peut être une valeur (scalaire), une liste, ou une section, le type sera un entier, une chaine ou un booléen.
  • Ça n'est pas forcément visible dans l'exemple, mais il existe des types plus complexes que les Integer, String ou Booleans, par exemple les Url, IPAddress ou Regex.
  • Les sous-sections sont définies dans des classes puis utilisées dans les sections parentes comme n'importe quel autre container.
  • Des méta-informations permettent de définir plusieurs contraintes sur les containers section, comme par exemple si elles doivent être répétées, le schéma de l'argument ou encore si les clés inconnues doivent être gardées.

Il est ensuite possible de parser et valider votre configuration de cette manière :

>>> from dotconf import Dotconf
>>> from myconfschema import MyWebserverConfiguration
>>> parsed_conf = Dotconf(conf, schema=MyWebserverConfiguration)
>>> print 'daemon:', parsed_conf.get('daemon')
daemon: True
>>> for vhost in parsed_conf.subsections('host'):
>>>     print vhost.args[0]
>>>     if vhost.get('enable_ssl'):
>>>         print '  SSL enabled'
>>>     for path in vhost.subsections('path'):
>>>         print '  ' + path.args[0]
>>>         if path.get('enable_auth'):
>>>             print '    Following users can access to this directory:'
>>>             for user in path.subsections('user'):
>>>                 print '     - ' + user.args[0]
>>>
example.org
  /
protected.example.org
  SSL enabled
  /files
    Following users can access to this directory:
      - foo
      - bar

Le tout est disponible sous licence MIT, sur le pipy, il est donc possible de l'installer avec pip :

pip install dotconf

Un paquet est aussi disponible pour Archlinux sur AUR (merci à Sébastien), et le paquet Debian sera disponible dans la prochaine version.

Il est évidemment possible de contribuer, le code source est disponible via ces moyens, et les patches et remarques sont les bienvenues :

Commentaires

Avatar de Yoko
Yoko
le 07 avril 2012 15:48

Salut,

Personnellement j'aime bien XML qui possède pleins d'éditeurs tous plus intéressants les uns que les autres. Je suis surpris que tu n'ai pas implémenté de map/dictionnaire dans ton format (qui est claire et lisible).

Ta syntaxe est proche de ce que propose nginx (http://wiki.nginx.org/FullExample), sauf que tes affectations se font avec des = et pas chez eux et leurs instructions se finissent avec des ; et pas chez toi.

Ca pourrait être intéressant de se rapprocher de ce format pour être un peu plus KISS

Avatar de Antoine
Antoine inaps.org
le 07 avril 2012 16:19

ehlo,

Attention je rejette pas en bloc le XML (ni les autres formats), je le trouve juste inadapté à de la configuration. En fait, je le trouve surtout peu lisible pour un humain sans un éditeur spécialisé, et je pense qu'un bon format de configuration doit être lisible et éditable facilement avec un éditeur standard.

Pour ce qui est des map/dict, ce sont en fait les sections qui jouent ce role.

Je ne pense pas me rapprocher du format d'nginx, c'était effectivement une source d'inspiration (pour les sections), mais le reste ne me plait pas vraiment (les point virgule, et le manque d'opérateur pour les affectations par exemple).

Avatar de sphax3d
sphax3d sphax3d.org
le 04 mai 2012 23:45

Sympa comme projet :) Je comprends mieux ce sur quoi j’ai tenté de t’aider la dernière fois :p

Tu parles du signe = pour les affectations, mais pourquoi ne pas l’avoir utilisé pour les sections ? host = 'example.org' {… ou host 'example.org' = {… par exemple. Bon, oui ça parait un peu bizarre, et je préfère ta syntaxe des sections sans = (sans marqueur d’affectation de sections), et du coup, j’aurais trouvé cohérent de ne pas les utiliser pour les autres affectations :p
Comme dit Yoko, KISS tout ça.
Mais oui, tu fais bien comme tu veux :)

Laisser un commentaire
:
:

Optionnel.

:

Ne sera pas publiée, elle est utile pour les Gravatars et la modération des commentaires.

:

Vous pouvez utiliser ces marqueurs : a, strong, em, pre, blockquote, abbr, acronym, et code. Les sauts de lignes et les liens sont automatiquement convertis.

:

Ce test permet de vérifier que vous n'êtes pas un (salaud de) robot de spam.


J'utilise Escaline 
!