Git Product home page Git Product logo

configargparse's Introduction

ConfigArgParse

PyPI version Supported Python versions Downloads per week API Documentation

Overview

Applications with more than a handful of user-settable options are best configured through a combination of command line args, config files, hard-coded defaults, and in some cases, environment variables.

Python's command line parsing modules such as argparse have very limited support for config files and environment variables, so this module extends argparse to add these features.

Available on PyPI: http://pypi.python.org/pypi/ConfigArgParse

Features

  • command-line, config file, env var, and default settings can now be defined, documented, and parsed in one go using a single API (if a value is specified in more than one way then: command line > environment variables > config file values > defaults)
  • config files can have .ini or .yaml style syntax (eg. key=value or key: value)
  • user can provide a config file via a normal-looking command line arg (eg. -c path/to/config.txt) rather than the argparse-style @config.txt
  • one or more default config file paths can be specified (eg. ['/etc/bla.conf', '~/.my_config'] )
  • all argparse functionality is fully supported, so this module can serve as a drop-in replacement (verified by argparse unittests).
  • env vars and config file keys & syntax are automatically documented in the -h help message
  • new method print_values() can report keys & values and where they were set (eg. command line, env var, config file, or default).
  • lite-weight (no 3rd-party library dependencies except (optionally) PyYAML)
  • extensible (ConfigFileParser can be subclassed to define a new config file format)
  • unittested by running the unittests that came with argparse but on configargparse, and using tox to test with Python 3.5+

Example

config_test.py:

Script that defines 4 options and a positional arg and then parses and prints the values. Also, it prints out the help message as well as the string produced by format_values() to show what they look like.

import configargparse

p = configargparse.ArgParser(default_config_files=['/etc/app/conf.d/*.conf', '~/.my_settings'])
p.add('-c', '--my-config', required=True, is_config_file=True, help='config file path')
p.add('--genome', required=True, help='path to genome file')  # this option can be set in a config file because it starts with '--'
p.add('-v', help='verbose', action='store_true')
p.add('-d', '--dbsnp', help='known variants .vcf', env_var='DBSNP_PATH')  # this option can be set in a config file because it starts with '--'
p.add('vcf', nargs='+', help='variant file(s)')

options = p.parse_args()

print(options)
print("----------")
print(p.format_help())
print("----------")
print(p.format_values())    # useful for logging where different settings came from

config.txt:

Since the script above set the config file as required=True, lets create a config file to give it:

# settings for config_test.py
genome = HCMV     # cytomegalovirus genome
dbsnp = /data/dbsnp/variants.vcf

command line:

Now run the script and pass it the config file:

DBSNP_PATH=/data/dbsnp/variants_v2.vcf python config_test.py --my-config config.txt f1.vcf f2.vcf

output:

Here is the result:

Namespace(dbsnp='/data/dbsnp/variants_v2.vcf', genome='HCMV', my_config='config.txt', v=False, vcf=['f1.vcf', 'f2.vcf'])
----------
usage: config_test.py [-h] -c MY_CONFIG --genome GENOME [-v] [-d DBSNP]
                      vcf [vcf ...]

Args that start with '--' (eg. --genome) can also be set in a config file
(/etc/app/conf.d/*.conf or ~/.my_settings or specified via -c). Config file
syntax allows: key=value, flag=true, stuff=[a,b,c] (for details, see syntax at
https://goo.gl/R74nmi). If an arg is specified in more than one place, then
commandline values override environment variables which override config file
values which override defaults.

positional arguments:
  vcf                   variant file(s)

optional arguments:
  -h, --help            show this help message and exit
  -c MY_CONFIG, --my-config MY_CONFIG
                        config file path
  --genome GENOME       path to genome file
  -v                    verbose
  -d DBSNP, --dbsnp DBSNP
                        known variants .vcf [env var: DBSNP_PATH]

----------
Command Line Args:   --my-config config.txt f1.vcf f2.vcf
Environment Variables:
  DBSNP_PATH:        /data/dbsnp/variants_v2.vcf
Config File (config.txt):
  genome:            HCMV

Special Values

Under the hood, configargparse handles environment variables and config file values by converting them to their corresponding command line arg. For example, "key = value" will be processed as if "--key value" was specified on the command line.

Also, the following special values (whether in a config file or an environment variable) are handled in a special way to support booleans and lists:

  • key = true is handled as if "--key" was specified on the command line. In your python code this key must be defined as a boolean flag (eg. action="store_true" or similar).
  • key = [value1, value2, ...] is handled as if "--key value1 --key value2" etc. was specified on the command line. In your python code this key must be defined as a list (eg. action="append").

Config File Syntax

Only command line args that have a long version (eg. one that starts with '--') can be set in a config file. For example, "--color" can be set by putting "color=green" in a config file. The config file syntax depends on the constructor arg: config_file_parser_class which can be set to one of the provided classes: DefaultConfigFileParser, YAMLConfigFileParser, ConfigparserConfigFileParser or to your own subclass of the ConfigFileParser abstract class.

DefaultConfigFileParser - the full range of valid syntax is:

# this is a comment
; this is also a comment (.ini style)
---            # lines that start with --- are ignored (yaml style)
-------------------
[section]      # .ini-style section names are treated as comments

# how to specify a key-value pair (all of these are equivalent):
name value     # key is case sensitive: "Name" isn't "name"
name = value   # (.ini style)  (white space is ignored, so name = value same as name=value)
name: value    # (yaml style)
--name value   # (argparse style)

# how to set a flag arg (eg. arg which has action="store_true")
--name
name
name = True    # "True" and "true" are the same

# how to specify a list arg (eg. arg which has action="append")
fruit = [apple, orange, lemon]
indexes = [1, 12, 35 , 40]

YAMLConfigFileParser - allows a subset of YAML syntax (http://goo.gl/VgT2DU)

# a comment
name1: value
name2: true    # "True" and "true" are the same

fruit: [apple, orange, lemon]
indexes: [1, 12, 35, 40]
colors:
  - green
  - red
  - blue

ConfigparserConfigFileParser - allows a subset of python's configparser module syntax (https://docs.python.org/3.7/library/configparser.html). In particular the following configparser options are set:

config = configparser.ArgParser(
    delimiters=("=",":"),
    allow_no_value=False,
    comment_prefixes=("#",";"),
    inline_comment_prefixes=("#",";"),
    strict=True,
    empty_lines_in_values=False,
)

Once configparser parses the config file all section names are removed, thus all keys must have unique names regardless of which INI section they are defined under. Also, any keys which have python list syntax are converted to lists by evaluating them as python code using ast.literal_eval (https://docs.python.org/3/library/ast.html#ast.literal_eval). To facilitate this all multi-line values are converted to single-line values. Thus multi-line string values will have all new-lines converted to spaces. Note, since key-value pairs that have python dictionary syntax are saved as single-line strings, even if formatted across multiple lines in the config file, dictionaries can be read in and converted to valid python dictionaries with PyYAML's safe_load. Example given below:

# inside your config file (e.g. config.ini)
[section1]  # INI sections treated as comments
system1_settings: { # start of multi-line dictionary
    'a':True,
    'b':[2, 4, 8, 16],
    'c':{'start':0, 'stop':1000},
    'd':'experiment 32 testing simulation with parameter a on'
    } # end of multi-line dictionary value

.......

# in your configargparse setup
import configargparse
import yaml

parser = configargparse.ArgParser(
    config_file_parser_class=configargparse.ConfigparserConfigFileParser
)
parser.add_argument('--system1_settings', type=yaml.safe_load)

args = parser.parse_args() # now args.system1 is a valid python dict

IniConfigParser - INI parser with support for sections.

This parser somewhat ressembles ConfigparserConfigFileParser. It uses configparser and apply the same kind of processing to values written with python list syntax.

With the following additions:
  • Must be created with argument to bind the parser to a list of sections.
  • Does not convert multiline strings to single line.
  • Optional support for converting multiline strings to list (if split_ml_text_to_list=True).
  • Optional support for quoting strings in config file
    (useful when text must not be converted to list or when text should contain trailing whitespaces).

This config parser can be used to integrate with setup.cfg files.

Example:

# this is a comment
; also a comment
[my_super_tool]
# how to specify a key-value pair
format-string: restructuredtext
# white space are ignored, so name = value same as name=value
# this is why you can quote strings
quoted-string = '\thello\tmom...  '
# how to set an arg which has action="store_true"
warnings-as-errors = true
# how to set an arg which has action="count" or type=int
verbosity = 1
# how to specify a list arg (eg. arg which has action="append")
repeatable-option = ["https://docs.python.org/3/objects.inv",
               "https://twistedmatrix.com/documents/current/api/objects.inv"]
# how to specify a multiline text:
multi-line-text =
   Lorem ipsum dolor sit amet, consectetur adipiscing elit.
   Vivamus tortor odio, dignissim non ornare non, laoreet quis nunc.
   Maecenas quis dapibus leo, a pellentesque leo.

If you use IniConfigParser(sections, split_ml_text_to_list=True):

# the same rules are applicable with the following changes:
[my-software]
# how to specify a list arg (eg. arg which has action="append")
repeatable-option = # Just enter one value per line (the list literal format can also be used)
   https://docs.python.org/3/objects.inv
   https://twistedmatrix.com/documents/current/api/objects.inv
# how to specify a multiline text (you have to quote it):
multi-line-text = '''
   Lorem ipsum dolor sit amet, consectetur adipiscing elit.
   Vivamus tortor odio, dignissim non ornare non, laoreet quis nunc.
   Maecenas quis dapibus leo, a pellentesque leo.
   '''

Usage:

import configargparse
parser = configargparse.ArgParser(
         default_config_files=['setup.cfg', 'my_super_tool.ini'],
         config_file_parser_class=configargparse.IniConfigParser(['tool:my_super_tool', 'my_super_tool']),
     )
...

TomlConfigParser - TOML parser with support for sections.

TOML parser. This config parser can be used to integrate with pyproject.toml files.

Example:

# this is a comment
[tool.my-software] # TOML section table.
# how to specify a key-value pair
format-string = "restructuredtext" # strings must be quoted
# how to set an arg which has action="store_true"
warnings-as-errors = true
# how to set an arg which has action="count" or type=int
verbosity = 1
# how to specify a list arg (eg. arg which has action="append")
repeatable-option = ["https://docs.python.org/3/objects.inv",
               "https://twistedmatrix.com/documents/current/api/objects.inv"]
# how to specify a multiline text:
multi-line-text = '''
   Lorem ipsum dolor sit amet, consectetur adipiscing elit.
   Vivamus tortor odio, dignissim non ornare non, laoreet quis nunc.
   Maecenas quis dapibus leo, a pellentesque leo.
   '''

Usage:

import configargparse
parser = configargparse.ArgParser(
         default_config_files=['pyproject.toml', 'my_super_tool.toml'],
         config_file_parser_class=configargparse.TomlConfigParser(['tool.my_super_tool']),
     )
...

CompositeConfigParser - Create a config parser to understand multiple formats.

This parser will successively try to parse the file with each parser, until it succeeds, else fail showing all encountered error messages.

The following code will make configargparse understand both TOML and INI formats. Making it easy to integrate in both pyproject.toml and setup.cfg.

import configargparse
my_tool_sections = ['tool.my_super_tool', 'tool:my_super_tool', 'my_super_tool']
                 # pyproject.toml like section, setup.cfg like section, custom section
parser = configargparse.ArgParser(
         default_config_files=['setup.cfg', 'my_super_tool.ini'],
         config_file_parser_class=configargparse.CompositeConfigParser(
            [configargparse.TomlConfigParser(my_tool_sections),
             configargparse.IniConfigParser(my_tool_sections, split_ml_text_to_list=True)]
            ),
     )
...

Note that it's required to put the TOML parser first because the INI syntax basically would accept anything whereas TOML.

ArgParser Singletons

To make it easier to configure different modules in an application, configargparse provides globally-available ArgumentParser instances via configargparse.get_argument_parser('name') (similar to logging.getLogger('name')).

Here is an example of an application with a utils module that also defines and retrieves its own command-line args.

main.py

import configargparse
import utils

p = configargparse.get_argument_parser()
p.add_argument("-x", help="Main module setting")
p.add_argument("--m-setting", help="Main module setting")
options = p.parse_known_args()   # using p.parse_args() here may raise errors.

utils.py

import configargparse
p = configargparse.get_argument_parser()
p.add_argument("--utils-setting", help="Config-file-settable option for utils")

if __name__ == "__main__":
   options = p.parse_known_args()

Help Formatters

ArgumentDefaultsRawHelpFormatter is a new HelpFormatter that both adds default values AND disables line-wrapping. It can be passed to the constructor: ArgParser(.., formatter_class=ArgumentDefaultsRawHelpFormatter)

Aliases

The configargparse.ArgumentParser API inherits its class and method names from argparse and also provides the following shorter names for convenience:

  • p = configargparse.get_arg_parser() # get global singleton instance
  • p = configargparse.get_parser()
  • p = configargparse.ArgParser() # create a new instance
  • p = configargparse.Parser()
  • p.add_arg(..)
  • p.add(..)
  • options = p.parse(..)

HelpFormatters:

  • RawFormatter = RawDescriptionHelpFormatter
  • DefaultsFormatter = ArgumentDefaultsHelpFormatter
  • DefaultsRawFormatter = ArgumentDefaultsRawHelpFormatter

API Documentation

You can review the generated API Documentation for the configargparse module: HERE

Design Notes

Unit tests:

tests/test_configargparse.py contains custom unittests for features specific to this module (such as config file and env-var support), as well as a hook to load and run argparse unittests (see the built-in test.test_argparse module) but on configargparse in place of argparse. This ensures that configargparse will work as a drop in replacement for argparse in all usecases.

Previously existing modules (PyPI search keywords: config argparse):

  • argparse (built-in module Python v2.7+)
    • Good:
      • fully featured command line parsing
      • can read args from files using an easy to understand mechanism
    • Bad:
      • syntax for specifying config file path is unusual (eg. @file.txt)and not described in the user help message.
      • default config file syntax doesn't support comments and is unintuitive (eg. --namevalue)
      • no support for environment variables
  • ConfArgParse v1.0.15 (https://pypi.python.org/pypi/ConfArgParse)
    • Good:
      • extends argparse with support for config files parsed by ConfigParser
      • clear documentation in README
    • Bad:
      • config file values are processed using ArgumentParser.set_defaults(..) which means "required" and "choices" are not handled as expected. For example, if you specify a required value in a config file, you still have to specify it again on the command line.
      • doesn't work with Python 3 yet
      • no unit tests, code not well documented
  • appsettings v0.5 (https://pypi.python.org/pypi/appsettings)
    • Good:
      • supports config file (yaml format) and env_var parsing
      • supports config-file-only setting for specifying lists and dicts
    • Bad:
      • passes in config file and env settings via parse_args namespace param
      • tests not finished and don't work with Python 3 (import StringIO)
  • argparse_config v0.5.1 (https://pypi.python.org/pypi/argparse_config)
    • Good:
      • similar features to ConfArgParse v1.0.15
    • Bad:
      • doesn't work with Python 3 (error during pip install)
  • yconf v0.3.2 - (https://pypi.python.org/pypi/yconf) - features and interface not that great
  • hieropt v0.3 - (https://pypi.python.org/pypi/hieropt) - doesn't appear to be maintained, couldn't find documentation
  • configurati v0.2.3 - (https://pypi.python.org/pypi/configurati)
    • Good:
      • JSON, YAML, or Python configuration files
      • handles rich data structures such as dictionaries
      • can group configuration names into sections (like .ini files)
    • Bad:
      • doesn't work with Python 3
      • 2+ years since last release to PyPI
      • apparently unmaintained

Design choices:

  1. all options must be settable via command line. Having options that can only be set using config files or env. vars adds complexity to the API, and is not a useful enough feature since the developer can split up options into sections and call a section "config file keys", with command line args that are just "--" plus the config key.
  2. config file and env. var settings should be processed by appending them to the command line (another benefit of #1). This is an easy-to-implement solution and implicitly takes care of checking that all "required" args are provided, etc., plus the behavior should be easy for users to understand.
  3. configargparse shouldn't override argparse's convert_arg_line_to_args method so that all argparse unit tests can be run on configargparse.
  4. in terms of what to allow for config file keys, the "dest" value of an option can't serve as a valid config key because many options can have the same dest. Instead, since multiple options can't use the same long arg (eg. "--long-arg-x"), let the config key be either "--long-arg-x" or "long-arg-x". This means the developer can allow only a subset of the command-line args to be specified via config file (eg. short args like -x would be excluded). Also, that way config keys are automatically documented whenever the command line args are documented in the help message.
  5. don't force users to put config file settings in the right .ini [sections]. This doesn't have a clear benefit since all options are command-line settable, and so have a globally unique key anyway. Enforcing sections just makes things harder for the user and adds complexity to the implementation. NOTE: This design choice was preventing configargparse from integrating with common Python project config files like setup.cfg or pyproject.toml, so additional parser classes were added that parse only a subset of the values defined in INI or TOML config files.
  6. if necessary, config-file-only args can be added later by implementing a separate add method and using the namespace arg as in appsettings_v0.5

Relevant sites:

Versioning

This software follows Semantic Versioning

configargparse's People

Contributors

bpiotr avatar bw2 avatar comargo avatar craymichael avatar danielhers avatar dbrnz avatar dufferzafar avatar dwfreed avatar ftesser avatar helgi avatar hugovk avatar ianco avatar j-a-n avatar josephcarmack avatar kuba avatar lahwaacz avatar macfreek avatar negation avatar orenshk avatar pareyesv avatar roi-meir avatar ronhanson avatar santosh653 avatar sdarwin avatar shakefu avatar speedyleion avatar tristanlatr avatar trougnouf avatar tzing avatar wcastello avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

configargparse's Issues

Value from env-var doesn't work for options defined on main parser when used with subparsers

Here's a minimal example:

parser = configargparse.getArgumentParser()
parser.add('--foo', env_var='MY_FOO')
subparsers = parser.add_subparsers(help='Choose command')
sub_parser = subparsers.add_parser('sub')
args = parser.parse()

when running MY_FOO=bar ./my_script.py this works fine, but MY_FOO=bar ./my_script.py sub throws this errors:

my_script.py: error: unrecognized arguments: --foo

I assume this is because --foo is not an argument of the subparser, and this works by appending --foo $MY_FOO at the end of sys.argv...

Subcommands params in config?

Is there way to have global and subparsers params in one config file?

For example:

#!/usr/bin/env python3

import configargparse
parser = configargparse.ArgParser(
    default_config_files=["config.ini"],
    args_for_setting_config_path=["-c"])

sub = parser.add_subparsers().add_parser('foo')
parser.add_argument("-b", "--bar")
sub.add_argument("-f", "--foo")
print(parser.parse_args())

And config.ini:

bar = baz
foo = fool

Now I get: contains unknown config key(s): foo

PS: I know about #26 , but this is not exactly same.

Feature request: Python string values

I'm just about to release the first version of mitmproxy with config parsing based on ConfigArgParse. Thanks a bunch for your solid work.

Something we'd find immensely useful is the ability to specify values in key/value pairs as Python strings and triple-quoted multi-line strings, with Python-style escape sequences. This would make the config files for our tools hugely more powerful.

This would require converting the current parser to something more sophisticated. PyParsing has built-in definitions for quoted strings, and implementing the parser in it would not be very much work.

I don't have the bandwidth for this myself, but please let me know if a patch like this would be pulled, and I may get to it some time down the track.

Positional argument seperator '--' strips config/env values

example.py

import configargparse

p = configargparse.ArgParser()
p.add('--required', required=True, env_var='TEST_REQUIRED')

print(p.parse_args())

Results

export TEST_REQUIRED=value;

python3 example.py
# Namespace(required='value')

python3 example.py --
# usage: example.py [-h] --required REQUIRED
# example.py: error: the following arguments are required: --required

Unable to set boolean value to False

If I have a boolean with a default of True, it appears impossible to set it to False from the command line since you can only set it to true with a --foobar singleton and you can't set it to false with an environment variable either.

(cap) /tmp $ cat ./cap.py
#!/usr/bin/env python

import configargparse

parser = configargparse.ArgParser()
parser.add('--foobar', action='store_true', default=True)
args = parser.parse_args()
parser.print_values()
if args.foobar:
    print('foobar is True')
else:
    print('foobar is False or undefined')
(cap) /tmp $ ./cap.py
foobar is True
(cap) /tmp $ export foobar=False
(cap) /tmp $ ./cap.py
foobar is True
(cap) /tmp $ export FOOBAR=False
(cap) /tmp $ ./cap.py
foobar is True
(cap) /tmp $ echo $FOOBAR $foobar
False False

Test failures under Python 3.5

I am getting the following test failures with python 3.5.0. Maybe allow_abbrev needs to be supported?

======================================================================
ERROR: test_failures_many_groups_listargs (tests.test_configargparse.TestOptionalsDisallowLongAbbreviation)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<string>", line 208, in wrapper
  File "<string>", line 223, in test_failures
  File "<string>", line 218, in _get_parser
TypeError: __init__() got an unexpected keyword argument 'allow_abbrev'

======================================================================
ERROR: test_failures_many_groups_sysargs (tests.test_configargparse.TestOptionalsDisallowLongAbbreviation)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<string>", line 208, in wrapper
  File "<string>", line 223, in test_failures
  File "<string>", line 218, in _get_parser
TypeError: __init__() got an unexpected keyword argument 'allow_abbrev'

======================================================================
ERROR: test_failures_no_groups_listargs (tests.test_configargparse.TestOptionalsDisallowLongAbbreviation)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<string>", line 208, in wrapper
  File "<string>", line 223, in test_failures
  File "<string>", line 218, in _get_parser
TypeError: __init__() got an unexpected keyword argument 'allow_abbrev'

======================================================================
ERROR: test_failures_no_groups_sysargs (tests.test_configargparse.TestOptionalsDisallowLongAbbreviation)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<string>", line 208, in wrapper
  File "<string>", line 223, in test_failures
  File "<string>", line 218, in _get_parser
TypeError: __init__() got an unexpected keyword argument 'allow_abbrev'

======================================================================
ERROR: test_failures_one_group_listargs (tests.test_configargparse.TestOptionalsDisallowLongAbbreviation)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<string>", line 208, in wrapper
  File "<string>", line 223, in test_failures
  File "<string>", line 218, in _get_parser
TypeError: __init__() got an unexpected keyword argument 'allow_abbrev'

======================================================================
ERROR: test_failures_one_group_sysargs (tests.test_configargparse.TestOptionalsDisallowLongAbbreviation)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<string>", line 208, in wrapper
  File "<string>", line 223, in test_failures
  File "<string>", line 218, in _get_parser
TypeError: __init__() got an unexpected keyword argument 'allow_abbrev'

======================================================================
ERROR: test_successes_many_groups_listargs (tests.test_configargparse.TestOptionalsDisallowLongAbbreviation)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<string>", line 208, in wrapper
  File "<string>", line 230, in test_successes
  File "<string>", line 218, in _get_parser
TypeError: __init__() got an unexpected keyword argument 'allow_abbrev'

======================================================================
ERROR: test_successes_many_groups_sysargs (tests.test_configargparse.TestOptionalsDisallowLongAbbreviation)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<string>", line 208, in wrapper
  File "<string>", line 230, in test_successes
  File "<string>", line 218, in _get_parser
TypeError: __init__() got an unexpected keyword argument 'allow_abbrev'

======================================================================
ERROR: test_successes_no_groups_listargs (tests.test_configargparse.TestOptionalsDisallowLongAbbreviation)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<string>", line 208, in wrapper
  File "<string>", line 230, in test_successes
  File "<string>", line 218, in _get_parser
TypeError: __init__() got an unexpected keyword argument 'allow_abbrev'

======================================================================
ERROR: test_successes_no_groups_sysargs (tests.test_configargparse.TestOptionalsDisallowLongAbbreviation)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<string>", line 208, in wrapper
  File "<string>", line 230, in test_successes
  File "<string>", line 218, in _get_parser
TypeError: __init__() got an unexpected keyword argument 'allow_abbrev'

======================================================================
ERROR: test_successes_one_group_listargs (tests.test_configargparse.TestOptionalsDisallowLongAbbreviation)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<string>", line 208, in wrapper
  File "<string>", line 230, in test_successes
  File "<string>", line 218, in _get_parser
TypeError: __init__() got an unexpected keyword argument 'allow_abbrev'

======================================================================
ERROR: test_successes_one_group_sysargs (tests.test_configargparse.TestOptionalsDisallowLongAbbreviation)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<string>", line 208, in wrapper
  File "<string>", line 230, in test_successes
  File "<string>", line 218, in _get_parser
TypeError: __init__() got an unexpected keyword argument 'allow_abbrev'

----------------------------------------------------------------------
Ran 1578 tests in 1.713s

FAILED (errors=12)

Argument parsing fails with --option=equals_delimiter and a config file

Test Files

test.py

#!/usr/bin/env python

import argparse
from pprint import pprint

import configargparse

a = argparse.ArgumentParser()
a.add_argument("--config")
a.add_argument("--foo")
args = a.parse_args()
pprint(args)

p = configargparse.getArgumentParser()
p.add_argument("--config", is_config_file=True)
p.add_argument("--foo")
args = p.parse_args()
pprint(args)

test.ini

foo=VALUE_IN_CONFIG

Example of Issue

Using --foo=bar (with equals) works without config parsing:

./test.py --foo=bar
Namespace(config=None, foo='bar')
Namespace(config=None, foo='bar')

However it breaks with config parsing (but not for --config):

./test.py --foo=bar --config=test.ini
Namespace(config='test.ini', foo='bar')
Namespace(config='test.ini', foo='VALUE_IN_CONFIG')

Using --foo bar (with space instead of equals) shows us the expected behavior:

./test.py --foo bar --config test.ini
Namespace(config='test.ini', foo='bar')
Namespace(config='test.ini', foo='bar')

missing SUPPRESS

Changed my program as follows:
import configargparse as argparse

And now:

$ tag -h
Traceback (most recent call last):
File "/home/user/make-deployment-group.py", line 31, in
parser.add_argument("--user-name", help=argparse.SUPPRESS)
AttributeError: 'module' object has no attribute 'SUPPRESS'

Optionally allow to address positional arguments

This function should be disabled per default since it might produce problems for some software. I would suggest that the metavar is used as the config parameter.

Probably it is hard to figure out if a config value is a positional argument (and in principle there could be some ambiguity). Maybe 'uppercase' is a good convention to label positional arguments?

feature - automatic env variables

I'd like to have an app where command line arguments, config file keys, and env vars are all "the same"

Given an app named "foo_bar", and command line options --single --multiple-word, without passing env_var, these config file keys would work (current behavior):

single = lonely
multiple-word = logorrhea

and these env vars would be automatic:

FOO_BAR__SINGLE=finally
FOO_BAR__MULTIPLE_WORD=unending

I used two underscores to separate the uniqueness prefix (FOO_BAR (defaulting to the uppercase app name)) but it could be one.

p = configargparse.ArgParser(automatic_env_vars=true)

p = configargparse.ArgParser(automatic_env_vars=true, env_var_prefix='FOO_BAR')

Setting is_config_file to true causes help to cut off

When I add the config argument with is_config_file=True and I run my program with the -h option, the help only shows the -h and the -c flags. Here is the example output using my_script.py from the documentation:

~$ python ./my_script.py -h
usage: my_script.py [-h] -c MY_CONFIG

optional arguments:
  -h, --help            show this help message and exit
  -c MY_CONFIG, --my-config MY_CONFIG
                    config file path

I am running python 2.7.5 on osx 10.9.4 with configargparse 0.9.2 installed via pip.

Add a "dump config" option

Any "--" argument can be used in a config file, and a user can see what those options are by parsing the help text, but there are other tools (like "clang-format") which will dump the config file equivalent of your command line if you ask them to.

You can then edit and modify this file as required, but you don't start from scratch, as you currently must do for ConfigArgParse.

If there's a straightforward way to do so outside of the module, that's cool, too, but I dug in a little and didn't see one.

My naive solution was to format the output of "vars(args)", but that fails to cover the cases where a flag is used (but not actually used, so its value is still "False"), or where a value is being stored to a destination different than its CLI name (via the "dest" kwarg), or where the CLI is not a "--" option.

To do this correctly, I really need to get access to the parser internals, so I'm requesting it as a new feature of the module itself.

new release soon?

Some important bugfixes have landed in develop but not made it into a release yet ... wondering if you can make one so that a "good" version can be resolved from PyPI without having to put GitHub URIs into my setup.py .... thanks!

arguments with config file string True get automatically converted to bool

Supposed we have the following in the INI files:

value = True

and the add command

p.add( "-v","--value", type=str, default="False" ) 

This results in an error value set to 'True' rather than a value (see below line 322)
But the user said it wants to have a type str. So it should be parsed as a string in any case and not transformed into bool automatically, this should only happen if the user did not specify a type , or really requested a type bool. (suggestion)
(I need this to happens since I reuse the parsed stuff to interpolate with other parsed ini files somewhere else in the code, and to make that happen everything needs to be strings )

I would use the following safe bool conversion:

def toBool(self,s, optionHint=None):
        """Safe converting to a boolean value, raises exception if s has wrong type or does not have the appropriate value"""
        if type(s) == str:
            try:
                if ['true','yes','on','false','no','off'].index(s.lower()) <= 2:
                    return True
                else:
                    return False
            except:
                raise ValueError("Value: %s %s !" % (s, "at option: %s" % optionsHint if optionHint else ""))
        else:
            raise ValueError("Converting type: %s with value: %s to bool is deliberately not supported!" % (type(s),s))

It would be good to be able to not automatically convert to bool here (line 322):
in convert_setting_to_command_line_arg

if value.lower() == "true":
            if type(action) not in ACTION_TYPES_THAT_DONT_NEED_A_VALUE:
                self.error("%s set to 'True' rather than a value" % key)
            args.append( action.option_strings[-1] )
``

Config file output can generate illigitimate values for keys when using custom Actions

Cause: ArgumentParser.get_items_for_config_file_output makes the incorrect assumption that an argument's config file value can always be found in the parsed namespace's attribute named after action.dest. With custom Actions this value can be arbitrary.

Effect: Using the action's computed value rather than the command-line's string value results in a config key-value pair that does not reproduce the original specified options.

class CustomAction(configargparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, 'arbitrary')

p = configargparse.ArgumentParser(args_for_setting_config_path=['-c'],
                                  args_for_writing_out_config_file=['-w'])
p.add_argument('--number', type=int, dest='result', action=CustomAction)
p.parse_args('--number 2' -w bad.conf)

The resulting namespace:

Namespace(result='arbitrary')  # Great, CustomAction did its job

But the resulting bad.conf

number = arbitrary  # This is not going to reproduce

Now consider when bad.conf is specified to -c

error: argument --number: invalid int value: 'arbitrary'

We only recognize the issue due to the type constraint on --number. If there were none, it might be more confusing as to why our option's arg is ignored or worse depending on our action's __call__ behavior.

The main problem here is the generated config does not reproduce the behavior of the original passed in arguments when passed back in as a config file.

Solution?: Basically we need to store the value passed on the command line to obtain reproducible behavior in situations like this, but it's not clear to me how best to do so. This information is available to the action's __call__ as arguments, and so could be stored on the parser for later reference in ArgumentParser.get_items_for_config_file_output.

Do you think this is a bug?

Thank you,
Brad

ConfigArgParse depends on pypandoc when installing via pip

The package is not installable via pip:

$ pip install ConfigArgParse
Downloading/unpacking ConfigArgParse
  Downloading ConfigArgParse-0.9.1.tar.gz
  Running setup.py (path:/.../build/ConfigArgParse/setup.py) egg_info for package ConfigArgParse
    ERROR: pypandoc module not found, could not convert Markdown to RST for PyPI
    Complete output from command python setup.py egg_info:
    ERROR: pypandoc module not found, could not convert Markdown to RST for PyPI

----------------------------------------
Cleaning up...
Command python setup.py egg_info failed with error code 1 in /.../build/ConfigArgParse
Storing debug log for failure in /.../.pip/pip.log

$ python --version
Python 2.7.8

Installing pypandoc solves the problem, but the README states the library has no 3rd-party requirements. The README.md file is not converted anyway, as it is missing in the PyPI package:

$ pip install pypandoc
[...]
$ pip install ConfigArgParse
Downloading/unpacking ConfigArgParse
  Downloading ConfigArgParse-0.9.1.tar.gz
  Running setup.py (path:/.../build/ConfigArgParse/setup.py) egg_info for package ConfigArgParse
    WARNING: couldn't find README.md

Installing collected packages: ConfigArgParse
  Running setup.py install for ConfigArgParse
    WARNING: couldn't find README.md

Successfully installed ConfigArgParse
Cleaning up...

allow unknown options in config file

Currently, only defined args are parsed from the config file, and unknown keys are ignored or cause an error (depending on the allow_unknown_config_file_keys constructor arg).

I want to change this so that unknown keys are always appended to the command line and whether they cause an error depends on whether parsing is done with parse_args() (which results in an error) or using parse_known_args() (in which case the unknown args are returned the same way as unknown command line args).

I'm still debating whether to also deprecate allow_unknown_config_file_keys constructor arg.

Refactor config file parsing, add support for more robust config file format(s)

The current config file format is overly simplistic. Support for lists and booleans was added as an after-thought and works only for very simple use-cases, but doesn't generalize.

This issue coalesces Issue #10 and other format-related Issues (#16, #19).

I think the right way to move forward on this will be:

  • step 1 - refactor / extract out config file-format-aware code into a separate class that can be overriden more easily.
  • step 2 - implement the class to provide a more robust alternative to the current format.

Any suggestions and/or pull requests will be appreciated.

Cannot escape comment characters within value in configuration file

For a configuration file ConfigfileParser.parse(), using a semicolon embedded in the value string (my case being SQL connection strings) cause the parser to fail. There should be a way to 1.) turn off comment parsing (the whole line becomes the value) or 2.) escape individual semicolon and hash characters or 3.) quote the value to separate it from comments. (Using 0.10.0 from PyPI)

AttributeError: 'module' object has no attribute 'ArgParser'

If I try to use the package (in either Python 2.7.8 or 3.4.1), it's as if there is nothing in the module.

For instance, from within iPython I do import configargparse, then if I type configargparse. and hit the tab key there is no auto-complete.

Trying to use the package in an app yields the following error:

Traceback (most recent call last):
  File "test/server.py", line 62, in <module>
    main(args=sys.argv)
  File "test/server.py", line 47, in main
    parser = configargparse.ArgParser(default_configfiles=['/etc/settings.ini'])
AttributeError: 'module' object has no attribute 'ArgParser'

default for default_config_files=[...]

I look at the README and see this:

p = configargparse.ArgParser(default_config_files=['/etc/settings.ini', '~/.my_settings'])

I think: everybody is going to have a different list here.

But I guess there could be a default which satisfies a lot of people.

My prefered API: just provide a basename.

Then the library checks below os.environ['VIRTUAL_ENV'], os.environ['HOME'], '/etc'.

Of course the new feature should not make old code break.

Please include the tests directory in your sdist tarball

I am packaging the module for Kali Linux (needed for a new version of mitmproxy) and I noticed that trying to run the test suite fails because the "tests" directory is missing from the tarball sent to PyPi:
https://pypi.python.org/packages/source/C/ConfigArgParse/ConfigArgParse-0.9.3.tar.gz

You might also want to create proper tags in your Git repository so that one can find out the precise commit used for each release sent to PyPi (and so that we can download tarballs from GitHub as a work-around when the PyPi tarball doesn't suit our needs, like is the case right now).

Thank you!

bug when setting a float value to -10. and not -10.0

When fixing a value defined with:
parser.add('--'+par_name,type=types.FloatType,required=False,help=par_help, default=par_default)

I get an error if in the config file i set
par_name = -10.
or
par_name = -9.
The error doesn't occur in the following:
`
par_name = -10.0

par_name = 10.

par_name = 10.0
`

Allow some command line options to be merged with those in the config

Taking from the first point of Features section of the readme:

If a value is specified in more than one way then: command line > environment variables > config file values > defaults.

ConfigArgParse (CAP) overrides the values specified in the config file with those specified in the command line arguments. For most of the cases this behaviour is nice, except when it isn't.

Consider the case of a mitmproxy user who has specified some scripts in the config file, and when they specify another script in the command line, the config ones get replaced.

The expected behaviour here is the merging of the list of scripts.

I propose that CAP should add a new parameter (say multiple) to parser.add_argument that determines this override behaviour. So, for eg, the following snippet would result in an option that merges the values:

parser.add_argument(
    "-s", "--script", 
    action="append", type=str, dest="scripts" default=[],
    multiple="merge"
)

The multiple parameter could have values:

  • merge - Merge the values specified in the command line, env variables & config files & defaults.
  • override - Follow the normal override process we follow now.

regression: unrecognized arguments error when using subparsers and config file

Test file:

#!/usr/bin/env python
import configargparse
parser = configargparse.ArgParser(args_for_setting_config_path=["-c"])
parser.add_subparsers().add_parser('foo')
parser.add_argument("-b", "--bar")
print parser.parse_args()

Test config:

bar = baz

Problem

(venv)jakub@iks:~/dev/letsencrypt/lets-encrypt-preview$ pip install git+https://github.com/bw2/[email protected]
Downloading/unpacking git+https://github.com/bw2/[email protected]
  Cloning https://github.com/bw2/ConfigArgParse.git (to 0.9.3) to /tmp/pip-80_Rgc-build
  Running setup.py (path:/tmp/pip-80_Rgc-build/setup.py) egg_info for package from git+https://github.com/bw2/[email protected]

Installing collected packages: ConfigArgParse
  Running setup.py install for ConfigArgParse

Successfully installed ConfigArgParse
Cleaning up...
(venv)jakub@iks:~/dev/letsencrypt/lets-encrypt-preview$ ./t.py -c t.ini foo                                                                                                                         
Namespace(bar='baz', config_file='t.ini')
(venv)jakub@iks:~/dev/letsencrypt/lets-encrypt-preview$ pip uninstall ConfigArgParse                                                                                                                
Uninstalling ConfigArgParse:
  /home/jakub/dev/letsencrypt/lets-encrypt-preview/venv/lib/python2.7/site-packages/ConfigArgParse-0.9.3-py2.7.egg-info
  /home/jakub/dev/letsencrypt/lets-encrypt-preview/venv/lib/python2.7/site-packages/configargparse.py
  /home/jakub/dev/letsencrypt/lets-encrypt-preview/venv/lib/python2.7/site-packages/configargparse.pyc
Proceed (y/n)? y
  Successfully uninstalled ConfigArgParse
(venv)jakub@iks:~/dev/letsencrypt/lets-encrypt-preview$ pip install git+https://github.com/bw2/ConfigArgParse.git@e0463e0ec5fbd1f453ed833abebc633c77d4e0c6                                        
Downloading/unpacking git+https://github.com/bw2/ConfigArgParse.git@e0463e0ec5fbd1f453ed833abebc633c77d4e0c6
  Cloning https://github.com/bw2/ConfigArgParse.git (to e0463e0ec5fbd1f453ed833abebc633c77d4e0c6) to /tmp/pip-6fNLp7-build
  Could not find a tag or branch 'e0463e0ec5fbd1f453ed833abebc633c77d4e0c6', assuming commit.
  Running setup.py (path:/tmp/pip-6fNLp7-build/setup.py) egg_info for package from git+https://github.com/bw2/ConfigArgParse.git@e0463e0ec5fbd1f453ed833abebc633c77d4e0c6

Installing collected packages: ConfigArgParse
  Running setup.py install for ConfigArgParse

Successfully installed ConfigArgParse
Cleaning up...
(venv)jakub@iks:~/dev/letsencrypt/lets-encrypt-preview$ ./t.py -c t.ini foo
usage: t.py [-h] [-c CONFIG_FILE] [-b BAR] {foo} ...
t.py: error: unrecognized arguments: --bar baz

Regex tests fail on 0.10.0

While testing 0.10.0, a couple of regex tests fail. This happens under both python 2.7 and 3.4.

Here is the failing portion of the log when building with python 2.7.10:

======================================================================
FAIL: testBasicCase2 (tests.test_configargparse.TestBasicUseCases)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/leo/tmp/nix-build-python2-configargparse-0.10.0.drv-0/ConfigArgParse-0.10.0/tests/test_configargparse.py", line 230, in testBasicCase2
    'positional arguments:\n'
AssertionError: Regexp didn't match: 'usage: .* \\[-h\\] --genome GENOME \\[-v\\] -g MY_CFG_FILE \\[-d DBSNP\\]\\s+\\[-f FRMT\\]\\s+vcf \\[vcf ...\\]\n\n(.+\\s+)(.+\\s+)(.+\\s+)(.+\\s+)(.+\\s+)(.+\\s+)(.+\\s+)(.+\\s+)positional arguments:\n  vcf \\s+ Variant file\\(s\\)\n\noptional arguments:\n  -h, --help \\s+ show this help message and exit\n  --genome GENOME \\s+ Path to genome file\n  -v\n  -g MY_CFG_FILE, --my-cfg-file MY_CFG_FILE\n  -d DBSNP, --dbsnp DBSNP\\s+\\[env var: DBSNP_PATH\\]\n  -f FRMT, --format FRMT\\s+\\[env var: OUTPUT_FORMAT\\]\n' not found in "usage: setup.py [-h] --genome GENOME [-v] -g MY_CFG_FILE [-d DBSNP] [-f FRMT]\n                vcf [vcf ...]\n\nArgs that start with '--' (eg. --genome) can also be set in a config file\n(/etc/settings.ini or /home/jeff/.user_settings or /home/leo/tmp/nix-build-\npython2-configargparse-0.10.0.drv-0/tmpVTIlXW or specified via -g). The\nrecognized syntax for setting (key, value) pairs is based on the INI and YAML\nformats (e.g. key=value or foo=TRUE). For full documentation of the\ndifferences from the standards please refer to the ConfigArgParse\ndocumentation. If an arg is specified in more than one place, then commandline\nvalues override environment variables which override config file values which\noverride defaults.\n\npositional arguments:\n  vcf                   Variant file(s)\n\noptional arguments:\n  -h, --help            show this help message and exit\n  --genome GENOME       Path to genome file\n  -v\n  -g MY_CFG_FILE, --my-cfg-file MY_CFG_FILE\n  -d DBSNP, --dbsnp DBSNP\n                        [env var: DBSNP_PATH]\n  -f FRMT, --format FRMT\n                        [env var: OUTPUT_FORMAT]\n"

======================================================================
FAIL: testBasicCase2_WithGroups (tests.test_configargparse.TestBasicUseCases)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/leo/tmp/nix-build-python2-configargparse-0.10.0.drv-0/ConfigArgParse-0.10.0/tests/test_configargparse.py", line 266, in testBasicCase2_WithGroups
    self.testBasicCase2(use_groups=True)
  File "/home/leo/tmp/nix-build-python2-configargparse-0.10.0.drv-0/ConfigArgParse-0.10.0/tests/test_configargparse.py", line 244, in testBasicCase2
    'positional arguments:\n'
AssertionError: Regexp didn't match: 'usage: .* \\[-h\\] --genome GENOME \\[-v\\] -g MY_CFG_FILE \\[-d DBSNP\\]\\s+\\[-f FRMT\\]\\s+vcf \\[vcf ...\\]\n\n.+\\s+.+\\s+.+\\s+.+\\s+.+\\s+.+\\s+.+\\s+.+\\s+positional arguments:\n  vcf \\s+ Variant file\\(s\\)\n\noptional arguments:\n  -h, --help \\s+ show this help message and exit\n\ng1:\n  --genome GENOME \\s+ Path to genome file\n  -v\n  -g MY_CFG_FILE, --my-cfg-file MY_CFG_FILE\n\ng2:\n  -d DBSNP, --dbsnp DBSNP\\s+\\[env var: DBSNP_PATH\\]\n  -f FRMT, --format FRMT\\s+\\[env var: OUTPUT_FORMAT\\]\n' not found in "usage: setup.py [-h] --genome GENOME [-v] -g MY_CFG_FILE [-d DBSNP] [-f FRMT]\n                vcf [vcf ...]\n\nArgs that start with '--' (eg. --genome) can also be set in a config file\n(/etc/settings.ini or /home/jeff/.user_settings or /home/leo/tmp/nix-build-\npython2-configargparse-0.10.0.drv-0/tmptLIJ5R or specified via -g). The\nrecognized syntax for setting (key, value) pairs is based on the INI and YAML\nformats (e.g. key=value or foo=TRUE). For full documentation of the\ndifferences from the standards please refer to the ConfigArgParse\ndocumentation. If an arg is specified in more than one place, then commandline\nvalues override environment variables which override config file values which\noverride defaults.\n\npositional arguments:\n  vcf                   Variant file(s)\n\noptional arguments:\n  -h, --help            show this help message and exit\n\ng1:\n  --genome GENOME       Path to genome file\n  -v\n  -g MY_CFG_FILE, --my-cfg-file MY_CFG_FILE\n\ng2:\n  -d DBSNP, --dbsnp DBSNP\n                        [env var: DBSNP_PATH]\n  -f FRMT, --format FRMT\n                        [env var: OUTPUT_FORMAT]\n"

----------------------------------------------------------------------
Ran 1646 tests in 2.850s

FAILED (failures=2)

And with python 3.4.3:

======================================================================
FAIL: testBasicCase2 (tests.test_configargparse.TestBasicUseCases)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/leo/tmp/nix-build-python-configargparse-0.10.0.drv-0/ConfigArgParse-0.10.0/tests/test_configargparse.py", line 230, in testBasicCase2
    'positional arguments:\n'
AssertionError: Regex didn't match: 'usage: .* \\[-h\\] --genome GENOME \\[-v\\] -g MY_CFG_FILE \\[-d DBSNP\\]\\s+\\[-f FRMT\\]\\s+vcf \\[vcf ...\\]\n\n(.+\\s+)(.+\\s+)(.+\\s+)(.+\\s+)(.+\\s+)(.+\\s+)(.+\\s+)(.+\\s+)positional arguments:\n  vcf \\s+ Variant file\\(s\\)\n\noptional arguments:\n  -h, --help \\s+ show this help message and exit\n  --genome GENOME \\s+ Path to genome file\n  -v\n  -g MY_CFG_FILE, --my-cfg-file MY_CFG_FILE\n  -d DBSNP, --dbsnp DBSNP\\s+\\[env var: DBSNP_PATH\\]\n  -f FRMT, --format FRMT\\s+\\[env var: OUTPUT_FORMAT\\]\n' not found in "usage: setup.py [-h] --genome GENOME [-v] -g MY_CFG_FILE [-d DBSNP] [-f FRMT]\n                vcf [vcf ...]\n\nArgs that start with '--' (eg. --genome) can also be set in a config file\n(/etc/settings.ini or /home/jeff/.user_settings or /home/leo/tmp/nix-build-\npython-configargparse-0.10.0.drv-0/tmpa49kdjqu or specified via -g). The\nrecognized syntax for setting (key, value) pairs is based on the INI and YAML\nformats (e.g. key=value or foo=TRUE). For full documentation of the\ndifferences from the standards please refer to the ConfigArgParse\ndocumentation. If an arg is specified in more than one place, then commandline\nvalues override environment variables which override config file values which\noverride defaults.\n\npositional arguments:\n  vcf                   Variant file(s)\n\noptional arguments:\n  -h, --help            show this help message and exit\n  --genome GENOME       Path to genome file\n  -v\n  -g MY_CFG_FILE, --my-cfg-file MY_CFG_FILE\n  -d DBSNP, --dbsnp DBSNP\n                        [env var: DBSNP_PATH]\n  -f FRMT, --format FRMT\n                        [env var: OUTPUT_FORMAT]\n"

======================================================================
FAIL: testBasicCase2_WithGroups (tests.test_configargparse.TestBasicUseCases)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/leo/tmp/nix-build-python-configargparse-0.10.0.drv-0/ConfigArgParse-0.10.0/tests/test_configargparse.py", line 266, in testBasicCase2_WithGroups
    self.testBasicCase2(use_groups=True)
  File "/home/leo/tmp/nix-build-python-configargparse-0.10.0.drv-0/ConfigArgParse-0.10.0/tests/test_configargparse.py", line 244, in testBasicCase2
    'positional arguments:\n'
AssertionError: Regex didn't match: 'usage: .* \\[-h\\] --genome GENOME \\[-v\\] -g MY_CFG_FILE \\[-d DBSNP\\]\\s+\\[-f FRMT\\]\\s+vcf \\[vcf ...\\]\n\n.+\\s+.+\\s+.+\\s+.+\\s+.+\\s+.+\\s+.+\\s+.+\\s+positional arguments:\n  vcf \\s+ Variant file\\(s\\)\n\noptional arguments:\n  -h, --help \\s+ show this help message and exit\n\ng1:\n  --genome GENOME \\s+ Path to genome file\n  -v\n  -g MY_CFG_FILE, --my-cfg-file MY_CFG_FILE\n\ng2:\n  -d DBSNP, --dbsnp DBSNP\\s+\\[env var: DBSNP_PATH\\]\n  -f FRMT, --format FRMT\\s+\\[env var: OUTPUT_FORMAT\\]\n' not found in "usage: setup.py [-h] --genome GENOME [-v] -g MY_CFG_FILE [-d DBSNP] [-f FRMT]\n                vcf [vcf ...]\n\nArgs that start with '--' (eg. --genome) can also be set in a config file\n(/etc/settings.ini or /home/jeff/.user_settings or /home/leo/tmp/nix-build-\npython-configargparse-0.10.0.drv-0/tmpy1sza7e8 or specified via -g). The\nrecognized syntax for setting (key, value) pairs is based on the INI and YAML\nformats (e.g. key=value or foo=TRUE). For full documentation of the\ndifferences from the standards please refer to the ConfigArgParse\ndocumentation. If an arg is specified in more than one place, then commandline\nvalues override environment variables which override config file values which\noverride defaults.\n\npositional arguments:\n  vcf                   Variant file(s)\n\noptional arguments:\n  -h, --help            show this help message and exit\n\ng1:\n  --genome GENOME       Path to genome file\n  -v\n  -g MY_CFG_FILE, --my-cfg-file MY_CFG_FILE\n\ng2:\n  -d DBSNP, --dbsnp DBSNP\n                        [env var: DBSNP_PATH]\n  -f FRMT, --format FRMT\n                        [env var: OUTPUT_FORMAT]\n"

----------------------------------------------------------------------
Ran 1554 tests in 3.005s

FAILED (failures=2)

Add multiline YAML (and/or INI).

YAML supports multiline input (https://gist.github.com/rjattrill/7523554) and lists (http://docs.ansible.com/YAMLSyntax.html#yaml-basics) but neither of those can be used in the ini file:

listoffruits:
    - Apple
    - Orange
theelements: >
    [my_extraorbitantly_long_element_address1,
    my_extraorbitantly_long_element_address2,
    my_extraorbitantly_long_element_address3]

these produce errors. I mainly wanted to use your library because I expect user to provide tens of long strings as configuration. Putting them in one-line would clutter the INI file.

Do you consider such feature useful and would like to implement it?

I also didn't find a way to provide an INI style multiline content but this is not officially supported (INI is not even a well defined standard and the most formal definitions I saw didn't mention multiline though some libs allow that with indentation).

Help page adds "CONFIGFILE or " for each configfile, including the final one (so always have trailing "or" + " ")

Repro:

$ cat >junktest <<EOF
import configargparse
p = configargparse.Parser(default_config_files=['woot1', 'woot2'])
p.add('--foo', help="Store some foo")
print(p.parse())
EOF

$ python junktest -h
usage: junktest [-h] [--foo FOO]

Args that start with '--' (eg. --foo) can also be set in a config file (woot1
or woot2 or ) by using .ini or .yaml-style syntax (eg. foo=value). If an arg
is specified in more than one place, then command-line values override config
file values which override defaults.

optional arguments:
  -h, --help  show this help message and exit
  --foo FOO   Store some foo

Notice the relevant part:

... set in a config file (woot1 or woot2 or ) by using .ini ...

Not the end of the world, but you know.

Allow getting values from a specific section of config file

I have a use case where my config file has a DEFAULT section, then a section for each code environment (test, dev, qa, etc.). I see in the documentation that you recommend not forcing users to put settings in specific sections, but in this case am I defining the same variables in each section. Is there a way to have my values merge only with one section? For example, I might have:

[DEFAULT]
color = blue

[dev]
name = Simon

[qa]
color = red
name = Bill

When my environment is dev, I'm going to want color: blue, name: Simon, with the possibility of overriding either from the CLI. Am I missing this functionality, or is it something you might accept a pull request for?

Command line arguments do not override config file values

If the following scheme is used and arguments are specified both in a config-file and from command line
the correct priority is not always followed.
E.g. if we specify --testarg in the config-file and --no-testarg in the command line the result will be testarg=True. This is due to the fact that --testarg and --no-testarg are treated as distinct arguments even if they refer to the same destination.

parser.add(
        '--testarg', dest='testarg', default=None, required=False,
        action='store_true'
    )
parser.add(
        '--no-testarg', dest='testarg', default=None, required=False,
        action='store_false'
    )

ConfigFileParse does not handle action='append'

Only the last key/value pair in the file gets stored, eg:

--foo a --foo b --foo c

Will produce options.foo ['a', 'b', 'c']

But a file with:

foo = a
foo = b
foo = c

Will only produce ['c']

convert_setting_to_command_line_arg not support nargs in ('+', '*')

replace:

        elif value.startswith("[") and value.endswith("]"):
            if action is not None:
                if type(action) != argparse._AppendAction:
                    self.error(("%s can't be set to a list '%s' unless its "
                        "action type is changed to 'append'") % (key, value))
            for list_elem in value[1:-1].split(","):
                args.append( command_line_key )
                args.append( list_elem.strip() )

with:

        elif value.startswith("[") and value.endswith("]"):
            if type(action) == argparse._AppendAction:
                for list_elem in value[1:-1].split(","):
                    args.append( list_elem.strip() )
            elif type(action) == argparse._StoreAction and action.nargs in ('+', '*'):
                args.append( command_line_key )
                for list_elem in value[1:-1].split(","):
                    args.append( list_elem.strip() )
            else:
                self.error(("%s can't be set to a list '%s', unsupported action type: %s") % (key, value, type(action)))

will work around this BUG. but, I have not fully tested, so just submit as an issue not pull a request.

Pypi is not up to date 0.11.0

Hi,

Could you update Pypi, because it would be convinient as I'm using a lot Yaml configuration files?

Kind regards,

Combination of nargs='+' and type=float fails when reading from config file

I love the work you've done with this package, it has spared me from so many parsing-induced headaches.

I did, however, run into a slight problem with the nargs-option (using ConfigArgParse 0.9.3). If I have a file foo.py and I add an argument with parser.add('--bar', type=float, nargs='+'), then the following works as expected:

./foo.py --bar 1.0 2.0 3.0

However, having bar = 1.0 2.0 3.0 in the config file conf.ini does not work:

./foo.py --conf conf.ini
foo.py: error: argument --bar: invalid float value: '1.0 2.0 3.0'

As far as I understand it from reading the source code, the problem arises because config file values are passed to argparse as a single string, so the config file setting above would actually be equivalent to

./foo.py --bar "1.0 2.0 3.0"
foo.py: error: argument --bar: invalid float value: '1.0 2.0 3.0'

This of course fails since that string is not a valid float.

feature request: Better formatting of help text

Current help text is output very strangely:

  --user USER           user to login to foobar with [env var: FOOBAR_USER]
  --password PASSWORD   user password to login to foobar with [env var:
                        FOOBAR_PASSWORD]

Please consider providing a formater that formats the help text as such:

  --user USER           user to login to foobar with
                        [env var: FOOBAR_USER]
  --password PASSWORD   user password to login to foobar with
                        [env var: FOOBAR_PASSWORD]

singleton -h and --help only show the first group of options to be parsed

Using the singleton option from the readme results in the following

$ python main.py --help
usage: main.py [-h] [--utils-setting UTILS_SETTING]

optional arguments:
  -h, --help            show this help message and exit
  --utils-setting UTILS_SETTING
                        Config-file-settable option for utils (default: None)

As you can see the main options aren't added because the -h is being processed by the first p.parse_known_args. Basically this make this great feature useless.

I've tried using a named parser too and got the same result.

I think a decent fix would be to not process -h until you hit a parse_args and completely ignore it in parse_known_args, but I haven't looked at the code at all to see how feasible that is.

Change API To conform to PEP 8 names

There doesn't seem to be a good reason for violating PEP 8 for some of the API.

For example, configargparse.getArgumentParser should better be named configargparse.get_argument_parser or similar.

support for config directories

Hello,

Thanks for writing configargparse - it is great.

It would be nice to extend the software to include arbitrary config snippets in a directory without having to specify each snippet file. Perhaps adding a named parameter to the constructor:

default_config_directories = [ '/etc/software/conf-enabled' ]
default_config_directories_globs = [ '.conf', '.yaml' ]

or

default config_directories = [
{ 'directory' = '/etc/software/conf-enabled', glob = '*.conf' },
]

Pros and cons to each.

Also, a way to specify the config-directory on the command line:

config_arg_parser.add(
'--config-directory',
is_config_directory = True,
glob = '*.conf',
)

For larger software projects, it is nice to break the configurations into snippets. This would be a very useful feature. It would also allow mimicking a conf-available, conf-enabled paradigm.

Thanks for considering it!

-m

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.