Standard Config. A module to load tooling config from a users project space.
Inspired from frustrations that some tools have a tool.ini, .tool.ini,
setup.cfg, or pyproject.toml. Some allow for global configs, some don't. Some
properly follow the users home directory, others end up in a weird temp
directory. Windows home directory is only more confusing. Some will even
respect the users $XDG_HOME
directory.
This file is for any project that can be configured in plain text such as ini
or toml
and not requiring a .py file. Just name your tool and let users put
config where it makes sense to them, no need to figure out resolution order.
Usage:
from standard_config import load # Retrieve any overrides from the user overrides = {'setting': True} config = load('my_tool', overrides)
Resolution Order
- First global file with a tool key
- First local file with a tool key
- Environment variables prefixed with
TOOL
- Overrides
Tool Specific Ini files
Ini file formats must include a <tool>
key.
[my_tool] setting = True
pyproject.toml
Toml files must include a tool.<tool>
key
[tool.my_tool] setting = True
setup.cfg
setup.cfg files must include a tool:<tool>
key
[tool:my_tool] setting = True
global files to consider
/tool.ini /.tool /.tool.ini /.config/tool.ini /.config/.tool /.config/.tool.ini
local files to consider
- <project_home>/tool.ini
- <project_home>/.tool
- <project_home>/.tool.ini
- <project_home>/pyproject.toml
- <project_home>/setup.cfg
_get_global_path_specs function
Generate a list of standard pathspecs for global config files.
Args: tool (str): name of the tool to configure
_get_global_path_specs source
def _get_global_path_specs(tool: str) -> path_spec_type: """ Generate a list of standard pathspecs for global config files. Args: tool (str): name of the tool to configure """ try: home = Path(os.environ["XDG_HOME"]) except KeyError: home = Path.home() return [ {"path_specs": home / f"{tool}.ini", "ac_parser": "ini", "keys": [tool]}, {"path_specs": home / f".{tool}", "ac_parser": "ini", "keys": [tool]}, {"path_specs": home / f".{tool}.ini", "ac_parser": "ini", "keys": [tool]}, { "path_specs": home / ".config" / f"{tool}.ini", "ac_parser": "ini", "keys": [tool], }, { "path_specs": home / ".config" / f".{tool}", "ac_parser": "ini", "keys": [tool], }, { "path_specs": home / ".config" / f".{tool}.ini", "ac_parser": "ini", "keys": [tool], }, ]
_get_local_path_specs function
Generate a list of standard pathspecs for local, project directory config files.
Args: tool (str): name of the tool to configure
_get_local_path_specs source
def _get_local_path_specs(tool: str, project_home: Union[str, Path]) -> path_spec_type: """ Generate a list of standard pathspecs for local, project directory config files. Args: tool (str): name of the tool to configure """ return [ { "path_specs": Path(project_home) / f"{tool}.ini", "ac_parser": "ini", "keys": [tool], }, { "path_specs": Path(project_home) / f".{tool}", "ac_parser": "ini", "keys": [tool], }, { "path_specs": Path(project_home) / f".{tool}.ini", "ac_parser": "ini", "keys": [tool], }, { "path_specs": Path(project_home) / f"{tool}.yml", "ac_parser": "yaml", "keys": [tool], }, { "path_specs": Path(project_home) / f".{tool}.yml", "ac_parser": "yaml", "keys": [tool], }, { "path_specs": Path(project_home) / f"{tool}.toml", "ac_parser": "toml", "keys": [tool], }, { "path_specs": Path(project_home) / f".{tool}.toml", "ac_parser": "toml", "keys": [tool], }, { "path_specs": Path(project_home) / "pyproject.toml", "ac_parser": "toml", "keys": ["tool", tool], }, { "path_specs": Path(project_home) / "setup.cfg", "ac_parser": "ini", "keys": [f"tool.{tool}"], }, ]
_get_attrs function
Get nested config data from a list of keys.
specifically written for pyproject.toml which needs to get tool
then <tool>
_get_attrs source
def _get_attrs(attrs: list, config: Dict) -> Dict: """Get nested config data from a list of keys. specifically written for pyproject.toml which needs to get `tool` then `<tool>` """ for attr in attrs: config = config[attr] return config
_load_files function
Use anyconfig to load config files stopping at the first one that exists.
config_path_specs (list): a list of pathspecs and keys to load
_load_files source
def _load_files(config_path_specs: path_spec_type) -> Dict: """Use anyconfig to load config files stopping at the first one that exists. config_path_specs (list): a list of pathspecs and keys to load """ for file in config_path_specs: if file["path_specs"].exists(): config = anyconfig.load(**file) else: # ignore missing files continue try: return _get_attrs(file["keys"], config) except KeyError: # ignore incorrect keys continue return {}
_load_env function
Load config from environment variables.
Args: tool (str): name of the tool to configure
_load_env source
def _load_env(tool: str) -> Dict: """Load config from environment variables. Args: tool (str): name of the tool to configure """ vars = [var for var in os.environ if var.startswith(tool.upper())] return { var.lower().strip(tool.lower()).strip("_").strip("-"): os.environ[var] for var in vars }
load function
Load tool config from standard config files.
Resolution Order
- First global file with a tool key
- First local file with a tool key
- Environment variables prefixed with
TOOL
- Overrides
Args: tool (str): name of the tool to configure
load source
def load(tool: str, project_home: Union[Path, str] = ".", overrides: Dict = {}) -> Dict: """Load tool config from standard config files. Resolution Order * First global file with a tool key * First local file with a tool key * Environment variables prefixed with `TOOL` * Overrides Args: tool (str): name of the tool to configure """ global_config = _load_files(_get_global_path_specs(tool)) local_config = _load_files(_get_local_path_specs(tool, project_home)) env_config = _load_env(tool) return {**global_config, **local_config, **env_config, **overrides}