Hello community, here is the log from the commit of package python-knack for openSUSE:Factory checked in at 2019-05-06 13:25:08 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-knack (Old) and /work/SRC/openSUSE:Factory/.python-knack.new.5148 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-knack" Mon May 6 13:25:08 2019 rev:6 rq:694216 version:0.5.4 Changes: -------- --- /work/SRC/openSUSE:Factory/python-knack/python-knack.changes 2019-03-28 22:50:17.931035821 +0100 +++ /work/SRC/openSUSE:Factory/.python-knack.new.5148/python-knack.changes 2019-05-06 13:25:09.841020349 +0200 @@ -1,0 +2,9 @@ +Fri Apr 12 12:24:44 UTC 2019 - pgaj...@suse.com + +- version update to 0.5.4 + * Allows the loading of text files using @filename syntax. + * Adds the argument kwarg configured_default to support setting + argument defaults via the config file's [defaults] section or + an environment variable. + +------------------------------------------------------------------- Old: ---- v0.5.3.tar.gz New: ---- v0.5.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-knack.spec ++++++ --- /var/tmp/diff_new_pack.iamgMo/_old 2019-05-06 13:25:11.125022974 +0200 +++ /var/tmp/diff_new_pack.iamgMo/_new 2019-05-06 13:25:11.125022974 +0200 @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-knack -Version: 0.5.3 +Version: 0.5.4 Release: 0 Summary: A Command-Line Interface framework License: MIT ++++++ v0.5.3.tar.gz -> v0.5.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/knack/cli.py new/knack-0.5.4/knack/cli.py --- old/knack-0.5.3/knack/cli.py 2019-03-06 00:03:37.000000000 +0100 +++ new/knack-0.5.4/knack/cli.py 2019-03-29 20:11:28.000000000 +0100 @@ -81,7 +81,7 @@ self._event_handlers = defaultdict(lambda: []) # Data that's typically backed to persistent storage self.config = config_cls( - config_dir=config_dir or os.path.join('~', '.{}'.format(cli_name)), + config_dir=config_dir or os.path.expanduser(os.path.join('~', '.{}'.format(cli_name))), config_env_var_prefix=config_env_var_prefix or cli_name.upper() ) # In memory collection of key-value data for this current cli. This persists between invocations. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/knack/commands.py new/knack-0.5.4/knack/commands.py --- old/knack-0.5.3/knack/commands.py 2019-03-06 00:03:37.000000000 +0100 +++ new/knack-0.5.4/knack/commands.py 2019-03-29 20:11:28.000000000 +0100 @@ -17,6 +17,7 @@ from .events import (EVENT_CMDLOADER_LOAD_COMMAND_TABLE, EVENT_CMDLOADER_LOAD_ARGUMENTS, EVENT_COMMAND_CANCELLED) from .log import get_logger +from .validators import DefaultInt, DefaultStr logger = get_logger(__name__) @@ -71,6 +72,18 @@ def should_load_description(self): return not self.cli_ctx.data['completer_active'] + def _resolve_default_value_from_config_file(self, arg, overrides): + default_key = overrides.settings.get('configured_default', None) + if not default_key: + return + + defaults_section = self.cli_ctx.config.defaults_section_name + config_value = self.cli_ctx.config.get(defaults_section, default_key, None) + if config_value: + logger.info("Configured default '%s' for arg %s", config_value, arg.name) + overrides.settings['default'] = DefaultStr(config_value) + overrides.settings['required'] = False + def load_arguments(self): if self.arguments_loader: cmd_args = self.arguments_loader() @@ -87,7 +100,20 @@ def update_argument(self, param_name, argtype): arg = self.arguments[param_name] + # resolve defaults from either environment variable or config file + self._resolve_default_value_from_config_file(arg, argtype) arg.type.update(other=argtype) + arg_default = arg.type.settings.get('default', None) + # apply DefaultStr and DefaultInt to allow distinguishing between + # when a default was applied or when the user specified a value + # that coincides with the default + if isinstance(arg_default, str): + arg_default = DefaultStr(arg_default) + elif isinstance(arg_default, int): + arg_default = DefaultInt(arg_default) + # update the default + if arg_default: + arg.type.settings['default'] = arg_default def execute(self, **kwargs): return self(**kwargs) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/knack/config.py new/knack-0.5.4/knack/config.py --- old/knack-0.5.3/knack/config.py 2019-03-06 00:03:37.000000000 +0100 +++ new/knack-0.5.4/knack/config.py 2019-03-29 20:11:28.000000000 +0100 @@ -23,6 +23,7 @@ _DEFAULT_CONFIG_ENV_VAR_PREFIX = 'CLI' _DEFAULT_CONFIG_DIR = os.path.join('~', '.{}'.format('cli')) _DEFAULT_CONFIG_FILE_NAME = 'config' + _CONFIG_DEFAULTS_SECTION = 'defaults' def __init__(self, config_dir=None, config_env_var_prefix=None, config_file_name=None): """ Manages configuration options available in the CLI @@ -44,6 +45,7 @@ configuration_file_name = config_file_name or CLIConfig._DEFAULT_CONFIG_FILE_NAME self.config_path = os.path.join(self.config_dir, configuration_file_name) self._env_var_format = '{}{}'.format(env_var_prefix, '{section}_{option}') + self.defaults_section_name = CLIConfig._CONFIG_DEFAULTS_SECTION self.config_parser.read(self.config_path) def env_var_name(self, section, option): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/knack/parser.py new/knack-0.5.4/knack/parser.py --- old/knack-0.5.3/knack/parser.py 2019-03-06 00:03:37.000000000 +0100 +++ new/knack-0.5.4/knack/parser.py 2019-03-29 20:11:28.000000000 +0100 @@ -7,8 +7,10 @@ from .deprecation import Deprecated from .events import EVENT_PARSER_GLOBAL_CREATE +from .log import get_logger from .util import CtxTypeError +logger = get_logger(__name__) # List of keyword arguments supported in argparse # from https://github.com/python/cpython/blob/master/Lib/argparse.py#L748 @@ -66,6 +68,25 @@ argparse_options['metavar'] = '<{}>'.format(argparse_options['dest'].upper()) return obj.add_argument(**argparse_options) + @staticmethod + def _expand_prefixed_files(args): + """ Load arguments prefixed with '@' from file as string + + :param args: Arguments passed from command line + :type args: list + """ + for arg, _ in enumerate(args): + if args[arg].startswith('@'): + try: + logger.debug('Attempting to read file %s', args[arg][1:]) + with open(args[arg][1:], 'r') as f: + content = f.read() + args[arg] = content + except IOError: + # Leave arg unmodified + logger.debug('File Error: Failed to open %s, assume not a file', args[arg][1:]) + return args + def __init__(self, cli_ctx=None, cli_help=None, **kwargs): """ Create the argument parser @@ -224,3 +245,12 @@ self._actions[-1] if is_group else self, is_group) self.exit() + + def parse_args(self, args=None, namespace=None): + """ Overrides argparse.ArgumentParser.parse_args + + Enables '@'-prefixed files to be expanded before arguments are processed + by ArgumentParser.parse_args as usual + """ + self._expand_prefixed_files(args) + return super(CLICommandParser, self).parse_args(args) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/knack/testsdk/patches.py new/knack-0.5.4/knack/testsdk/patches.py --- old/knack-0.5.3/knack/testsdk/patches.py 2019-03-06 00:03:37.000000000 +0100 +++ new/knack-0.5.4/knack/testsdk/patches.py 2019-03-29 20:11:28.000000000 +0100 @@ -4,7 +4,10 @@ # -------------------------------------------------------------------------------------------- import unittest -import mock +try: + import mock +except ImportError: + from unittest import mock from .exceptions import CliTestError diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/knack/validators.py new/knack-0.5.4/knack/validators.py --- old/knack-0.5.3/knack/validators.py 1970-01-01 01:00:00.000000000 +0100 +++ new/knack-0.5.4/knack/validators.py 2019-03-29 20:11:28.000000000 +0100 @@ -0,0 +1,20 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +class DefaultStr(str): + + def __new__(cls, *args, **kwargs): + instance = str.__new__(cls, *args, **kwargs) + instance.is_default = True + return instance + + +class DefaultInt(int): + + def __new__(cls, *args, **kwargs): + instance = int.__new__(cls, *args, **kwargs) + instance.is_default = True + return instance diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/setup.py new/knack-0.5.4/setup.py --- old/knack-0.5.3/setup.py 2019-03-06 00:03:37.000000000 +0100 +++ new/knack-0.5.4/setup.py 2019-03-29 20:11:28.000000000 +0100 @@ -9,7 +9,7 @@ from codecs import open from setuptools import setup, find_packages -VERSION = '0.5.3' +VERSION = '0.5.4' DEPENDENCIES = [ 'argcomplete', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/tests/test_cli_scenarios.py new/knack-0.5.4/tests/test_cli_scenarios.py --- old/knack-0.5.3/tests/test_cli_scenarios.py 2019-03-06 00:03:37.000000000 +0100 +++ new/knack-0.5.4/tests/test_cli_scenarios.py 2019-03-29 20:11:28.000000000 +0100 @@ -5,6 +5,10 @@ import os import unittest +try: + import mock +except ImportError: + from unittest import mock import mock from collections import OrderedDict diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/tests/test_command_with_configured_defaults.py new/knack-0.5.4/tests/test_command_with_configured_defaults.py --- old/knack-0.5.3/tests/test_command_with_configured_defaults.py 1970-01-01 01:00:00.000000000 +0100 +++ new/knack-0.5.4/tests/test_command_with_configured_defaults.py 2019-03-29 20:11:28.000000000 +0100 @@ -0,0 +1,90 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- +from __future__ import print_function +import os +import logging +import unittest +try: + import mock +except ImportError: + from unittest import mock +from six import StringIO +import sys + +from knack.arguments import ArgumentsContext +from knack.commands import CLICommandsLoader, CLICommand, CommandGroup +from knack.config import CLIConfig +from tests.util import DummyCLI, redirect_io + + +# a dummy callback for arg-parse +def load_params(_): + pass + + +def list_foo(my_param): + print(str(my_param), end='') + + +class TestCommandWithConfiguredDefaults(unittest.TestCase): + + @classmethod + def setUpClass(cls): + # Ensure initialization has occurred correctly + logging.basicConfig(level=logging.DEBUG) + + @classmethod + def tearDownClass(cls): + logging.shutdown() + + def _set_up_command_table(self, required): + + class TestCommandsLoader(CLICommandsLoader): + + def load_command_table(self, args): + super(TestCommandsLoader, self).load_command_table(args) + with CommandGroup(self, 'foo', '{}#{{}}'.format(__name__)) as g: + g.command('list', 'list_foo') + return self.command_table + + def load_arguments(self, command): + with ArgumentsContext(self, 'foo') as c: + c.argument('my_param', options_list='--my-param', + configured_default='param', required=required) + super(TestCommandsLoader, self).load_arguments(command) + self.cli_ctx = DummyCLI(commands_loader_cls=TestCommandsLoader) + + @mock.patch.dict(os.environ, {'CLI_DEFAULTS_PARAM': 'myVal'}) + @redirect_io + def test_apply_configured_defaults_on_required_arg(self): + self._set_up_command_table(required=True) + self.cli_ctx.invoke('foo list'.split()) + actual = self.io.getvalue() + expected = 'myVal' + self.assertEqual(expected, actual) + + @redirect_io + def test_no_configured_default_on_required_arg(self): + self._set_up_command_table(required=True) + with self.assertRaises(SystemExit): + self.cli_ctx.invoke('foo list'.split()) + actual = self.io.getvalue() + expected = 'required: --my-param' + if sys.version_info[0] == 2: + expected = 'argument --my-param is required' + self.assertEqual(expected in actual, True) + + @mock.patch.dict(os.environ, {'CLI_DEFAULTS_PARAM': 'myVal'}) + @redirect_io + def test_apply_configured_defaults_on_optional_arg(self): + self._set_up_command_table(required=False) + self.cli_ctx.invoke('foo list'.split()) + actual = self.io.getvalue() + expected = 'myVal' + self.assertEqual(expected, actual) + + +if __name__ == '__main__': + unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/tests/test_completion.py new/knack-0.5.4/tests/test_completion.py --- old/knack-0.5.3/tests/test_completion.py 2019-03-06 00:03:37.000000000 +0100 +++ new/knack-0.5.4/tests/test_completion.py 2019-03-29 20:11:28.000000000 +0100 @@ -5,7 +5,10 @@ import os import unittest -import mock +try: + import mock +except ImportError: + from unittest import mock from knack.completion import CLICompletion, CaseInsensitiveChoicesCompleter, ARGCOMPLETE_ENV_NAME from tests.util import MockContext diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/tests/test_config.py new/knack-0.5.4/tests/test_config.py --- old/knack-0.5.3/tests/test_config.py 2019-03-06 00:03:37.000000000 +0100 +++ new/knack-0.5.4/tests/test_config.py 2019-03-29 20:11:28.000000000 +0100 @@ -7,7 +7,10 @@ import stat import unittest import tempfile -import mock +try: + import mock +except ImportError: + from unittest import mock from six.moves import configparser from knack.config import CLIConfig, get_config_parser diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/tests/test_deprecation.py new/knack-0.5.4/tests/test_deprecation.py --- old/knack-0.5.3/tests/test_deprecation.py 2019-03-06 00:03:37.000000000 +0100 +++ new/knack-0.5.4/tests/test_deprecation.py 2019-03-29 20:11:28.000000000 +0100 @@ -5,16 +5,17 @@ from __future__ import unicode_literals -import sys import unittest -import mock +try: + import mock +except ImportError: + from unittest import mock from threading import Lock -from six import StringIO from knack.arguments import ArgumentsContext from knack.commands import CLICommand, CLICommandsLoader, CommandGroup -from tests.util import DummyCLI +from tests.util import DummyCLI, redirect_io def example_handler(arg1, arg2=None, arg3=None): @@ -27,20 +28,6 @@ pass -original_stdout = sys.stdout -original_stderr = sys.stderr - - -def redirect_io(func): - def wrapper(self): - sys.stdout = sys.stderr = self.io = StringIO() - func(self) - self.io.close() - sys.stdout = original_stderr - sys.stderr = original_stderr - return wrapper - - class TestCommandDeprecation(unittest.TestCase): def setUp(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/tests/test_log.py new/knack-0.5.4/tests/test_log.py --- old/knack-0.5.3/tests/test_log.py 2019-03-06 00:03:37.000000000 +0100 +++ new/knack-0.5.4/tests/test_log.py 2019-03-29 20:11:28.000000000 +0100 @@ -4,7 +4,10 @@ # -------------------------------------------------------------------------------------------- import unittest -import mock +try: + import mock +except ImportError: + from unittest import mock import logging import colorama diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/tests/test_parser.py new/knack-0.5.4/tests/test_parser.py --- old/knack-0.5.3/tests/test_parser.py 2019-03-06 00:03:37.000000000 +0100 +++ new/knack-0.5.4/tests/test_parser.py 2019-03-29 20:11:28.000000000 +0100 @@ -133,6 +133,34 @@ parser = CLICommandParser() parser.load_command_table(self.mock_ctx.commands_loader) + def test_prefix_file_expansion(self): + import json, os + + def test_handler(): + pass + + def create_test_file(file, contents): + with open(file, 'w') as f: + f.write(contents) + + def remove_test_file(file): + os.remove(file) + + json_test_data = json.dumps({'one': 1, 'two': 2, 'three': 3}) + create_test_file('test.json', json_test_data) + + command = CLICommand(self.mock_ctx, 'test command', test_handler) + command.add_argument('json_data', '--param') + cmd_table = {'test command': command} + self.mock_ctx.commands_loader.command_table = cmd_table + parser = CLICommandParser() + parser.load_command_table(self.mock_ctx.commands_loader) + + args = parser.parse_args('test command --param @test.json'.split()) + self.assertEqual(json_test_data, args.json_data) + + remove_test_file('test.json') + class VerifyError(object): # pylint: disable=too-few-public-methods diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/tests/test_prompting.py new/knack-0.5.4/tests/test_prompting.py --- old/knack-0.5.3/tests/test_prompting.py 2019-03-06 00:03:37.000000000 +0100 +++ new/knack-0.5.4/tests/test_prompting.py 2019-03-29 20:11:28.000000000 +0100 @@ -5,7 +5,10 @@ import sys import unittest -import mock +try: + import mock +except ImportError: + from unittest import mock from six import StringIO from knack.prompting import (verify_is_a_tty, NoTTYException, _INVALID_PASSWORD_MSG, prompt, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/knack-0.5.3/tests/util.py new/knack-0.5.4/tests/util.py --- old/knack-0.5.3/tests/util.py 2019-03-06 00:03:37.000000000 +0100 +++ new/knack-0.5.4/tests/util.py 2019-03-29 20:11:28.000000000 +0100 @@ -3,11 +3,29 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -import mock +try: + import mock +except ImportError: + from unittest import mock +import sys import tempfile +from six import StringIO from knack.cli import CLI, CLICommandsLoader, CommandInvoker +def redirect_io(func): + + original_stderr = sys.stderr + original_stdout = sys.stdout + + def wrapper(self): + sys.stdout = sys.stderr = self.io = StringIO() + func(self) + self.io.close() + sys.stdout = original_stderr + sys.stderr = original_stderr + return wrapper + class MockContext(CLI):