diff --git a/.gitignore b/.gitignore index 433c5da..2a8d446 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ zdist/ build.cache .release_name .release_path +docs/source/_build/ # Byte-compiled / optimized / DLL files __pycache__/ @@ -113,4 +114,3 @@ ENV/ # mypy .mypy_cache/ -zdist/ diff --git a/ksconf/builder/__init__.py b/ksconf/builder/__init__.py index 74528f6..e564daf 100644 --- a/ksconf/builder/__init__.py +++ b/ksconf/builder/__init__.py @@ -90,7 +90,7 @@ def run(self, executable, *args, **kw_only): The process will run withing the working directory of the build folder. :param str executable: Executable to launch for a build step. - :param str *args: Additional arguments for the new process. + :param str args: Additional argument(s) for the new process. :param str cwd: Optional kw arg to change the working directory. This defaults to the build folder. """ diff --git a/ksconf/util/__init__.py b/ksconf/util/__init__.py index f40cd18..543f622 100644 --- a/ksconf/util/__init__.py +++ b/ksconf/util/__init__.py @@ -65,32 +65,42 @@ def debug_traceback(): # pragma: no cover traceback.print_exc(level) -def handle_py3_kw_only_args(kw, *args): +def handle_py3_kw_only_args(kw, *default_args): """ Python 2.7 workaround for Python 3 style kw arg after '*' arg. Example Python 3 syntax: .. code-block:: py - def f(arg, *args, a=True, b=False): ... + def f(arg, *args, a=True, b=False): + ... - Example Python 2 syntax: + Example Python 2 syntax with this helper function: .. code-block:: py def f(arg, *args, **kw_only): a, b = handle_py3_kw_only_args(kw_only, ("a", True), ("b", False)) + ... + + :param dict kw: keyword arguments provided to the calling function. Be aware + that this dict will be empty after this function is done. + :param tuple default_args: pairs of keyword argument to the caller function + in argument (arg_name, default_value) order. + :raises TypeError: if ``kw`` contains any keys not defined in ``args`` + This mirrors Python's native behavior when an unexpected + argument is passed to a function. """ out = [] - for arg_name, arg_default in args: - if arg_name in kw: + for arg_name, arg_default in default_args: + try: out.append(kw.pop(arg_name)) - else: + except KeyError: out.append(arg_default) - if args: + if kw: import inspect caller = inspect.currentframe().f_back.f_code.co_name # Should all unexpected args be reported? feels like this good enough raise TypeError("{} got an unexpected keyword argument '{}'" - .format(caller, args.keys()[0])) + .format(caller, list(kw)[0])) return out diff --git a/tests/test_util.py b/tests/test_util.py index 280c635..a8e7ecb 100755 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -24,6 +24,19 @@ def test_bwlist(self): self.assertTrue(match_bwlist("hotdog", bwlist)) self.assertTrue(match_bwlist("bake", bwlist)) + def test_handle_p3koa(self): + from ksconf.util import handle_py3_kw_only_args + kw = {"a": 1, "b": 2} + a, b, c = handle_py3_kw_only_args(kw, ("a", None), ("b", 3), ("c", 99)) + self.assertEqual(a, 1) + self.assertEqual(b, 2) + self.assertEqual(c, 99) + + # Should raise 'unexpected argument' here because 'a' is not defined + kw = {a: 1} + with self.assertRaises(TypeError): + c = handle_py3_kw_only_args(kw, ("c", 99)) + class KsconfMiscIternalsTest(unittest.TestCase):