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 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> 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 pypi, il est donc possible de l’installer avec pip :

1
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 :