1

I was looking at couple of the python projects. Almost every good python project usually have a app.conf or app.ini file for their configuration management. After that they use something like configparser.py module to extract configuration information from the app.conf file. In the end they somehow have to use the values in their .py module.

Let's go to an example

app.conf

[BREED]
beagle=dog
ragdoll=cat
deilenaar=rabbit
cockatoo=parrot

app.py

#!/usr/bin/env python
#-*- coding: utf-8 -*-

import configparser

config = configparser.ConfigParser()
config.read('app.conf')
print(config['BREED']['cockatoo']) # prints parrot

Then why not to use a dictionary and use that dictionary instead something like the following.

#!usr/bin/env python
# -*- coding: utf-8 -*-

config = {'BREED': {'beagle': 'dog',
                    'ragdoll': 'cat',
                    'deilenaar': 'rabbit',
                    'cockatoo': 'parrot'}}
print(config['BREED']['cockatoo']) # prints parrot

If we need to use config globally then we will put the variable in __init__.py in the root of the package. In that way we don't have to read the app.conf file anymore.

I believe there is a really good reason to use app.conf file. So what is that reason ?

Abhisek
  • 4,610
  • 3
  • 17
  • 27
  • 1
    The reuse of a configuration file outside of a Python context to start; decoupling of executable code from user-configurable configuration makes it possible for end users to manipulate without modifying source code, also enable the relocation of these configuration files as the program can read from multiple locations. – metatoaster Dec 08 '16 at 05:31
  • I already mentioned that if our app needed to use the configuration information from multiple location then we would put the configuration information in a separate `.py` file and about the end user thing: why an end user need to change a source code or config file for that matter ? – Abhisek Dec 08 '16 at 05:39
  • Note: Django does use Python config files, it's just not seen as a good decision for the reasons mentioned. – mVChr Dec 08 '16 at 05:39

3 Answers3

1

When reading from a configuration file, you the programmer have the choice of how to handle errors in the formatting of the file. Can't parse a particular line? Assign a default value. Want to be forgiving and accept strings without a closing quote? Go for it.

A Python module, on the other hand, is all or nothing. If the syntax of the file isn't perfect, you get nothing

kindall
  • 178,883
  • 35
  • 278
  • 309
  • ok, that would be the case but isn't it bad that accepting strings without a closing quote ? – Abhisek Dec 08 '16 at 05:56
  • If you define a string as beginning with a quote and closing either with a matching quote or a newline... nope. It would be a rare use case I'd imagine, but I've seen weirder file formats... – kindall Dec 08 '16 at 06:30
  • It would be helpful If you give an example with a small code as I'm not getting the clear picture what you're trying to say. – Abhisek Dec 08 '16 at 08:17
1

Security.

I agree with your points, but security can't cope with config.py, except maybe with a wrapper, which in this case can be called config_parser.py

Assume a config.py is imported all over your code.

from config.dir import config

This line executes config.py!

This means any attacker can do anything there, probably with elevated permissions.

That's enough reason for me not to do it.


Here is my boilerplate config

class ConfigProvider(object):
    _config = None

    @staticmethod
    def get_config():
        if ConfigProvider._config is None:
            ConfigProvider._config = ConfigParser(config_path).parse()
        return ConfigProvider._config


class ConfigParser(object):
    def __init__(self, config_path):
        self._config_path = config_path

    def parse(self):
        parsed = None
        with open(self._config_path, 'r') as stream:
            try:
                # https://stackoverflow.com/questions/2352181/how-to-use-a-dot-to-access-members-of-dictionary
                parsed = Map(yaml.safe_load(stream))
            except yaml.YAMLError as exc:
                traceback.print_exc(exc)

        return parsed


class Map(dict):
    # https://stackoverflow.com/questions/2352181/how-to-use-a-dot-to-access-members-of-dictionary
    """
    Example:
    m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
    """
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.items():
                    self[k] = v
                    if isinstance(v, dict):
                        self[k] = Map(v)

        if kwargs:
            # for python 3 use kwargs.items()
            for k, v in kwargs.items():
                self[k] = v
                if isinstance(v, dict):
                    self[k] = Map(v)

    def __getattr__(self, attr):
        return self.get(attr)

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __setitem__(self, key, value):
        super(Map, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(Map, self).__delitem__(key)
        del self.__dict__[key]

    def __getstate__(self):  # required for successful pickling
        return self.__dict__

    def __setstate__(self, d):  # required for successful pickling
        self.__dict__.update(d)


config = ConfigProvider.get_config()

users always only need a single line to get their config.

Less convenient than a .py of course, but security is important.

Notice the safe_load on yaml.

Gulzar
  • 23,452
  • 27
  • 113
  • 201
0

So a user does not have to edit the source code to change the values in the .conf file. Also allows settings to be saved to the .conf and read later without editing the source file.

Thmei Esi
  • 434
  • 2
  • 9