diff --git a/cumulusci/core/utils.py b/cumulusci/core/utils.py index 88cd570657..87ec2e0c5e 100644 --- a/cumulusci/core/utils.py +++ b/cumulusci/core/utils.py @@ -50,6 +50,22 @@ def parse_datetime(dt_str, format): return datetime(t[0], t[1], t[2], t[3], t[4], t[5], t[6], pytz.UTC) +def process_list_of_dicts_arg(arg): + """Parse an option that must be a list of dicts. + + Accepts an actual list of dicts or a JSON string encoding one, which is + how complex options arrive when passed through the CLI with ``-o``. + """ + if isinstance(arg, str): + try: + arg = json.loads(arg) + except json.JSONDecodeError as e: + raise TaskOptionsError(f"Option value is not valid JSON: {e}") + if not isinstance(arg, list) or not all(isinstance(item, dict) for item in arg): + raise TaskOptionsError("Option value must be a list of objects.") + return arg + + def process_bool_arg(arg: T.Union[int, str, None]): """Determine True/False from argument. diff --git a/cumulusci/tasks/metadata_etl/picklists.py b/cumulusci/tasks/metadata_etl/picklists.py index 8a319e7655..1c0f8150f7 100644 --- a/cumulusci/tasks/metadata_etl/picklists.py +++ b/cumulusci/tasks/metadata_etl/picklists.py @@ -3,7 +3,11 @@ from urllib.parse import unquote from cumulusci.core.exceptions import TaskOptionsError -from cumulusci.core.utils import process_bool_arg, process_list_arg +from cumulusci.core.utils import ( + process_bool_arg, + process_list_arg, + process_list_of_dicts_arg, +) from cumulusci.tasks.metadata_etl import MetadataSingleEntityTransformTask from cumulusci.utils.xml.metadata_tree import MetadataElement @@ -65,6 +69,8 @@ def _init_options(self, kwargs): self._inject_namespace(field) ) + self.options["entries"] = process_list_of_dicts_arg(self.options["entries"]) + if not all(["fullName" in entry for entry in self.options["entries"]]): raise TaskOptionsError( "The 'fullName' key is required on all picklist values." diff --git a/cumulusci/tasks/metadata_etl/tests/test_picklists.py b/cumulusci/tasks/metadata_etl/tests/test_picklists.py index f0afa460f4..3e62210e17 100644 --- a/cumulusci/tasks/metadata_etl/tests/test_picklists.py +++ b/cumulusci/tasks/metadata_etl/tests/test_picklists.py @@ -495,6 +495,29 @@ def test_init_options__missing_fullname(self): }, ) + def test_init_options__entries_as_json_string(self): + # Simulates CLI usage: -o entries '[{"fullName": "Test", "label": "Test"}]' + task = create_task( + AddPicklistEntries, + { + "api_version": "47.0", + "picklists": ["Opportunity.Type__c"], + "entries": '[{"fullName": "Test", "label": "Test"}]', + }, + ) + assert task.options["entries"] == [{"fullName": "Test", "label": "Test"}] + + def test_init_options__entries_as_invalid_json_string(self): + with pytest.raises(TaskOptionsError): + create_task( + AddPicklistEntries, + { + "api_version": "47.0", + "picklists": ["Opportunity.Type__c"], + "entries": '[{"fullName": "Test"', + }, + ) + def test_init_options__multiple_defaults(self): with pytest.raises(TaskOptionsError): create_task( diff --git a/cumulusci/tasks/metadata_etl/tests/test_value_sets.py b/cumulusci/tasks/metadata_etl/tests/test_value_sets.py index af7d537eff..cc1e4f75a2 100644 --- a/cumulusci/tasks/metadata_etl/tests/test_value_sets.py +++ b/cumulusci/tasks/metadata_etl/tests/test_value_sets.py @@ -30,6 +30,31 @@ class TestAddValueSetEntries: + def test_init_options__entries_as_json_string(self): + # Simulates CLI usage: -o entries '[{"fullName": "Test", "label": "Test"}]' + task = create_task( + AddValueSetEntries, + { + "managed": True, + "api_version": "47.0", + "api_names": "bar,foo", + "entries": '[{"fullName": "Test", "label": "Test"}]', + }, + ) + assert task.options["entries"] == [{"fullName": "Test", "label": "Test"}] + + def test_init_options__entries_as_invalid_json_string(self): + with pytest.raises(TaskOptionsError): + create_task( + AddValueSetEntries, + { + "managed": True, + "api_version": "47.0", + "api_names": "bar,foo", + "entries": '[{"fullName": "Test"', + }, + ) + def test_adds_entry(self): task = create_task( AddValueSetEntries, diff --git a/cumulusci/tasks/metadata_etl/value_sets.py b/cumulusci/tasks/metadata_etl/value_sets.py index 91d9612004..56fbb7036f 100644 --- a/cumulusci/tasks/metadata_etl/value_sets.py +++ b/cumulusci/tasks/metadata_etl/value_sets.py @@ -1,4 +1,5 @@ from cumulusci.core.exceptions import TaskOptionsError +from cumulusci.core.utils import process_list_of_dicts_arg from cumulusci.tasks.metadata_etl import MetadataSingleEntityTransformTask from cumulusci.utils.xml.metadata_tree import MetadataElement @@ -52,6 +53,11 @@ class AddValueSetEntries(MetadataSingleEntityTransformTask): }, } + def _init_options(self, kwargs): + super()._init_options(kwargs) + + self.options["entries"] = process_list_of_dicts_arg(self.options["entries"]) + def _transform_entity(self, metadata: MetadataElement, api_name: str): for entry in self.options.get("entries", []): if "fullName" not in entry or "label" not in entry: