Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement disabling of command line arguments in config #156

Merged
merged 6 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@ Changelog
=========


dev (not yet released)
~~~~~~~~~~~~~~~~~~~~~~
.. _changes-1_4_0:

1.4.0 (not yet released)
~~~~~~~~~~~~~~~~~~~~~~~~

New features
------------

+ `#155`_, `#156`_: Add an option to disable parsing of command line
arguments in :class:`icat.config.Config`.

Bug fixes and minor changes
---------------------------
Expand All @@ -13,6 +21,8 @@ Bug fixes and minor changes

.. _#152: https://github.com/icatproject/python-icat/pull/152
.. _#154: https://github.com/icatproject/python-icat/pull/154
.. _#155: https://github.com/icatproject/python-icat/issues/155
.. _#156: https://github.com/icatproject/python-icat/pull/156


.. _changes-1_3_0:
Expand Down
69 changes: 50 additions & 19 deletions src/icat/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,16 @@ def get(self, variable):
raise NotImplementedError


class ConfigSourceDisabled():
"""A disabled configuration source.

Do nothing and return :const:`None` for each variable to signal
that this variable is not set in this source.
"""
def get(self, variable):
return None


class ConfigSourceCmdArgs(ConfigSource):
"""Get configuration from command line arguments.
"""
Expand Down Expand Up @@ -490,7 +500,7 @@ def add_variable(self, name, arg_opts=(), arg_kws=None,
:type subst: :class:`bool`
:return: the new configuration variable object.
:rtype: :class:`icat.config.ConfigVariable`
:raise RuntimeError: if this objects already has subcommands
:raise RuntimeError: if this config object already has subcommands
defined with :meth:`icat.config.BaseConfig.add_subcommands`.
:raise ValueError: if the name is not valid.
:see: the documentation of the :mod:`argparse` standard
Expand All @@ -502,6 +512,16 @@ def add_variable(self, name, arg_opts=(), arg_kws=None,
raise ValueError("Config variable name '%s' is reserved." % name)
if name in self.confvariable:
raise ValueError("Config variable '%s' is already defined." % name)
if self.argparser:
self._add_argparser_argument(name, arg_opts, arg_kws, default, type)
if type == flag:
type = boolean
var = ConfigVariable(name, envvar, optional, default, type, subst)
self.confvariable[name] = var
self.confvariables.append(var)
return var

def _add_argparser_argument(self, name, arg_opts, arg_kws, default, type):
if arg_kws is None:
arg_kws = dict()
else:
Expand Down Expand Up @@ -529,7 +549,6 @@ def add_variable(self, name, arg_opts=(), arg_kws=None,
arg_kws['const'] = False
arg_kws['help'] = argparse.SUPPRESS
self.argparser.add_argument("--no-"+arg, **arg_kws)
type = boolean
elif arg_opts:
prefix = self.argparser.prefix_chars
if len(arg_opts) == 1 and arg_opts[0][0] not in prefix:
Expand All @@ -542,10 +561,6 @@ def add_variable(self, name, arg_opts=(), arg_kws=None,
# optional argument
arg_kws['dest'] = name
self.argparser.add_argument(*arg_opts, **arg_kws)
var = ConfigVariable(name, envvar, optional, default, type, subst)
self.confvariable[name] = var
self.confvariables.append(var)
return var

def add_subcommands(self, name='subcmd', arg_kws=None, optional=False):
"""Defines a new configuration variable to select subcommands.
Expand Down Expand Up @@ -576,11 +591,16 @@ def add_subcommands(self, name='subcmd', arg_kws=None, optional=False):
:type optional: :class:`bool`
:return: the new subcommand object.
:rtype: :class:`icat.config.ConfigSubCmd`
:raise RuntimeError: if this objects already has subcommands.
:raise RuntimeError: if parsing of command line arguments is
disabled in this config object or if it already has
subcommands.
:raise ValueError: if the name is not valid.
:see: the documentation of the :mod:`argparse` standard
library module for details on `arg_kws`.
"""
if not self.argparser:
raise RuntimeError("Command line parsing is disabled "
"in this config, cannot add subcommands.")
if self._subcmds is not None:
raise RuntimeError("This config already has subcommands.")
if name in self.ReservedVariables or name[0] == '_':
Expand Down Expand Up @@ -618,12 +638,12 @@ def _getconfig(self, sources, config=None):
if value is not None and var.subst:
value = value % config.as_dict()
setattr(config, var.name, value)
if var.postprocess:
var.postprocess(self, config)
if isinstance(var, ConfigSubCmds):
if value is not None:
value._getconfig(sources, config)
break
if var.postprocess:
var.postprocess(self, config)
return config


Expand Down Expand Up @@ -662,21 +682,30 @@ class Config(BaseConfig):
environment variables, and settings in the configuration files
still take precedence over the preset values.
:type preset: :class:`dict`
:param args: list of command line arguments or :const:`None`. If
not set, the command line arguments will be taken from
:data:`sys.argv`.
:type args: :class:`list` of :class:`str`
:param args: list of command line arguments. If set to the
special value :const:`False`, parsing of command line
arguments will be disabled. The default, if :const:`None` is
to take the command line arguments from :data:`sys.argv`.
:type args: :class:`list` of :class:`str` or :class:`bool`

.. versionchanged:: 1.0.0
add the `preset` argument.

.. versionchanged:: 1.4.0
allow to disable parsing of command line arguments, setting
`args` to :const:`False`.
"""

def __init__(self, defaultvars=True, needlogin=True, ids="optional",
preset=None, args=None):
"""Initialize the object.
"""
super().__init__(argparse.ArgumentParser())
self.cmdargs = ConfigSourceCmdArgs(self.argparser)
if args is False:
super().__init__(None)
self.cmdargs = ConfigSourceDisabled()
else:
super().__init__(argparse.ArgumentParser())
self.cmdargs = ConfigSourceCmdArgs(self.argparser)
self.environ = ConfigSourceEnvironment()
defaultFiles = [str(d / cfgfile) for d in cfgdirs]
self.conffile = ConfigSourceFile(defaultFiles)
Expand Down Expand Up @@ -731,7 +760,8 @@ def getconfig(self):
configuration file, if an invalid value is given to a
variable, or if a mandatory variable is not defined.
"""
self.cmdargs.parse_args(self.args)
if self.argparser:
self.cmdargs.parse_args(self.args)
config = self._getconfig(self.sources)

if self.needlogin:
Expand Down Expand Up @@ -841,9 +871,10 @@ def _setup_client(self):
"""Initialize the client.
"""
try:
with _argparserDisableExit(self.argparser):
self.cmdargs.parse_args(self.args, partial=True)
config = self._getconfig(self.sources)
if self.argparser:
with _argparserDisableExit(self.argparser):
self.cmdargs.parse_args(self.args, partial=True)
config = self._getconfig(self.sources)
except ConfigError:
return None, None
client_kwargs = {}
Expand Down
Loading