NefariousOctopus NefariousOctopus - 1 month ago 13
Python Question

Python setuptools: Distribute configuration files to OS specific directories

I am having kind of a chicken or the egg problem with Python setuptools.

What I am trying to achieve, is to distribute a configuration file with my pip package (which in itself is perfectly possible with

data_files
parameter in
setup.py
) to OS specific common locations for user configuration files (e.g.
~/.config
on Linux).

I figured out that OS "specificity" can be solved using
appdirs
[1] PyPi package. And there is my problem - the
appdirs
is not guaranteed to be installed when installing my own package since it is a dependency of my package and thus installed after it (promised chicken or the egg :) )

My
setup.py
contains something like this:

from setuptools import setup
from appdirs import AppDirs
...
setup(
...
data_files=[
(AppDirs(name, author).user_config_dir, ['config/myconfig'])
],
...
)


Can this be solved without writing my own version of setuptools (allusion intended ;) )?

[1]: https://pypi.python.org/pypi/appdirs

Answer

As I mentioned in my comment I would recommend distributing a generic copy of your file with your package and then copying it to the user's config dir at runtime if it does not exist.

This shouldn't be very hard and involves:

  1. Using setuptools's package_data (instead of data_files). This places the file where it is accessible at runtime using pkg_resources, in the "correct" location for the specific OS

  2. When the program runs, use appdirs to look for a user-specific, locally installed file.

  3. If it does not exist, use pkg_resources to find the file and copy it to the location provided by appdirs

While I haven't done this, this process should work nicely for multiple OSes and environments and as a bonus, during development too due to how pkg_resources works.

Example setup.py

In setup.py, you should make sure to include your data file for your package using package_data:

setup(
    # ...
    data_files={
        "my_package": [ "my_package.conf.dist" 
    }
    # ...
)

Example app code:

import os.path
import pkg_resources
import appdirs

def main():
    """Your app's main function"""
    config = get_config()
    # ... 
    # ...

def get_config():
    """Read configuration file and return its contents
    """
    cfg_dir = appdirs.user_config_dir('MyApplication')
    cfg_file = os.path.join(cfg_dir, 'my_application.conf')
    if not os.path.isfile(cfg_file):
        create_user_config(cfg_file)
    with open(cfg_file) as f:
        data = f.read()
        # ... probably parse the file contents here ...
        return data

def create_user_config(cfg_file):
    """Create the user's config file

    Note: you can replace the copying of file contents using shutil.copyfile
    """
    source = pkg_resources.resource_stream(__name__, 'my_package.conf.dist')
    with open(cfg_file, 'w') as dest:
        dest.writelines(source)

I hope this clears up the usage of pkg_resources and package_data.

Comments