From de81403412fc9bbdaf9ea9922270253ff108790a Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Mon, 3 Aug 2026 18:19:45 -0700 Subject: [PATCH 1/2] Fix: accept yes/no on --encrypt for Classic parity tabcmd Classic's `--encrypt` on `createextracts` takes an explicit yes/no value; tabcmd 2 treated it as a bare boolean flag, silently ignoring any following value. Scripts that pass `--encrypt no` on Classic ended up encrypting on tabcmd 2 because argparse consumed the "no" as a positional or bailed. Make --encrypt accept an optional yes|no|true|false argument (case-insensitive). Bare `--encrypt` still means True; omitted means False; explicit `--encrypt no` means False. This preserves current tabcmd 2 default behavior while matching Classic syntax. --- tabcmd/execution/global_options.py | 23 ++++++++++++- tests/parsers/test_parser_create_extracts.py | 36 ++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/tabcmd/execution/global_options.py b/tabcmd/execution/global_options.py index 1a24133c..a58f81f1 100644 --- a/tabcmd/execution/global_options.py +++ b/tabcmd/execution/global_options.py @@ -111,11 +111,32 @@ def set_embedded_datasources_options(parser): # used in create extract. listed in delete-extract but makes no sense there +def _parse_yes_no(value: str) -> bool: + # tabcmd Classic accepted "yes"/"no"/"true"/"false" on --encrypt; tabcmd 2 + # kept the flag but silently ignored a following value. Accept the Classic + # forms so scripts port cleanly. + lowered = value.strip().lower() + if lowered in ("yes", "true", "1"): + return True + if lowered in ("no", "false", "0"): + return False + import argparse + raise argparse.ArgumentTypeError( + "Expected yes/no/true/false for --encrypt, got {!r}".format(value) + ) + + def set_encryption_option(parser): parser.add_argument( "--encrypt", dest="encrypt", - action="store_true", # set to true IF user passes in option --encrypt + # Classic parity: --encrypt yes / --encrypt no. Bare --encrypt still + # means True (default tabcmd 2 behavior). Omitted -> False. + nargs="?", + const=True, + default=False, + type=_parse_yes_no, + metavar="yes|no", help=_("createextracts.options.encrypt"), ) return parser diff --git a/tests/parsers/test_parser_create_extracts.py b/tests/parsers/test_parser_create_extracts.py index 94ca7a7e..0486f2d8 100644 --- a/tests/parsers/test_parser_create_extracts.py +++ b/tests/parsers/test_parser_create_extracts.py @@ -106,3 +106,39 @@ def test_create_extract_parser_missing_embedded_datasources(self): ] with self.assertRaises(SystemExit): args = self.parser_under_test.parse_args(mock_args) + + # --encrypt Classic-parity: accepts yes|no|true|false, bare flag still True, omitted False + def _base_args(self): + return [commandname, "--datasource", "ds", "--project", "p", "--parent-project-path", "pp"] + + def test_encrypt_omitted_is_false(self): + args = self.parser_under_test.parse_args(self._base_args()) + assert args.encrypt is False, args + + def test_encrypt_bare_flag_is_true(self): + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt"]) + assert args.encrypt is True, args + + def test_encrypt_yes_is_true(self): + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "yes"]) + assert args.encrypt is True, args + + def test_encrypt_no_is_false(self): + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "no"]) + assert args.encrypt is False, args + + def test_encrypt_true_is_true(self): + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "true"]) + assert args.encrypt is True, args + + def test_encrypt_false_is_false(self): + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "false"]) + assert args.encrypt is False, args + + def test_encrypt_case_insensitive(self): + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "YES"]) + assert args.encrypt is True, args + + def test_encrypt_bad_value_rejected(self): + with self.assertRaises(SystemExit): + self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "maybe"]) From 01c00648e92ad521641a56d51ead179138ef7890 Mon Sep 17 00:00:00 2001 From: Jac Fitzgerald Date: Fri, 7 Aug 2026 00:00:41 -0700 Subject: [PATCH 2/2] style: apply black to satisfy CI --- tabcmd/execution/global_options.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tabcmd/execution/global_options.py b/tabcmd/execution/global_options.py index a58f81f1..e9e29009 100644 --- a/tabcmd/execution/global_options.py +++ b/tabcmd/execution/global_options.py @@ -121,9 +121,8 @@ def _parse_yes_no(value: str) -> bool: if lowered in ("no", "false", "0"): return False import argparse - raise argparse.ArgumentTypeError( - "Expected yes/no/true/false for --encrypt, got {!r}".format(value) - ) + + raise argparse.ArgumentTypeError("Expected yes/no/true/false for --encrypt, got {!r}".format(value)) def set_encryption_option(parser):