def test_parse_kfp_component_file_no_inputs():
    # Define the appropriate reader for a filesystem-type component definition
    kfp_supported_file_types = [".yaml"]
    reader = FilesystemComponentCatalogConnector(kfp_supported_file_types)

    # Read contents of given path
    path = _get_resource_path("kfp_test_operator_no_inputs.yaml")
    catalog_entry_data = {"path": path}

    # Construct a catalog instance
    catalog_type = "local-file-catalog"
    catalog_instance = ComponentCatalogMetadata(schema_name=catalog_type,
                                                metadata={
                                                    "categories": ["Test"],
                                                    "runtime_type":
                                                    RUNTIME_PROCESSOR.name
                                                })

    # Build the catalog entry data structures required for parsing
    entry_data = reader.get_entry_data(catalog_entry_data, {})
    catalog_entry = CatalogEntry(entry_data, catalog_entry_data,
                                 catalog_instance, ["path"])

    # Parse the component entry
    parser = KfpComponentParser.create_instance(platform=RUNTIME_PROCESSOR)
    component = parser.parse(catalog_entry)[0]
    properties_json = ComponentCache.to_canvas_properties(component)

    # Properties JSON should only include the two parameters common to every
    # component:'label' and 'component_source', the component description if
    # exists (which it does for this component), and the output parameter for
    # this component
    num_common_params = 4
    assert len(
        properties_json["current_parameters"].keys()) == num_common_params
    assert len(properties_json["parameters"]) == num_common_params
    assert len(
        properties_json["uihints"]["parameter_info"]) == num_common_params

    # Total number of groups includes one for each parameter,
    # plus one for the output group header,
    # plus 1 for the component_source header
    num_groups = num_common_params + 2
    assert len(properties_json["uihints"]["group_info"][0]
               ["group_info"]) == num_groups

    # Ensure that template still renders the two common parameters correctly
    assert properties_json["current_parameters"]["label"] == ""

    component_source = json.dumps({
        "catalog_type":
        catalog_type,
        "component_ref":
        catalog_entry.entry_reference
    })
    assert properties_json["current_parameters"][
        "component_source"] == component_source
Example #2
0
    async def get(self, runtime_type, component_id):
        self.log.debug(
            f"Retrieving pipeline component properties for component: {component_id}"
        )

        if not component_id:
            raise web.HTTPError(400, "Missing component ID")

        processor_manager = PipelineProcessorManager.instance()
        if processor_manager.is_supported_runtime(runtime_type):
            # The endpoint path contains the shorthand version of a runtime (e.g., 'kfp',
            # 'airflow'). This case and its associated functions should eventually be removed
            # in favor of using the RuntimeProcessorType name in the request path.
            self.log.warning(
                f"Deprecation warning: when calling endpoint '{self.request.path}' "
                f"use runtime type name (e.g. 'KUBEFLOW_PIPELINES', 'APACHE_AIRFLOW') "
                f"instead of shorthand name (e.g., 'kfp', 'airflow')")
            runtime_processor_type = processor_manager.get_runtime_type(
                runtime_type)
        elif processor_manager.is_supported_runtime_type(runtime_type):
            # The request path uses the appropriate RuntimeProcessorType name. Use this
            # to get the RuntimeProcessorType instance to pass to get_component
            runtime_processor_type = RuntimeProcessorType.get_instance_by_name(
                runtime_type)
        else:
            raise web.HTTPError(400, f"Invalid runtime type '{runtime_type}'")

        # Try to get component_id as a generic component; assigns None if id is not a generic component
        component: Optional[Component] = ComponentCache.get_generic_component(
            component_id)

        # Try to retrieve a runtime-type-specific component; assigns None if not found
        if not component:
            component = ComponentCache.instance().get_component(
                platform=runtime_processor_type, component_id=component_id)

        if not component:
            raise web.HTTPError(404, f"Component '{component_id}' not found")

        if self.request.path.endswith("/properties"):
            # Return complete set of component properties
            json_response = ComponentCache.to_canvas_properties(component)
        else:
            # Return component definition content
            json_response = json.dumps({
                "content":
                component.definition,
                "mimeType":
                self.get_mimetype(component.file_extension)
            })

        self.set_status(200)
        self.set_header("Content-Type", "application/json")
        await self.finish(json_response)
def test_parse_kfp_component_url():
    # Define the appropriate reader for a URL-type component definition
    kfp_supported_file_types = [".yaml"]
    reader = UrlComponentCatalogConnector(kfp_supported_file_types)

    # Read contents of given path
    url = "https://raw.githubusercontent.com/kubeflow/pipelines/1.4.1/components/notebooks/Run_notebook_using_papermill/component.yaml"  # noqa: E501
    catalog_entry_data = {"url": url}

    # Construct a catalog instance
    catalog_type = "url-catalog"
    catalog_instance = ComponentCatalogMetadata(schema_name=catalog_type,
                                                metadata={
                                                    "categories": ["Test"],
                                                    "runtime_type":
                                                    RUNTIME_PROCESSOR.name
                                                })

    # Build the catalog entry data structures required for parsing
    entry_data = reader.get_entry_data(catalog_entry_data, {})
    catalog_entry = CatalogEntry(entry_data, catalog_entry_data,
                                 catalog_instance, ["url"])

    # Parse the component entry
    parser = KfpComponentParser.create_instance(platform=RUNTIME_PROCESSOR)
    component = parser.parse(catalog_entry)[0]
    properties_json = ComponentCache.to_canvas_properties(component)

    # Ensure component parameters are prefixed (and system parameters are not) and all hold correct values
    assert properties_json["current_parameters"]["label"] == ""

    component_source = json.dumps({
        "catalog_type":
        catalog_type,
        "component_ref":
        catalog_entry.entry_reference
    })
    assert properties_json["current_parameters"][
        "component_source"] == component_source
    assert properties_json["current_parameters"][
        "elyra_notebook"] == "None"  # Default value for type `inputpath`
    assert properties_json["current_parameters"]["elyra_parameters"] == {
        "StringControl": "{}",
        "activeControl": "StringControl",
    }
    assert properties_json["current_parameters"][
        "elyra_packages_to_install"] == {
            "StringControl": "[]",
            "activeControl": "StringControl",
        }
    assert properties_json["current_parameters"]["elyra_input_data"] == {
        "StringControl": "",
        "activeControl": "StringControl",
    }
def test_parse_airflow_component_url():
    # Define the appropriate reader for a URL-type component definition
    airflow_supported_file_types = [".py"]
    reader = UrlComponentCatalogConnector(airflow_supported_file_types)

    # Read contents of given path
    url = "https://raw.githubusercontent.com/apache/airflow/1.10.15/airflow/operators/bash_operator.py"  # noqa: E501
    catalog_entry_data = {"url": url}

    # Construct a catalog instance
    catalog_type = "url-catalog"
    catalog_instance = ComponentCatalogMetadata(schema_name=catalog_type,
                                                metadata={
                                                    "categories": ["Test"],
                                                    "runtime_type":
                                                    RUNTIME_PROCESSOR.name
                                                })

    # Build the catalog entry data structures required for parsing
    entry_data = reader.get_entry_data(catalog_entry_data, {})
    catalog_entry = CatalogEntry(entry_data, catalog_entry_data,
                                 catalog_instance, ["url"])

    # Parse the component entry
    parser = ComponentParser.create_instance(platform=RUNTIME_PROCESSOR)
    component = parser.parse(catalog_entry)[0]
    properties_json = ComponentCache.to_canvas_properties(component)

    # Ensure component parameters are prefixed, and system parameters are not, and hold correct values
    assert properties_json["current_parameters"]["label"] == ""

    # Helper method to retrieve the requested parameter value from the dictionary
    def get_parameter(param_name):
        property_dict = properties_json["current_parameters"][param_name]
        return property_dict[property_dict["activeControl"]]

    component_source = json.dumps({
        "catalog_type":
        catalog_type,
        "component_ref":
        catalog_entry.entry_reference
    })
    assert properties_json["current_parameters"][
        "component_source"] == component_source
    assert get_parameter("elyra_bash_command") == ""
    assert get_parameter("elyra_xcom_push") is True
    assert get_parameter("elyra_env") == "{}"  # {}
    assert get_parameter("elyra_output_encoding") == "utf-8"
Example #5
0
    async def get(self, runtime_type, component_id):
        self.log.debug(
            f"Retrieving pipeline component properties for component: {component_id}"
        )

        if not component_id:
            raise web.HTTPError(400, "Missing component ID")

        runtime_processor_type = get_runtime_processor_type(
            runtime_type, self.log, self.request.path)
        if not runtime_processor_type:
            raise web.HTTPError(400, f"Invalid runtime type '{runtime_type}'")

        # Try to get component_id as a generic component; assigns None if id is not a generic component
        component: Optional[Component] = ComponentCache.get_generic_component(
            component_id)

        # Try to retrieve a runtime-type-specific component; assigns None if not found
        if not component:
            component = ComponentCache.instance().get_component(
                platform=runtime_processor_type, component_id=component_id)

        if not component:
            raise web.HTTPError(404, f"Component '{component_id}' not found")

        if self.request.path.endswith("/properties"):
            # Return complete set of component properties
            json_response = ComponentCache.to_canvas_properties(component)
        else:
            # Return component definition content
            json_response = json.dumps({
                "content":
                component.definition,
                "mimeType":
                self.get_mimetype(component.file_extension)
            })

        self.set_status(200)
        self.set_header("Content-Type", "application/json")
        await self.finish(json_response)
Example #6
0
    async def _get_component_properties(self, pipeline_runtime: str, components: dict, node_op: str) -> Dict:
        """
        Retrieve the full dict of properties associated with the node_op
        :param components: list of components associated with the pipeline runtime being used e.g. kfp, airflow
        :param node_op: the node operation e.g. execute-notebook-node
        :return: a list of property names associated with the node op
        """

        if node_op == "execute-notebook-node":
            node_op = "notebooks"
        elif node_op == "execute-r-node":
            node_op = "r-script"
        elif node_op == "execute-python-node":
            node_op = "python-script"
        for category in components["categories"]:
            for node_type in category["node_types"]:
                if node_op == node_type["op"]:
                    component: Component = await PipelineProcessorManager.instance().get_component(
                        pipeline_runtime, node_op
                    )
                    component_properties = ComponentCache.to_canvas_properties(component)
                    return component_properties

        return {}
def test_parse_airflow_component_file():
    # Define the appropriate reader for a filesystem-type component definition
    airflow_supported_file_types = [".py"]
    reader = FilesystemComponentCatalogConnector(airflow_supported_file_types)

    # Read contents of given path
    path = _get_resource_path("airflow_test_operator.py")
    catalog_entry_data = {"path": path}

    # Construct a catalog instance
    catalog_type = "local-file-catalog"
    catalog_instance = ComponentCatalogMetadata(schema_name=catalog_type,
                                                metadata={
                                                    "categories": ["Test"],
                                                    "runtime_type":
                                                    RUNTIME_PROCESSOR.name
                                                })

    # Build the catalog entry data structures required for parsing
    entry_data = reader.get_entry_data(catalog_entry_data, {})
    catalog_entry = CatalogEntry(entry_data, catalog_entry_data,
                                 catalog_instance, ["path"])

    # Parse the component entry
    parser = ComponentParser.create_instance(platform=RUNTIME_PROCESSOR)
    components = parser.parse(catalog_entry)
    assert len(
        components
    ) == 3  # TestOperator, DeriveFromTestOperator, and DeriveFromImportedOperator

    # Split components list into its constituent operators
    components = sorted(components, key=lambda component: component.id)
    import_test_op, derive_test_op, test_op = components[0], components[
        1], components[2]

    # Helper method to retrieve the requested parameter value from the dictionary
    def get_parameter_value(param_name):
        property_dict = properties_json["current_parameters"][param_name]
        return property_dict[property_dict["activeControl"]]

    # Helper method to retrieve the requested parameter info from the dictionary
    def get_parameter_format(param_name, control_id="StringControl"):
        param_info = None
        for prop_info in properties_json["uihints"]["parameter_info"]:
            if prop_info.get("parameter_ref") == param_name:
                param_info = prop_info["data"]["controls"][control_id][
                    "format"]
                break

        return param_info

    # Helper method to retrieve the requested parameter description from the dictionary
    def get_parameter_description(param_name):
        param_desc = None
        for prop_info in properties_json["uihints"]["parameter_info"]:
            if prop_info.get("parameter_ref") == param_name:
                param_desc = prop_info["description"]["default"]
                break

        return param_desc

    # Helper method to retrieve whether the requested parameter is required
    def get_parameter_required(param_name):
        param_info = None
        for prop_info in properties_json["uihints"]["parameter_info"]:
            if prop_info.get("parameter_ref") == param_name:
                param_info = prop_info["data"]["required"]
                break

        return param_info

    # Retrieve properties for TestOperator
    # Test Operator does not include type hints for the init function args
    properties_json = ComponentCache.to_canvas_properties(test_op)

    # Ensure system parameters are not prefixed and hold correct values
    assert properties_json["current_parameters"]["label"] == ""

    component_source = json.dumps({
        "catalog_type":
        catalog_type,
        "component_ref":
        catalog_entry.entry_reference
    })
    assert properties_json["current_parameters"][
        "component_source"] == component_source

    # Ensure component parameters are prefixed with 'elyra_' and values are as expected
    assert get_parameter_value("elyra_str_no_default") == ""
    assert get_parameter_value("elyra_str_default") == "default"
    assert get_parameter_value("elyra_str_empty") == ""
    assert get_parameter_value("elyra_str_not_in_docstring") == ""

    assert get_parameter_value("elyra_bool_no_default") is False
    assert get_parameter_value("elyra_bool_default_false") is False
    assert get_parameter_value("elyra_bool_default_true") is True
    assert get_parameter_value("elyra_bool_not_in_docstring") is False

    assert get_parameter_value("elyra_int_no_default") == 0
    assert get_parameter_value("elyra_int_default_zero") == 0
    assert get_parameter_value("elyra_int_default_non_zero") == 2
    assert get_parameter_value("elyra_int_not_in_docstring") == 3

    assert get_parameter_value("elyra_dict_default_is_none") == "{}"  # {}
    assert get_parameter_value("elyra_list_default_is_none") == "[]"  # []

    # Ensure that type information is inferred correctly for properties that
    # define 'unusual' types, such as 'a dictionary of lists'
    assert get_parameter_format("elyra_unusual_type_dict") == "dictionary"
    assert get_parameter_format("elyra_unusual_type_list") == "list"

    # Ensure that type information falls back to string if no type hint present
    # and no ':type: <type info>' phrase found in docstring
    assert get_parameter_format("elyra_fallback_type") == "string"

    # Ensure component parameters are marked as required in the correct circumstances
    # (parameter is required if there is no default value provided or if a type hint
    # does not include 'Optional[...]')
    assert get_parameter_required("elyra_str_no_default") is True
    assert get_parameter_required("elyra_str_default") is False
    assert get_parameter_required("elyra_str_empty") is False

    # Ensure descriptions are rendered properly with type hint in parentheses
    assert (get_parameter_description("elyra_unusual_type_dict") ==
            "a dictionary parameter with the "
            "phrase 'list' in type description "
            "(type: a dictionary of arrays)")
    assert (get_parameter_description("elyra_unusual_type_list") ==
            "a list parameter with the phrase "
            "'string' in type description "
            "(type: a list of strings)")
    assert get_parameter_description("elyra_fallback_type") == "(type: str)"

    # Ensure that a long description with line wrapping and a backslash escape has rendered
    # (and hence did not raise an error during json.loads in the properties API request)
    parsed_description = """a string parameter with a very long description
        that wraps lines and also has an escaped underscore in it, as shown here: (\_)  # noqa W605"""
    modified_description = parsed_description.replace(
        "\n", " ") + " (type: str)"  # modify desc acc. to parser rules
    assert get_parameter_description(
        "elyra_long_description_property") == modified_description

    # Retrieve properties for DeriveFromTestOperator
    # DeriveFromTestOperator includes type hints for all init arguments
    properties_json = ComponentCache.to_canvas_properties(derive_test_op)

    # Ensure default values are parsed correct in the case where type hints are present
    assert get_parameter_value("elyra_str_default") == "default"
    assert get_parameter_value("elyra_bool_default") is True
    assert get_parameter_value("elyra_int_default") == 2

    # Ensure component parameters are prefixed with 'elyra_' and types are as expected
    # in the case when a type hint is provided (and regardless of whether or not the
    # parameter type is included in the docstring)
    assert get_parameter_format("elyra_str_no_default") == "string"
    assert get_parameter_format("elyra_str_default") == "string"
    assert get_parameter_format("elyra_str_optional_default") == "string"
    assert get_parameter_format("elyra_str_not_in_docstring") == "string"

    assert get_parameter_format("elyra_bool_no_default",
                                "BooleanControl") == "boolean"
    assert get_parameter_format("elyra_bool_default",
                                "BooleanControl") == "boolean"
    assert get_parameter_format("elyra_bool_not_in_docstring",
                                "BooleanControl") == "boolean"

    assert get_parameter_format("elyra_int_no_default",
                                "NumberControl") == "number"
    assert get_parameter_format("elyra_int_default",
                                "NumberControl") == "number"
    assert get_parameter_format("elyra_int_not_in_docstring",
                                "NumberControl") == "number"

    assert get_parameter_format("elyra_list_optional_default") == "list"

    # Ensure component parameters are marked as required in the correct circumstances
    assert get_parameter_required("elyra_str_no_default") is True
    assert get_parameter_required("elyra_str_default") is False
    assert get_parameter_required("elyra_str_optional_default") is False
    assert get_parameter_required("elyra_str_not_in_docstring") is True

    # Retrieve properties for DeriveFromImportedOperator
    # DeriveFromImportedOperator includes type hints for  dictionary and
    # list values to test the more complex parsing required in this case
    properties_json = ComponentCache.to_canvas_properties(import_test_op)

    # Ensure component parameters are prefixed with 'elyra_' and types are as expected
    assert get_parameter_format("elyra_dict_no_default") == "dictionary"
    assert get_parameter_format(
        "elyra_dict_optional_no_default") == "dictionary"
    assert get_parameter_format("elyra_nested_dict_default") == "dictionary"
    assert get_parameter_format("elyra_dict_not_in_docstring") == "dictionary"

    assert get_parameter_format("elyra_list_no_default") == "list"
    assert get_parameter_format("elyra_list_optional_no_default") == "list"
    assert get_parameter_format("elyra_list_default") == "list"
    assert get_parameter_format("elyra_list_optional_default") == "list"
    assert get_parameter_format("elyra_list_not_in_docstring") == "list"

    assert get_parameter_value("elyra_dict_no_default") == "{}"
    assert get_parameter_value("elyra_list_no_default") == "[]"
async def test_parse_components_additional_metatypes():
    # Define the appropriate reader for a URL-type component definition
    kfp_supported_file_types = [".yaml"]
    reader = UrlComponentCatalogConnector(kfp_supported_file_types)

    # Read contents of given path
    url = "https://raw.githubusercontent.com/kubeflow/pipelines/1.4.1/components/keras/Train_classifier/from_CSV/component.yaml"  # noqa: E501
    catalog_entry_data = {"url": url}

    # Construct a catalog instance
    catalog_type = "url-catalog"
    catalog_instance = ComponentCatalogMetadata(schema_name=catalog_type,
                                                metadata={
                                                    "categories": ["Test"],
                                                    "runtime_type":
                                                    RUNTIME_PROCESSOR.name
                                                })

    # Build the catalog entry data structures required for parsing
    entry_data = reader.get_entry_data(catalog_entry_data, {})
    catalog_entry = CatalogEntry(entry_data, catalog_entry_data,
                                 catalog_instance, ["url"])

    # Parse the component entry
    parser = KfpComponentParser()
    component = parser.parse(catalog_entry)[0]
    properties_json = ComponentCache.to_canvas_properties(component)

    # Ensure component parameters are prefixed (and system parameters are not) and all hold correct values
    assert properties_json["current_parameters"]["label"] == ""

    component_source = json.dumps({
        "catalog_type":
        catalog_type,
        "component_ref":
        catalog_entry.entry_reference
    })
    assert properties_json["current_parameters"][
        "component_source"] == component_source
    assert properties_json["current_parameters"][
        "elyra_training_features"] == "None"  # inputPath
    assert properties_json["current_parameters"][
        "elyra_training_labels"] == "None"  # inputPath
    assert properties_json["current_parameters"][
        "elyra_network_json"] == "None"  # inputPath
    assert properties_json["current_parameters"]["elyra_loss_name"] == {
        "StringControl": "categorical_crossentropy",
        "activeControl": "StringControl",
    }
    assert properties_json["current_parameters"]["elyra_num_classes"] == {
        "NumberControl": 0,
        "activeControl": "NumberControl",
    }
    assert properties_json["current_parameters"]["elyra_optimizer"] == {
        "StringControl": "rmsprop",
        "activeControl": "StringControl",
    }
    assert properties_json["current_parameters"]["elyra_optimizer_config"] == {
        "StringControl": "",
        "activeControl": "StringControl",
    }
    assert properties_json["current_parameters"]["elyra_learning_rate"] == {
        "NumberControl": 0.01,
        "activeControl": "NumberControl",
    }
    assert properties_json["current_parameters"]["elyra_num_epochs"] == {
        "NumberControl": 100,
        "activeControl": "NumberControl",
    }
    assert properties_json["current_parameters"]["elyra_batch_size"] == {
        "NumberControl": 32,
        "activeControl": "NumberControl",
    }
    assert properties_json["current_parameters"]["elyra_metrics"] == {
        "StringControl": "['accuracy']",
        "activeControl": "StringControl",
    }
    assert properties_json["current_parameters"]["elyra_random_seed"] == {
        "NumberControl": 0,
        "activeControl": "NumberControl",
    }
def test_parse_kfp_component_file():
    # Define the appropriate reader for a filesystem-type component definition
    kfp_supported_file_types = [".yaml"]
    reader = FilesystemComponentCatalogConnector(kfp_supported_file_types)

    # Read contents of given path
    path = _get_resource_path("kfp_test_operator.yaml")
    catalog_entry_data = {"path": path}

    # Construct a catalog instance
    catalog_type = "local-file-catalog"
    catalog_instance = ComponentCatalogMetadata(schema_name=catalog_type,
                                                metadata={
                                                    "categories": ["Test"],
                                                    "runtime_type":
                                                    RUNTIME_PROCESSOR.name
                                                })

    # Build the catalog entry data structures required for parsing
    entry_data = reader.get_entry_data(catalog_entry_data, {})
    catalog_entry = CatalogEntry(entry_data, catalog_entry_data,
                                 catalog_instance, ["path"])

    # Parse the component entry
    parser = KfpComponentParser.create_instance(platform=RUNTIME_PROCESSOR)
    component = parser.parse(catalog_entry)[0]
    properties_json = ComponentCache.to_canvas_properties(component)

    # Ensure description is rendered even with an unescaped character
    description = 'This component description contains an unescaped " character'
    assert properties_json["current_parameters"][
        "component_description"] == description

    # Ensure component parameters are prefixed (and system parameters are not) and all hold correct values
    assert properties_json["current_parameters"]["label"] == ""

    component_source = json.dumps({
        "catalog_type":
        catalog_type,
        "component_ref":
        catalog_entry.entry_reference
    })
    assert properties_json["current_parameters"][
        "component_source"] == component_source
    assert properties_json["current_parameters"][
        "elyra_test_string_no_default"] == {
            "StringControl": "",
            "activeControl": "StringControl",
        }

    assert properties_json["current_parameters"][
        "elyra_test_string_default_value"] == {
            "StringControl": "default",
            "activeControl": "StringControl",
        }
    assert properties_json["current_parameters"][
        "elyra_test_string_default_empty"] == {
            "StringControl": "",
            "activeControl": "StringControl",
        }

    assert properties_json["current_parameters"][
        "elyra_test_bool_default"] == {
            "BooleanControl": False,
            "activeControl": "BooleanControl",
        }
    assert properties_json["current_parameters"]["elyra_test_bool_false"] == {
        "BooleanControl": False,
        "activeControl": "BooleanControl",
    }
    assert properties_json["current_parameters"]["elyra_test_bool_true"] == {
        "BooleanControl": True,
        "activeControl": "BooleanControl",
    }

    assert properties_json["current_parameters"]["elyra_test_int_default"] == {
        "NumberControl": 0,
        "activeControl": "NumberControl",
    }
    assert properties_json["current_parameters"]["elyra_test_int_zero"] == {
        "NumberControl": 0,
        "activeControl": "NumberControl",
    }
    assert properties_json["current_parameters"][
        "elyra_test_int_non_zero"] == {
            "NumberControl": 1,
            "activeControl": "NumberControl",
        }

    assert properties_json["current_parameters"][
        "elyra_test_float_default"] == {
            "NumberControl": 0.0,
            "activeControl": "NumberControl",
        }
    assert properties_json["current_parameters"]["elyra_test_float_zero"] == {
        "NumberControl": 0.0,
        "activeControl": "NumberControl",
    }
    assert properties_json["current_parameters"][
        "elyra_test_float_non_zero"] == {
            "NumberControl": 1.0,
            "activeControl": "NumberControl",
        }

    assert properties_json["current_parameters"][
        "elyra_test_dict_default"] == {
            "StringControl": "{}",
            "activeControl": "StringControl",
        }  # {}
    assert properties_json["current_parameters"][
        "elyra_test_list_default"] == {
            "StringControl": "[]",
            "activeControl": "StringControl",
        }  # []

    # Ensure that the 'required' attribute was set correctly. KFP components default to required
    # unless explicitly marked otherwise in component YAML.
    required_property = next(
        prop for prop in properties_json["uihints"]["parameter_info"]
        if prop.get("parameter_ref") == "elyra_test_required_property")
    assert required_property["data"]["required"] is True

    optional_property = next(
        prop for prop in properties_json["uihints"]["parameter_info"]
        if prop.get("parameter_ref") == "elyra_test_optional_property")
    assert optional_property["data"]["required"] is False

    default_required_property = next(
        prop for prop in properties_json["uihints"]["parameter_info"]
        if prop.get("parameter_ref") == "elyra_test_required_property_default")
    assert default_required_property["data"]["required"] is True

    # Ensure that type information is inferred correctly
    unusual_dict_property = next(
        prop for prop in properties_json["uihints"]["parameter_info"]
        if prop.get("parameter_ref") == "elyra_test_unusual_type_dict")
    assert unusual_dict_property["data"]["controls"]["StringControl"][
        "format"] == "dictionary"

    unusual_list_property = next(
        prop for prop in properties_json["uihints"]["parameter_info"]
        if prop.get("parameter_ref") == "elyra_test_unusual_type_list")
    assert unusual_list_property["data"]["controls"]["StringControl"][
        "format"] == "list"

    unusual_string_property = next(
        prop for prop in properties_json["uihints"]["parameter_info"]
        if prop.get("parameter_ref") == "elyra_test_unusual_type_string")
    assert unusual_string_property["data"]["controls"]["StringControl"][
        "format"] == "string"

    file_property = next(
        prop for prop in properties_json["uihints"]["parameter_info"]
        if prop.get("parameter_ref") == "elyra_test_unusual_type_file")
    assert file_property["data"]["format"] == "inputpath"

    no_type_property = next(
        prop for prop in properties_json["uihints"]["parameter_info"]
        if prop.get("parameter_ref") == "elyra_test_unusual_type_notgiven")
    assert no_type_property["data"]["controls"]["StringControl"][
        "format"] == "string"

    # Ensure descriptions are rendered properly with type hint in parentheses
    assert (unusual_dict_property["description"]["default"] ==
            "The test command description "
            "(type: Dictionary of arrays)")
    assert unusual_list_property["description"][
        "default"] == "The test command description (type: An array)"
    assert unusual_string_property["description"][
        "default"] == "The test command description (type: A string)"
    assert (
        file_property["description"]["default"] ==
        "The test command description"
    )  # No data type info is included in parentheses for inputPath variables
    assert no_type_property["description"][
        "default"] == "The test command description (type: string)"