Skip to content

Instantly share code, notes, and snippets.

@matthewfeickert
Last active October 23, 2023 09:32
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matthewfeickert/3b7d30e408fe4002aac728fc911ced35 to your computer and use it in GitHub Desktop.
Save matthewfeickert/3b7d30e408fe4002aac728fc911ced35 to your computer and use it in GitHub Desktop.
JSON config files with argparse from the CLI example
import json
import argparse
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
matplotlib.rcParams['text.usetex'] = True
# Inspiration came from https://stackoverflow.com/q/3609852/8931942
def main(args):
# Example taken from matplotlib docs
# https://matplotlib.org/2.0.2/examples/lines_bars_and_markers/fill_demo.html
x = np.linspace(0, 1, 500)
y = np.sin(4 * np.pi * x) * np.exp(-5 * x)
fig, ax = plt.subplots()
ax.fill(x, y, zorder=10)
ax.grid(True, zorder=5)
if hasattr(args, 'plot_title'):
plt.title(args.plot_title)
if hasattr(args, 'x_axis_title'):
plt.xlabel(args.x_axis_title)
if hasattr(args, 'y_axis_title'):
plt.ylabel(args.y_axis_title)
if hasattr(args, 'plot_name'):
# extensions = ['.pdf', '.png', '.svg', '.jpg']
# writing SVG is slow
extensions = ['.pdf', '.png', '.jpg']
for extension in extensions:
plt.savefig(args.plot_name + extension)
if __name__ == '__main__':
cli_parser = argparse.ArgumentParser(
description='configuration arguments provided at run time from the CLI'
)
cli_parser.add_argument(
'-c',
'--config_file',
dest='config_file',
type=str,
default=None,
help='config file',
)
cli_parser.add_argument(
'-x',
'--x_axis_title',
dest='x_axis_title',
type=str,
default='',
help='x-axis name',
)
args, unknown = cli_parser.parse_known_args()
parser = argparse.ArgumentParser(parents=[cli_parser], add_help=False)
if args.config_file is not None:
if '.json' in args.config_file:
# The escaping of "\t" in the config file is necesarry as
# otherwise Python will try to treat is as the string escape
# sequence for ASCII Horizontal Tab when it encounters it
# during json.load
config = json.load(open(args.config_file))
parser.set_defaults(**config)
[
parser.add_argument(arg)
for arg in [arg for arg in unknown if arg.startswith('--')]
if arg.split('--')[-1] in config
]
args = parser.parse_args()
main(args)
{
"plot_title": "$\\theta_2$~\\textrm{spectrum for various solar neutrino energies}",
"x_axis_title": "\\textrm{x-axis title}",
"y_axis_title": "\\textrm{y-axis title}",
"plot_name": "test_plot"
}
[tool.black]
line-length = 88
py36 = false # Python 2 is still supported
skip-string-normalization = true
skip-numeric-underscore-normalization = true # Python 2 is still supported
include = '\.pyi?$'
exclude = '''
/(
\.git
)/
'''
#!/usr/bin/env bash
# https://jvns.ca/blog/2017/03/26/bash-quirks/
set -eu
function clean {
rm *.jpg *.pdf *.svg *.png
}
function main() {
while [[ $# -gt 0 ]]; do
if [[ "$1" = "clean" ]]; then
clean
exit 0
fi
done
python3 config_example.py -c config_file.json
# python3 config_example.py -c config_file.json --x_axis_title "hello" --y_axis_title "there"
}
# https://stackoverflow.com/a/16159057/8931942
main "$@" || exit 1
@BereGabor
Copy link

I found your repo and it inspired me when i looking for solutions to load config from json. I create the solution in other way, but your code helped me to find this way, what i would like to share with you:

Create argparse custom action:

DEFAULT_CONFIG = {
    "param1": 10,
    "param2": 5
}

class LoadConfigJsonFile(argparse.Action):

    @staticmethod
    def setup_config(namespace, config):
        for key, value in config.items():
            setattr(namespace, key, value)

    @staticmethod
    def load_config_from_file(namespace, file_name):
        config = dict(DEFAULT_CONFIG)
        # try to read json object
        try:
            with open(file_name, "r") as json_file:
                input_conf = json.load(json_file)
                config.update(input_conf)
                LoadConfigJsonFile.setup_config(namespace, config)
        except Exception as ex:
            raise argparse.ArgumentTypeError(
                "file:{0} is not a valid json file. Read error: {1}".format(file_name, ex))

    def __call__(self, parser, namespace, values, option_string=None):
        file_name = values
        if not os.path.exists(file_name):
            raise argparse.ArgumentTypeError("file:{0} is not exists".format(file_name))
        if os.access(file_name, os.R_OK):
            self.load_config_from_file(namespace, file_name)
            setattr(namespace, self.dest, file_name)
        else:
            raise argparse.ArgumentTypeError("file:{0} is not a readable file".format(file_name))

Add config param to parser:
parser.add_argument('--config_file', action=LoadConfigJsonFile, default="-")
Needed set defaults when missed config_file param

        if self.args.config_file == "-":
            LoadConfigJsonFile.setup_config(self.args, DEFAULT_CONFIG)

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