Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Packaging a python app with a config file?

I'm writing a Python CLI app and want to package it. I need the app to have a config file that applies to the entire "installation" (i.e. no matter where the user calls my CLI app from, it needs to read this config file).

I want this to be in the package install directory, not just some arbitrary place on the filesystem if I can avoid it. What's the un-messy way to do this?

like image 713
K4KFH Avatar asked Oct 21 '25 23:10

K4KFH


1 Answers

Sorry to give a "that's not what you want to do"-answer, but I would strongly advice against bundling an editable config file into your package. The reasons being:

  • Any serious os has well-defined standards where user- or system-level configs should go. Putting your app's config there as well is not messy at all.
  • Python packages (read, .wheel files) don't need to be unzipped in order to be runnable, so any os that supports python may chose not to do so on install. If you want to edit a config that is in there, you need to unzip the package first, which is a bit inconvenient.
  • For the same reason, the config can't be found with search-tools. So good luck to your users if they ever forget where it was.
  • Last and least importantly, there is an assumption that an executable is static. In other languages where the code is compiled there is no way for it not to be, but for interpreted languages I'd deem it good style to follow suit.

But the best argument for following standards is usually that you get to use well-written tools that support said standard, in this case appdirs. It can (among other things) find the common config-directory for you, so using it is as simple as this:

from pathlib import Path
from appdirs import site_config_dir
from configparser import ConfigParser


def load_config():
    # .ini is easiest to work with, but .toml is probably better in the long run
    cfg_loc = Path(site_config_dir(appname="my_cli_app", appauthor="K4KFH")) / "config.ini"
    # that's it, that was the magic. the rest of the code here is just for illustration

    if not cfg_loc.exists():
        cfg_loc.parent.mkdir(parents=True, exist_ok=True)
        with open(config_loc) as f:
            f.write(
                "[basic]\n"
                "foo = 1\n"
            )
        print(f"Initialized new default config at {config_loc}.")

    cfg = ConfigParser()
    cfg.read(cfg_loc)
    return cfg

Which, on Windows, will get you this:

>>> cfg = load_config()
Initialized new default config at C:\ProgramData\K4KFH\my_cli_app\config.ini.
>>> cfg["basic"]["foo"]
1

And on debian buster this:

>>> cfg = load_config()
Initialized new default config at /etc/xdg/my_cli_app/config.ini.
>>> cfg["basic"]["foo"]
1
like image 82
Arne Avatar answered Oct 23 '25 13:10

Arne