Skip to content

Instantly share code, notes, and snippets.

@elmotec
Last active December 1, 2021 17:14
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save elmotec/6008118 to your computer and use it in GitHub Desktop.
Save elmotec/6008118 to your computer and use it in GitHub Desktop.
Minimal python program with logging and argparse.
#!python
"""See main.__doc__"""
import os
import logging
import glob
import unittest
import sys
import click
import click.testing
module = sys.modules["__main__"].__file__
log = logging.getLogger(module)
def process(filename, output):
"""Do something more useful that printing out the file name"""
print(filename)
@click.command()
@click.argument("files", nargs=-1)
@click.option(
"--output",
help="output directory where to write to",
default=os.getenv("TEMP"),
type=click.Path(),
)
@click.option("--verbose", "-v", help="increases verbosity", count=True)
def main(files, output, verbose):
"""This help line will show in the command line --help output"""
logging.root.setLevel(max(3 - verbose, 0) * 10) # impact all loggers
log.debug("log level: %s", log.getEffectiveLevel())
log.debug("output: %s", output)
# credit https://stackoverflow.com/questions/48604754
all_files = []
for argument in files:
log.debug("processing argument %s ...", argument)
# if our shell does not do filename globbing
expanded = list(glob.glob(argument))
if len(expanded) == 0 and "*" not in argument:
raise (click.BadParameter("{}: file not found".format(argument)))
all_files.extend(expanded)
# Actual processing of the files.
for filename in all_files:
log.info("processing file %s ...", filename)
process(filename, output)
if __name__ == "__main__":
logging.basicConfig(
stream=sys.stderr,
level=logging.DEBUG,
format="%(name)s (%(levelname)s): %(message)s",
)
try:
main()
except KeyboardInterrupt:
log.info("%s interrupted", module)
finally:
logging.shutdown()
class BasicCommandLineTest(unittest.TestCase):
"""Tests can be run with python -m unittest <script>"""
def setUp(self):
self.runner = click.testing.CliRunner()
def test_offers_help_for_invalid_option(self):
"""Shows usage when the option is not valid"""
result = self.runner.invoke(main, ["--invalid"])
self.assertEqual(result.exit_code, 2)
self.assertRegex(result.output, r"Try.*--help")
def test_shows_help(self):
"""Makes sure the help is available."""
result = self.runner.invoke(main, ["--help"])
self.assertEqual(result.exit_code, 0)
self.assertRegex(result.output, r"Usage: ")
def test_one_v_for_info_level_logging(self):
"""-v sets logging to info."""
_ = self.runner.invoke(main, ["-v"])
self.assertEqual(log.getEffectiveLevel(), logging.INFO)
def test_two_v_for_info_level_logging(self):
"""-vv sets logging to debug."""
_ = self.runner.invoke(main, ["-vv"])
self.assertEqual(log.getEffectiveLevel(), logging.DEBUG)
@AaronNBrock
Copy link

This doesn't seem to actually write the logs to a file when -o is specified. It only creates the file.

@elmotec
Copy link
Author

elmotec commented Jul 19, 2019

The log is going to stderr. Only the output goes to the file referenced in -o. However, I use click nowadays for command line utilities. Much easier. I will probably delete that gist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment