Skip to content

Instantly share code, notes, and snippets.

@wrouesnel
Last active May 23, 2021 12:52
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save wrouesnel/72c237b6e4d76c214a28 to your computer and use it in GitHub Desktop.
Save wrouesnel/72c237b6e4d76c214a28 to your computer and use it in GitHub Desktop.
Python argparse with config file fallback. This is great for scripting up daemon-like tools (note PyDev template syntax - replace as needed)
#!/usr/bin/env python
# encoding: utf-8
'''
${module}
'''
import sys
import os
from os import path
import appdirs
import argparse
import logging
import logging.config
import ConfigParser
LOGGING_CONFIG_FILE='${module}.logging.conf'
CONFIG_FILE='${module}.conf'
CONFIG_LOCATIONS=['/etc',
'/usr/local/etc',
appdirs.user_config_dir('${module}'),
os.curdir]
# Regular argumnent parser configuration goes here
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
def _parse_args():
"""parses and sets up the command line argument system above
with config file parsing."""
global parser, CONFIG_LOCATIONS, CONFIG_FILE, LOGGING_CONFIG_FILE
early_parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False)
early_parser.add_argument('--python-debug', help='connect to a python debugging server',
action='store_true')
early_parser.add_argument('--config', dest='config',
help='change default configuration location',
default=None)
early_parser.add_argument('--logging-config', dest='logging_config',
help='change default log file configuration',
default=None)
args,remainder_argv = early_parser.parse_known_args()
# Configure logging as early as possible
log_locations = [ path.join(dir, LOGGING_CONFIG_FILE) \
for dir in reversed(CONFIG_LOCATIONS) ]
if args.logging_config:
log_locations.insert(0, args.logging_config)
loggingConfig = None
for p in log_locations:
if path.exists(p):
loggingConfig = p
if loggingConfig:
logging.config.fileConfig(loggingConfig, disable_existing_loggers=True)
logging.info('Loaded logging config from {}'.format(loggingConfig))
if args.python_debug:
import pydevd
pydevd.settrace(suspend=False)
logging.info('PyDev debugging activated')
# Override config file defaults if explicitly requested
if args.config:
CONFIG_LOCATIONS=[ args.config ]
cp = ConfigParser.SafeConfigParser()
cp.read([ path.join(confpath, CONFIG_FILE) \
for confpath in CONFIG_LOCATIONS ])
# Load config file sections as settings for argparse groups
if cp is not None:
for g in parser._action_groups:
# Optional args are general args
section = None
if g.title == 'optional arguments':
if 'general' in cp._sections:
section = cp._sections['general']
elif g.title in cp._sections:
section = cp._sections[g.title]
if section is None:
continue
for action in g._actions:
for option_string in action.option_strings:
if option_string.startswith('--'):
if option_string[2:] in section:
if action.nargs is not None:
if action.nargs == argparse.ZERO_OR_MORE or \
action.nargs == argparse.ONE_OR_MORE:
val = section[option_string[2:]].split()
else:
val = section[option_string[2:]]
else:
val = section[option_string[2:]]
if action.type is not None:
if hasattr(val, '__iter__'):
action.default = map(action.type, val)
else:
action.default = action.type(val)
else:
action.default = val
args = parser.parse_args(remainder_argv)
return args
def main():
args = _parse_args()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment