From 7f0debfb1d0ad8398b7986871bcf27188347e79a Mon Sep 17 00:00:00 2001 From: Colin Wood Date: Mon, 15 Jun 2026 15:03:43 -0700 Subject: [PATCH 1/2] render type-mapped Bool variables the standard way, tests --- q2cli/core/state.py | 13 ++++++++++++- q2cli/tests/test_cli.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/q2cli/core/state.py b/q2cli/core/state.py index 1bcf493c..64094809 100644 --- a/q2cli/core/state.py +++ b/q2cli/core/state.py @@ -102,12 +102,21 @@ def _special_option_flags(type): metadata = 'file' elif expr.name == 'MetadataColumn': metadata = 'column' - elif expr.name == 'Bool': + elif _is_bool_ast(type.to_ast()): is_bool_flag = True return multiple, is_bool_flag, metadata +def _is_bool_ast(ast): + if ast['type'] == 'expression': + return ast['name'] == 'Bool' + if ast['type'] == 'variable': + index = ast['index'] + return all(_is_bool_ast(row[index]) for row in ast['mapping']) + return False + + def _get_type_repr(type): import qiime2.sdk.util @@ -157,6 +166,8 @@ def _get_metavar(type): metavar = 'METADATA' elif style.style is not None and style.style != 'simple': metavar = 'VALUE' + elif _is_bool_ast(type.to_ast()): + metavar = '' elif qiime2.sdk.util.is_union(type): metavar = 'VALUE' else: diff --git a/q2cli/tests/test_cli.py b/q2cli/tests/test_cli.py index f2aa44d9..052a322f 100644 --- a/q2cli/tests/test_cli.py +++ b/q2cli/tests/test_cli.py @@ -233,6 +233,43 @@ def test_action_parameter_types(self): '10', '--output-dir', output_dir, '--verbose']) self.assertEqual(result.exit_code, 0) + def test_bool_typemap_parameter_is_flag(self): + qiime_cli = RootCommand() + command = qiime_cli.get_command(ctx=None, name='dummy-plugin') + + results = self.runner.invoke( + command, ['bool-flag-swaps-output-method', '--help']) + self.assertEqual(results.exit_code, 0) + self.assertIn('--p-b / --p-no-b', results.output) + + input_path = Artifact.import_data( + 'Bar', 'element', view_type=str).save( + os.path.join(self.tempdir, 'bar.qza')) + true_output_path = os.path.join(self.tempdir, 'true-output.qza') + false_output_path = os.path.join(self.tempdir, 'false-output.qza') + + result = self.runner.invoke( + command, [ + 'bool-flag-swaps-output-method', + '--i-a', input_path, + '--p-b', + '--o-x', true_output_path, + '--verbose' + ]) + self.assertEqual(result.exit_code, 0) + self.assertEqual(repr(Artifact.load(true_output_path).type), 'C1[Foo]') + + result = self.runner.invoke( + command, [ + 'bool-flag-swaps-output-method', + '--i-a', input_path, + '--p-no-b', + '--o-x', false_output_path, + '--verbose' + ]) + self.assertEqual(result.exit_code, 0) + self.assertEqual(repr(Artifact.load(false_output_path).type), 'Foo') + def test_execute_hidden_action(self): int_path = os.path.join(self.tempdir, 'int.qza') qiime_cli = RootCommand() From c525ec40beed52be5c2835123912d70f946baaef Mon Sep 17 00:00:00 2001 From: Colin Wood Date: Tue, 30 Jun 2026 15:36:28 -0700 Subject: [PATCH 2/2] suggestions, on the fly dummy action test --- q2cli/core/state.py | 14 +++++-------- q2cli/tests/test_cli.py | 46 ++++++++++++++++++++++++++++++++++------- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/q2cli/core/state.py b/q2cli/core/state.py index 64094809..1d3ce727 100644 --- a/q2cli/core/state.py +++ b/q2cli/core/state.py @@ -102,19 +102,15 @@ def _special_option_flags(type): metadata = 'file' elif expr.name == 'MetadataColumn': metadata = 'column' - elif _is_bool_ast(type.to_ast()): + elif _is_bool(type): is_bool_flag = True return multiple, is_bool_flag, metadata -def _is_bool_ast(ast): - if ast['type'] == 'expression': - return ast['name'] == 'Bool' - if ast['type'] == 'variable': - index = ast['index'] - return all(_is_bool_ast(row[index]) for row in ast['mapping']) - return False +def _is_bool(type_expr): + return all(t.name == 'Bool' for t in type_expr) \ + and not type_expr.is_bottom() def _get_type_repr(type): @@ -166,7 +162,7 @@ def _get_metavar(type): metavar = 'METADATA' elif style.style is not None and style.style != 'simple': metavar = 'VALUE' - elif _is_bool_ast(type.to_ast()): + elif _is_bool(type): metavar = '' elif qiime2.sdk.util.is_union(type): metavar = 'VALUE' diff --git a/q2cli/tests/test_cli.py b/q2cli/tests/test_cli.py index 052a322f..fd094849 100644 --- a/q2cli/tests/test_cli.py +++ b/q2cli/tests/test_cli.py @@ -19,14 +19,19 @@ from click.testing import CliRunner from qiime2.core.cache import get_cache -from qiime2.core.testing.type import IntSequence1, IntSequence2, SingleInt +from qiime2.core.testing.format import EchoFormat +from qiime2.core.testing.type import ( + Bar, C1, Foo, IntSequence1, IntSequence2, SingleInt +) from qiime2.core.testing.util import get_dummy_plugin +from qiime2.plugin import Bool, Choices, TypeMap from qiime2.sdk import Artifact, Visualization, ResultCollection from q2cli.builtin.info import info from q2cli.builtin.tools import tools -from q2cli.commands import RootCommand +from q2cli.commands import PluginCommand, RootCommand from q2cli.click.type import QIIME2Type +from q2cli.core.state import get_plugin_state CONFIG_LEVEL_2 = """[parsl] @@ -94,6 +99,30 @@ class = "_TEST_EXECUTOR_" """ # noqa: E501 +def _register_higher_union_bool_flag_action(): + def higher_union_bool_flag_swaps_output_method(a: EchoFormat, + b: bool) -> EchoFormat: + return a + + plugin = get_dummy_plugin() + action_id = 'higher_union_bool_flag_swaps_output_method' + if action_id not in plugin.actions: + P, R = TypeMap({ + Bool % Choices(True): C1[Foo], + Bool % Choices(False): Foo, + }) + plugin.methods.register_function( + function=higher_union_bool_flag_swaps_output_method, + inputs={'a': Bar}, + parameters={'b': P}, + outputs=[('x', R)], + name='Higher Union Bool Flag Swaps Output Method', + description=( + 'Test if a higher-union bool parameter can change output'), + ) + return plugin + + class CliTests(unittest.TestCase): def setUp(self): get_dummy_plugin() @@ -233,12 +262,13 @@ def test_action_parameter_types(self): '10', '--output-dir', output_dir, '--verbose']) self.assertEqual(result.exit_code, 0) - def test_bool_typemap_parameter_is_flag(self): - qiime_cli = RootCommand() - command = qiime_cli.get_command(ctx=None, name='dummy-plugin') + def test_higher_union_bool_typemap_parameter_is_flag(self): + plugin = _register_higher_union_bool_flag_action() + command = PluginCommand(get_plugin_state(plugin), 'dummy-plugin') results = self.runner.invoke( - command, ['bool-flag-swaps-output-method', '--help']) + command, [ + 'higher-union-bool-flag-swaps-output-method', '--help']) self.assertEqual(results.exit_code, 0) self.assertIn('--p-b / --p-no-b', results.output) @@ -250,7 +280,7 @@ def test_bool_typemap_parameter_is_flag(self): result = self.runner.invoke( command, [ - 'bool-flag-swaps-output-method', + 'higher-union-bool-flag-swaps-output-method', '--i-a', input_path, '--p-b', '--o-x', true_output_path, @@ -261,7 +291,7 @@ def test_bool_typemap_parameter_is_flag(self): result = self.runner.invoke( command, [ - 'bool-flag-swaps-output-method', + 'higher-union-bool-flag-swaps-output-method', '--i-a', input_path, '--p-no-b', '--o-x', false_output_path,