Last active
April 21, 2024 08:13
-
-
Save tomschr/10837c2f7286b13d3ebc59e40d943188 to your computer and use it in GitHub Desktop.
Template for an example CLI program with argparse, docopts and logging
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import argparse | |
import logging | |
from logging.config import dictConfig | |
import sys | |
__version__ = "0.2.0" | |
__author__ = "Tux Penguin <[email protected]>" | |
#: The logger name; can also set to "__name__" | |
LOGGERNAME = "examplelog" | |
#: The dictionary, passed to :class:`logging.config.dictConfig`, | |
#: is used to setup your logging formatters, handlers, and loggers | |
#: For details, see https://docs.python.org/3.4/library/logging.config.html#configuration-dictionary-schema | |
DEFAULT_LOGGING_DICT = { | |
'version': 1, | |
'disable_existing_loggers': True, | |
'formatters': { | |
'standard': {'format': '[%(levelname)s] %(funcName)s: %(message)s'}, | |
'file': {'format': '[%(levelname)s] %(asctime)s (%(funcName)s): %(message)s', | |
#: Depending on your wanted precision, disable this line | |
'datefmt': '%Y-%m-%d %H:%M:%S', | |
}, | |
}, | |
'handlers': { | |
'console': { | |
'level': 'NOTSET', # will be set later | |
'formatter': 'standard', | |
'class': 'logging.StreamHandler', | |
}, | |
'fh': { | |
'level': 'DEBUG', # we want all in the log file | |
# Change the formatting here, if you want a different output in your log file | |
'formatter': 'file', | |
'class': 'logging.FileHandler', | |
'filename': '/tmp/log.txt', | |
'mode': 'w', # use "a" if you want to append log output or remove this lien | |
}, | |
}, | |
'loggers': { | |
LOGGERNAME: { | |
'handlers': ['console', 'fh'], # Remove "fh" if you don't want log files | |
'level': 'INFO', | |
# 'propagate': True | |
}, | |
# Set the root logger's log level: | |
'': { | |
'level': 'NOTSET', | |
} | |
} | |
} | |
#: Map verbosity level (int) to log level | |
LOGLEVELS = {None: logging.WARNING, # 0 | |
0: logging.ERROR, | |
1: logging.WARNING, | |
2: logging.INFO, | |
3: logging.DEBUG, | |
} | |
#: Change root logger level from WARNING (default) to NOTSET | |
#: in order for all messages to be delegated. | |
logging.getLogger().setLevel(logging.NOTSET) | |
#: Instantiate our logger | |
log = logging.getLogger(LOGGERNAME) | |
def parsecli(cliargs=None) -> argparse.Namespace: | |
"""Parse CLI with :class:`argparse.ArgumentParser` and return parsed result | |
:param cliargs: Arguments to parse or None (=use sys.argv) | |
:return: parsed CLI result | |
""" | |
parser = argparse.ArgumentParser(description=__doc__, | |
epilog="Version %s written by %s " % (__version__, __author__) | |
) | |
parser.add_argument('-v', '--verbose', | |
action='count', | |
default=0, | |
help="increase verbosity level") | |
parser.add_argument('--version', | |
action='version', | |
version='%(prog)s ' + __version__ | |
) | |
parser.add_argument("DIR", | |
help="Searches the directory for files" | |
) | |
args = parser.parse_args(args=cliargs) | |
# Setup logging and the log level according to the "-v" option | |
dictConfig(DEFAULT_LOGGING_DICT) | |
# Setup logging and the log level according to the "-v" option | |
loglevel = LOGLEVELS.get(args.verbose, logging.DEBUG) | |
# Set console logger to the requested log level | |
for handler in log.handlers: | |
if handler.name == "console": | |
handler.setLevel(loglevel) | |
log.debug("CLI result: %s", args) | |
return args | |
def main(cliargs=None) -> int: | |
"""Entry point for the application script | |
:param cliargs: Arguments to parse or None (=use :class:`sys.argv`) | |
:return: error code | |
""" | |
try: | |
args = parsecli(cliargs) | |
# do some useful things here... | |
# If everything was good, return without error: | |
log.info("I'm an info message") | |
log.debug("I'm a debug message.") | |
log.warning("I'm a warning message.") | |
log.error("I'm an error message.") | |
log.fatal("I'm a really fatal massage!") | |
return 0 | |
# List possible exceptions here and return error codes | |
except Exception as error: # FIXME: add a more specific exception here! | |
log.fatal(error) | |
# Use whatever return code is appropriate for your specific exception | |
return 10 | |
if __name__ == "__main__": | |
sys.exit(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Does some fancy stuff | |
Usage: | |
{proc} [-h | --help] | |
{proc} [-v ...] INPUT OUTPUT | |
Options: | |
-h, --help Shows this help | |
-v Raise verbosity level | |
Arguments: | |
DIR Searches the directory for files | |
""" | |
__version__ = "0.1.0" | |
__author__ = "Tux Penguin <[email protected]>" | |
from docopt import docopt | |
import logging | |
from logging.config import dictConfig | |
from os.path import basename | |
import sys | |
#: The dictionary, passed to :class:`logging.config.dictConfig`, | |
#: is used to setup your logging formatters, handlers, and loggers | |
#: For details, see https://docs.python.org/3.4/library/logging.config.html#configuration-dictionary-schema | |
DEFAULT_LOGGING_DICT = { | |
'version': 1, | |
'disable_existing_loggers': True, | |
'formatters': { | |
'standard': {'format': '[%(levelname)s] %(funcName)s: %(message)s'}, | |
}, | |
'handlers': { | |
'console': { | |
'level': 'NOTSET', # will be set later | |
'formatter': 'standard', | |
'class': 'logging.StreamHandler', | |
}, | |
'fh': { | |
'level': 'DEBUG', # we want all in the log file | |
# Change the formatting here, if you want a different output in your log file | |
'formatter': 'standard', | |
'class': 'logging.FileHandler', | |
'filename': '/tmp/log.txt', | |
'mode': 'w', # use "a" if you want to append log output | |
}, | |
}, | |
'loggers': { | |
__name__: { | |
'handlers': ['console', 'fh'], # Remove "fh" if you don't want log files | |
'level': 'INFO', | |
# 'propagate': True | |
}, | |
# Set the root logger's log level: | |
'': { | |
'level': 'NOTSET', | |
} | |
} | |
} | |
#: Map verbosity level (int) to log level | |
LOGLEVELS = {None: logging.WARNING, # 0 | |
0: logging.ERROR, | |
1: logging.WARNING, | |
2: logging.INFO, | |
3: logging.DEBUG, | |
} | |
#: Change root logger level from WARNING (default) to NOTSET | |
#: in order for all messages to be delegated. | |
logging.getLogger().setLevel(logging.NOTSET) | |
#: Instantiate our logger | |
log = logging.getLogger(__name__) | |
#: Use best practice from Hitchhiker's Guide | |
#: see https://docs.python-guide.org/writing/logging/#logging-in-a-library | |
log.addHandler(logging.NullHandler()) | |
def parsecli(cliargs=None) -> dict: | |
"""Parse CLI arguments with docopt | |
:param list cliargs: List of commandline arguments or None (=use sys.argv) | |
:type clicargs: list | None | |
:return: result dictionary from docopt | |
""" | |
version = f"{__name__} {__version__}" | |
args = docopt(__doc__.format(proc=basename(sys.argv[0])), | |
argv=cliargs, | |
version=version) | |
# Setup logging and the log level according to the "-v" option | |
dictConfig(DEFAULT_LOGGING_DICT) | |
# Setup logging and the log level according to the "-v" option | |
loglevel = LOGLEVELS.get(args['-v'], logging.DEBUG) | |
# Set console logger to the requested log level | |
for handler in log.handlers: | |
if handler.name == "console": | |
handler.setLevel(loglevel) | |
log.debug("CLI result: %s", args) | |
return args | |
def main(cliargs=None) -> int: | |
"""Entry point for the application script | |
:param list cliargs: Arguments to parse or None (=use sys.argv) | |
:type clicargs: list | None | |
:return: error codes | |
""" | |
try: | |
args = parsecli(cliargs) | |
# do some useful things here... | |
# If everything was good, return without error: | |
log.info("I'm an info message") | |
log.debug("I'm a debug message.") | |
log.warning("I'm a warning message.") | |
log.error("I'm an error message.") | |
log.fatal("I'm a really fatal massage!") | |
return 0 | |
# List possible exceptions here and turn exceptions into return codes | |
except Exception as error: # FIXME: add a more specific exception here! | |
log.fatal(error) | |
# Use whatever return code is appropriate for your specific exception | |
return 10 | |
if __name__ == "__main__": | |
sys.exit(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment