跳转至

ConfigParser

Bases: ArgumentParser

Parser to parse command-line arguments for CHANfiG.

ConfigParser is a subclass of argparse.ArgumentParser. It provides a new parse method to parse command-line arguments to CHANfiG.Config object.

Different to ArgumentParser.parse_args, ConfigParser.parse will try to parse any command-line arguments, even if they are not pre-defined by ArgumentParser.add_argument. This allows to relief the burden of adding tons of arguments for each tuneable parameter. In the meantime, there is no mechanism to notify you if you made a typo in command-line arguments.

Note that ArgumentParser.parse_args method is not overridden in ConfigParser. This is because it is still possible to construct CHANfiG.Config with ArgumentParser.parse_args, which has strict checking on command-line arguments.

Source code in chanfig/config.py
Python
class ConfigParser(ArgumentParser):  # pylint: disable=C0115
    r"""
    Parser to parse command-line arguments for CHANfiG.

    `ConfigParser` is a subclass of `argparse.ArgumentParser`.
    It provides a new `parse` method to parse command-line arguments to `CHANfiG.Config` object.

    Different to `ArgumentParser.parse_args`, `ConfigParser.parse` will try to parse any command-line arguments,
    even if they are not pre-defined by `ArgumentParser.add_argument`.
    This allows to relief the burden of adding tons of arguments for each tuneable parameter.
    In the meantime, there is no mechanism to notify you if you made a typo in command-line arguments.

    Note that `ArgumentParser.parse_args` method is not overridden in `ConfigParser`.
    This is because it is still possible to construct `CHANfiG.Config` with `ArgumentParser.parse_args`,
    which has strict checking on command-line arguments.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._registries["action"][None] = StoreAction
        self._registries["action"]["store"] = StoreAction

    def parse(  # pylint: disable=R0912
        self,
        args: Optional[Sequence[str]] = None,
        config: Optional[Config] = None,
        default_config: Optional[str] = None,
        no_default_config_action: str = "raise",
    ) -> Config:
        r"""
        Parse the arguments for `Config`.

        You may optionally specify a name for `default_config`,
        and CHANfiG will read the file under this name.

        There are three levels of config:

        1. The base `Config` parsed into the function,
        2. The base config file located at the path of `default_config` (if specified),
        3. The config specified in arguments.

        Higher levels override lower levels (i.e. 3 > 2 > 1).

        Args:
            args: The arguments to parse.
                Defaults to sys.argv[1:].
            config: The base `Config`.
            default_config: Path to the base config file.
            no_default_config_action: What to do when `default_config` is specified but not found in args.

        Returns:
            config: The parsed `Config`.

        Raises:
            ValueError: If `default_config` is specified but not found in args,
                and `no_default_config_action` is neither `warn` nor `igonre`.
            ValueError: If `no_default_config_action` is not in `raise`, `warn` and `igonre`.

        Examples:
            >>> p = ConfigParser()
            >>> p.parse(['--i.d', '1013', '--f.n', 'chang']).dict()
            {'i': {'d': 1013}, 'f': {'n': 'chang'}}

            Values in command line overrides values in `default_config` file.
            >>> p = ConfigParser()
            >>> p.parse(['--a', '2', '--config', 'example.yaml'], default_config='config').dict()
            {'a': 2, 'b': 2, 'c': 3, 'config': 'example.yaml'}

            Values in `default_config` file overrides values in `Config` object.
            >>> c = Config(a=2)
            >>> c.parse(['--config', 'example.yaml'], default_config='config').dict()
            {'a': 1, 'b': 2, 'c': 3, 'config': 'example.yaml'}

            ValueError will be raised when `default_config` is specified but not presented in command line.
            >>> p = ConfigParser()
            >>> p.parse(['--a', '2'], default_config='config').dict()
            Traceback (most recent call last):
            ValueError: default_config is set to config, but not found in args.

            ValueError will be suppressed when `default_config` is specified bug not presented in command line,
            and `no_default_config_action` is set to `ignore` or `warn`.
            >>> p = ConfigParser()
            >>> p.parse(['--a', '2'], default_config='config', no_default_config_action='ignore').dict()
            {'a': 2}

            ValueError will be raised when `no_default_config_action` is not in `raise`, `ignore`, and `warn`.
            >>> p = ConfigParser()
            >>> p.parse(['--a', '2'], default_config='config', no_default_config_action='suppress').dict()
            Traceback (most recent call last):
            ValueError: no_default_config_action must be one of 'warn', 'ignore', 'raise', bug got suppress
        """

        if no_default_config_action not in ("warn", "ignore", "raise"):
            raise ValueError(
                f"no_default_config_action must be one of 'warn', 'ignore', 'raise', bug got {no_default_config_action}"
            )

        if args is None:
            args = sys.argv[1:]
        key_value_args = []
        for arg in args:
            if args == "--":
                break
            if arg.startswith("--"):
                key_value_args.append(arg.split("=", maxsplit=1))
            else:
                if not key_value_args:
                    continue
                key_value_args[-1].append(arg)
        for key_value in key_value_args:
            if key_value[0] not in self._option_string_actions:
                if len(key_value) > 2:
                    self.add_argument(key_value[0], nargs="+")
                else:
                    self.add_argument(key_value[0])
        if config is None:
            config = Config()
        namespace = config.clone()
        if "help" not in namespace:
            namespace.help = Null
        parsed: dict = self.parse_args(args)  # type: ignore
        if isinstance(parsed, Namespace):
            parsed = vars(parsed)
        if not isinstance(parsed, NestedDict):
            parsed = NestedDict(parsed)
        parsed = parsed.dropnull()

        # parse the config file
        if default_config is not None:
            if default_config in parsed:
                path = parsed[default_config]
                warn(f"Config has 'default_config={path}' specified, its values will override values in Config")
                # create a temp config to avoid issues when users inherit from Config
                config = config.merge(Config.load(path))  # type: ignore
            elif no_default_config_action == "ignore":
                pass
            elif no_default_config_action == "warn":
                warn(f"default_config is set to {default_config}, but not found in args.")
            else:
                raise ValueError(f"default_config is set to {default_config}, but not found in args.")

        # parse the command-line arguments
        config = config.merge(parsed)  # type: ignore
        return config  # type: ignore

    parse_config = parse

    @staticmethod
    def identity(string):
        r"""
        https://stackoverflow.com/questions/69896931/cant-pickle-local-object-argumentparser-init-locals-identity
        """

        return string

parse(args=None, config=None, default_config=None, no_default_config_action='raise')

Parse the arguments for Config.

You may optionally specify a name for default_config, and CHANfiG will read the file under this name.

There are three levels of config:

  1. The base Config parsed into the function,
  2. The base config file located at the path of default_config (if specified),
  3. The config specified in arguments.

Higher levels override lower levels (i.e. 3 > 2 > 1).

Parameters:

Name Type Description Default
args Optional[Sequence[str]]

The arguments to parse. Defaults to sys.argv[1:].

None
config Optional[Config]

The base Config.

None
default_config Optional[str]

Path to the base config file.

None
no_default_config_action str

What to do when default_config is specified but not found in args.

'raise'

Returns:

Name Type Description
config Config

The parsed Config.

Raises:

Type Description
ValueError

If default_config is specified but not found in args, and no_default_config_action is neither warn nor igonre.

ValueError

If no_default_config_action is not in raise, warn and igonre.

Examples:

Python Console Session
>>> p = ConfigParser()
>>> p.parse(['--i.d', '1013', '--f.n', 'chang']).dict()
{'i': {'d': 1013}, 'f': {'n': 'chang'}}

Values in command line overrides values in default_config file.

Python Console Session
>>> p = ConfigParser()
>>> p.parse(['--a', '2', '--config', 'example.yaml'], default_config='config').dict()
{'a': 2, 'b': 2, 'c': 3, 'config': 'example.yaml'}

Values in default_config file overrides values in Config object.

Python Console Session
>>> c = Config(a=2)
>>> c.parse(['--config', 'example.yaml'], default_config='config').dict()
{'a': 1, 'b': 2, 'c': 3, 'config': 'example.yaml'}

ValueError will be raised when default_config is specified but not presented in command line.

Python Console Session
>>> p = ConfigParser()
>>> p.parse(['--a', '2'], default_config='config').dict()
Traceback (most recent call last):
ValueError: default_config is set to config, but not found in args.

ValueError will be suppressed when default_config is specified bug not presented in command line, and no_default_config_action is set to ignore or warn.

Python Console Session
>>> p = ConfigParser()
>>> p.parse(['--a', '2'], default_config='config', no_default_config_action='ignore').dict()
{'a': 2}

ValueError will be raised when no_default_config_action is not in raise, ignore, and warn.

Python Console Session
>>> p = ConfigParser()
>>> p.parse(['--a', '2'], default_config='config', no_default_config_action='suppress').dict()
Traceback (most recent call last):
ValueError: no_default_config_action must be one of 'warn', 'ignore', 'raise', bug got suppress
Source code in chanfig/config.py
Python
def parse(  # pylint: disable=R0912
    self,
    args: Optional[Sequence[str]] = None,
    config: Optional[Config] = None,
    default_config: Optional[str] = None,
    no_default_config_action: str = "raise",
) -> Config:
    r"""
    Parse the arguments for `Config`.

    You may optionally specify a name for `default_config`,
    and CHANfiG will read the file under this name.

    There are three levels of config:

    1. The base `Config` parsed into the function,
    2. The base config file located at the path of `default_config` (if specified),
    3. The config specified in arguments.

    Higher levels override lower levels (i.e. 3 > 2 > 1).

    Args:
        args: The arguments to parse.
            Defaults to sys.argv[1:].
        config: The base `Config`.
        default_config: Path to the base config file.
        no_default_config_action: What to do when `default_config` is specified but not found in args.

    Returns:
        config: The parsed `Config`.

    Raises:
        ValueError: If `default_config` is specified but not found in args,
            and `no_default_config_action` is neither `warn` nor `igonre`.
        ValueError: If `no_default_config_action` is not in `raise`, `warn` and `igonre`.

    Examples:
        >>> p = ConfigParser()
        >>> p.parse(['--i.d', '1013', '--f.n', 'chang']).dict()
        {'i': {'d': 1013}, 'f': {'n': 'chang'}}

        Values in command line overrides values in `default_config` file.
        >>> p = ConfigParser()
        >>> p.parse(['--a', '2', '--config', 'example.yaml'], default_config='config').dict()
        {'a': 2, 'b': 2, 'c': 3, 'config': 'example.yaml'}

        Values in `default_config` file overrides values in `Config` object.
        >>> c = Config(a=2)
        >>> c.parse(['--config', 'example.yaml'], default_config='config').dict()
        {'a': 1, 'b': 2, 'c': 3, 'config': 'example.yaml'}

        ValueError will be raised when `default_config` is specified but not presented in command line.
        >>> p = ConfigParser()
        >>> p.parse(['--a', '2'], default_config='config').dict()
        Traceback (most recent call last):
        ValueError: default_config is set to config, but not found in args.

        ValueError will be suppressed when `default_config` is specified bug not presented in command line,
        and `no_default_config_action` is set to `ignore` or `warn`.
        >>> p = ConfigParser()
        >>> p.parse(['--a', '2'], default_config='config', no_default_config_action='ignore').dict()
        {'a': 2}

        ValueError will be raised when `no_default_config_action` is not in `raise`, `ignore`, and `warn`.
        >>> p = ConfigParser()
        >>> p.parse(['--a', '2'], default_config='config', no_default_config_action='suppress').dict()
        Traceback (most recent call last):
        ValueError: no_default_config_action must be one of 'warn', 'ignore', 'raise', bug got suppress
    """

    if no_default_config_action not in ("warn", "ignore", "raise"):
        raise ValueError(
            f"no_default_config_action must be one of 'warn', 'ignore', 'raise', bug got {no_default_config_action}"
        )

    if args is None:
        args = sys.argv[1:]
    key_value_args = []
    for arg in args:
        if args == "--":
            break
        if arg.startswith("--"):
            key_value_args.append(arg.split("=", maxsplit=1))
        else:
            if not key_value_args:
                continue
            key_value_args[-1].append(arg)
    for key_value in key_value_args:
        if key_value[0] not in self._option_string_actions:
            if len(key_value) > 2:
                self.add_argument(key_value[0], nargs="+")
            else:
                self.add_argument(key_value[0])
    if config is None:
        config = Config()
    namespace = config.clone()
    if "help" not in namespace:
        namespace.help = Null
    parsed: dict = self.parse_args(args)  # type: ignore
    if isinstance(parsed, Namespace):
        parsed = vars(parsed)
    if not isinstance(parsed, NestedDict):
        parsed = NestedDict(parsed)
    parsed = parsed.dropnull()

    # parse the config file
    if default_config is not None:
        if default_config in parsed:
            path = parsed[default_config]
            warn(f"Config has 'default_config={path}' specified, its values will override values in Config")
            # create a temp config to avoid issues when users inherit from Config
            config = config.merge(Config.load(path))  # type: ignore
        elif no_default_config_action == "ignore":
            pass
        elif no_default_config_action == "warn":
            warn(f"default_config is set to {default_config}, but not found in args.")
        else:
            raise ValueError(f"default_config is set to {default_config}, but not found in args.")

    # parse the command-line arguments
    config = config.merge(parsed)  # type: ignore
    return config  # type: ignore

identity(string) staticmethod

Source code in chanfig/config.py
Python
@staticmethod
def identity(string):
    r"""
    https://stackoverflow.com/questions/69896931/cant-pickle-local-object-argumentparser-init-locals-identity
    """

    return string

最后更新: 2023-05-20 15:08:43