예제 #1
0
    def test_build_class_attribute_from_dict(self):
        target = ClassFactory.create()
        data = {"sub1": 1, "sub2": "value"}
        DictMapper.build_class_attribute(target, "a", data)

        expected = AttrFactory.create(
            name="a",
            tag=Tag.ELEMENT,
            types=[AttrTypeFactory.create(qname="a", forward=True)],
        )

        expected_inner = ClassFactory.create(
            qname="a",
            tag=Tag.ELEMENT,
            module="",
            ns_map={},
            attrs=[
                AttrFactory.native(DataType.SHORT, name="sub1"),
                AttrFactory.native(DataType.STRING, name="sub2"),
            ],
        )

        self.assertEqual(expected, target.attrs[0])
        self.assertEqual(expected_inner, target.inner[0])
        self.assertEqual(1, len(target.inner))
예제 #2
0
    def test_rename_class_dependencies(self):
        attr_type = AttrTypeFactory.create("{foo}bar")

        target = ClassFactory.create(
            extensions=[
                ExtensionFactory.create(),
                ExtensionFactory.create(attr_type.clone()),
            ],
            attrs=[
                AttrFactory.create(),
                AttrFactory.create(
                    types=[AttrTypeFactory.create(),
                           attr_type.clone()]),
            ],
            inner=[
                ClassFactory.create(
                    extensions=[ExtensionFactory.create(attr_type.clone())],
                    attrs=[
                        AttrFactory.create(),
                        AttrFactory.create(types=[
                            AttrTypeFactory.create(),
                            attr_type.clone()
                        ]),
                    ],
                )
            ],
        )

        self.sanitizer.rename_class_dependencies(target, "{foo}bar", "thug")
        dependencies = set(target.dependencies())
        self.assertNotIn("{foo}bar", dependencies)
        self.assertIn("thug", dependencies)
예제 #3
0
    def test_create_substitutions(self, mock_create_substitution):
        ns = "xsdata"
        classes = [
            ClassFactory.create(
                substitutions=[build_qname(ns, "foo"), build_qname(ns, "bar")],
                abstract=True,
            ),
            ClassFactory.create(substitutions=[build_qname(ns, "foo")], abstract=True),
        ]

        reference_attrs = AttrFactory.list(3)
        mock_create_substitution.side_effect = reference_attrs

        self.processor.container.extend(classes)
        self.processor.create_substitutions()

        expected = {
            build_qname(ns, "foo"): [reference_attrs[0], reference_attrs[2]],
            build_qname(ns, "bar"): [reference_attrs[1]],
        }
        self.assertEqual(expected, self.processor.substitutions)

        mock_create_substitution.assert_has_calls(
            [mock.call(classes[0]), mock.call(classes[0]), mock.call(classes[1])]
        )
예제 #4
0
    def test_get_or_create_inner_class(self):

        target = ClassFactory.create(module="foo",
                                     package=None,
                                     ns_map={"foo": "bar"})

        actual = DefinitionsMapper.build_inner_class(target, "body")
        expected = ClassFactory.create(
            qname="body",
            tag=Tag.BINDING_MESSAGE,
            module=target.module,
            package=None,
            ns_map=target.ns_map,
        )
        self.assertEqual(expected, actual)
        self.assertIn(actual, target.inner)

        self.assertIsNot(actual.ns_map, target.ns_map)

        expected_attr = DefinitionsMapper.build_attr("body",
                                                     "body",
                                                     forward=True)
        self.assertEqual(expected_attr, target.attrs[0])

        repeat = DefinitionsMapper.build_inner_class(target, "body")
        self.assertIs(repeat, actual)
예제 #5
0
    def test_map_binding_operation_messages_with_style_rpc(
            self, mock_build_envelope_class, mock_build_message_class):
        definitions = Definitions()
        operation = BindingOperation()
        port_operation = PortTypeOperation()
        name = "Add"
        namespace = "someNS"
        target = ClassFactory.create()
        message = ClassFactory.create()
        style = "rpc"

        mock_build_message_class.return_value = message
        mock_build_envelope_class.return_value = target

        operation.input = BindingMessage()
        port_operation.input = PortTypeMessage()

        result = DefinitionsMapper.map_binding_operation_messages(
            definitions, operation, port_operation, name, style, namespace)
        self.maxDiff = None
        self.assertEqual([message, target], list(result))

        mock_build_message_class.assert_called_once_with(
            definitions, port_operation.input)
        mock_build_envelope_class.assert_called_once_with(
            definitions,
            operation.input,
            port_operation.input,
            f"{name}_input",
            style,
            namespace,
        )
예제 #6
0
    def test_build_envelope_fault(self):
        body = ClassFactory.create(qname="Body")
        target = ClassFactory.create()
        target.inner.append(body)

        port_type_operation = PortTypeOperation()
        definitions = Definitions()

        DefinitionsMapper.build_envelope_fault(definitions,
                                               port_type_operation, target)
        expected_fault_attr = DefinitionsMapper.build_attr(
            "Fault",
            body.inner[0].qname,
            forward=True,
            namespace=target.namespace)
        str_qname = str(DataType.STRING)
        expected_fault_attrs = [
            DefinitionsMapper.build_attr(name,
                                         str_qname,
                                         native=True,
                                         namespace="")
            for name in ["faultcode", "faultstring", "faultactor", "detail"]
        ]

        self.assertEqual(1, len(body.attrs))
        self.assertEqual(expected_fault_attr, body.attrs[0])
        self.assertEqual(expected_fault_attrs, body.inner[0].attrs)
예제 #7
0
    def test_render_package(self):
        classes = [
            ClassFactory.create(qname="a", package="foo"),
            ClassFactory.create(qname="b", package="foo"),
            ClassFactory.create(qname="c", package="foo"),
            ClassFactory.create(qname="a", package="foo", module="bar"),
        ]

        random.shuffle(classes)

        actual = self.generator.render_package(classes)
        expected = "\n".join([
            "from foo.bar import A as BarA",
            "from foo.tests import (",
            "    A as TestsA,",
            "    B,",
            "    C,",
            ")",
            "",
            "__all__ = [",
            '    "BarA",',
            '    "TestsA",',
            '    "B",',
            '    "C",',
            "]",
            "",
        ])
        self.assertEqual(expected, actual)
예제 #8
0
    def test_render(self, mock_render_module, mock_render_package):
        classes = [
            ClassFactory.create(package="foo.bar"),
            ClassFactory.create(package="bar.foo"),
            ClassFactory.create(package="thug.life"),
        ]

        mock_render_module.return_value = "module"
        mock_render_package.return_value = "package"

        iterator = self.generator.render(classes)

        cwd = Path.cwd()
        actual = [(out.path, out.title, out.source) for out in iterator]
        expected = [
            (cwd.joinpath("foo/bar/__init__.py"), "init", "package"),
            (cwd.joinpath("foo/__init__.py"), "init", "# nothing here\n"),
            (cwd.joinpath("bar/foo/__init__.py"), "init", "package"),
            (cwd.joinpath("bar/__init__.py"), "init", "# nothing here\n"),
            (cwd.joinpath("thug/life/__init__.py"), "init", "package"),
            (cwd.joinpath("thug/__init__.py"), "init", "# nothing here\n"),
            (cwd.joinpath("foo/bar/tests.py"), "foo.bar.tests", "module"),
            (cwd.joinpath("bar/foo/tests.py"), "bar.foo.tests", "module"),
            (cwd.joinpath("thug/life/tests.py"), "thug.life.tests", "module"),
        ]
        self.assertEqual(expected, actual)
        mock_render_package.assert_has_calls([mock.call([x]) for x in classes])
        mock_render_module.assert_has_calls(
            [mock.call(mock.ANY, [x]) for x in classes])
예제 #9
0
    def test_initialize(self):
        classes = [
            ClassFactory.create(qname="{xsdata}foo", tag=Tag.ELEMENT),
            ClassFactory.create(qname="{xsdata}foo", tag=Tag.COMPLEX_TYPE),
            ClassFactory.create(qname="{xsdata}foobar", tag=Tag.COMPLEX_TYPE),
        ]
        container = ClassContainer()
        container.extend(classes)

        expected = {
            "{xsdata}foo": classes[:2],
            "{xsdata}foobar": classes[2:],
        }

        self.assertEqual(2, len(container.data))
        self.assertEqual(expected, container.data)
        self.assertEqual(
            [
                "AttributeGroupHandler",
                "ClassExtensionHandler",
                "ClassEnumerationHandler",
                "AttributeSubstitutionHandler",
                "AttributeTypeHandler",
                "AttributeMergeHandler",
                "AttributeMixedContentHandler",
                "AttributeSanitizerHandler",
            ],
            [x.__class__.__name__ for x in container.processors],
        )
예제 #10
0
    def test_copy_attributes(self, mock_clone_attribute, mock_copy_inner_classes):
        mock_clone_attribute.side_effect = lambda x, y: x.clone()
        target = ClassFactory.create(
            attrs=[AttrFactory.create(name="a"), AttrFactory.create(name="b")]
        )
        source = ClassFactory.create(
            attrs=[
                AttrFactory.create(name="c", index=sys.maxsize),
                AttrFactory.create(name="a"),
                AttrFactory.create(name="b"),
                AttrFactory.create(name="d"),
            ]
        )
        extension = ExtensionFactory.create(AttrTypeFactory.create(qname="foo"))
        target.extensions.append(extension)

        ClassUtils.copy_attributes(source, target, extension)

        self.assertEqual(["a", "b", "d", "c"], [attr.name for attr in target.attrs])

        mock_copy_inner_classes.assert_has_calls(
            [
                mock.call(source, target, source.attrs[0]),
                mock.call(source, target, source.attrs[3]),
            ]
        )
        mock_clone_attribute.assert_has_calls(
            [
                mock.call(source.attrs[0], extension.restrictions),
                mock.call(source.attrs[3], extension.restrictions),
            ]
        )
예제 #11
0
    def test_rename_classes_protects_single_element(self, mock_rename_class):
        classes = [
            ClassFactory.create(qname="_a", tag=Tag.ELEMENT),
            ClassFactory.create(qname="a", tag=Tag.COMPLEX_TYPE),
        ]
        self.sanitizer.rename_classes(classes)

        mock_rename_class.assert_called_once_with(classes[1])
예제 #12
0
    def test_copy_inner_class_with_missing_inner(self):
        source = ClassFactory.create()
        target = ClassFactory.create()
        attr = AttrFactory.create()
        attr_type = AttrTypeFactory.create(forward=True, qname=target.qname)

        with self.assertRaises(CodeGenerationError):
            ClassUtils.copy_inner_class(source, target, attr, attr_type)
예제 #13
0
    def test_dependencies(self):
        obj = ClassFactory.create(
            attrs=[
                AttrFactory.create(
                    types=[AttrTypeFactory.native(DataType.DECIMAL)]),
                AttrFactory.create(
                    types=[
                        AttrTypeFactory.create(
                            qname=build_qname(Namespace.XS.uri, "annotated"),
                            forward=True,
                        )
                    ],
                    choices=[
                        AttrFactory.create(
                            name="x",
                            types=[
                                AttrTypeFactory.create(qname="choiceAttr"),
                                AttrTypeFactory.native(DataType.STRING),
                            ],
                        ),
                        AttrFactory.create(
                            name="x",
                            types=[
                                AttrTypeFactory.create(qname="choiceAttrTwo"),
                                AttrTypeFactory.create(qname="choiceAttrEnum"),
                            ],
                        ),
                    ],
                ),
                AttrFactory.create(types=[
                    AttrTypeFactory.create(
                        qname=build_qname(Namespace.XS.uri, "openAttrs")),
                    AttrTypeFactory.create(
                        qname=build_qname(Namespace.XS.uri, "localAttribute")),
                ]),
            ],
            extensions=[
                ExtensionFactory.reference(
                    build_qname(Namespace.XS.uri, "foobar")),
                ExtensionFactory.reference(
                    build_qname(Namespace.XS.uri, "foobar")),
            ],
            inner=[
                ClassFactory.create(attrs=AttrFactory.list(
                    2, types=AttrTypeFactory.list(1, qname="{xsdata}foo")))
            ],
        )

        expected = [
            "choiceAttr",
            "choiceAttrTwo",
            "choiceAttrEnum",
            "{http://www.w3.org/2001/XMLSchema}openAttrs",
            "{http://www.w3.org/2001/XMLSchema}localAttribute",
            "{http://www.w3.org/2001/XMLSchema}foobar",
            "{xsdata}foo",
        ]
        self.assertCountEqual(expected, list(obj.dependencies()))
예제 #14
0
    def test_property_is_complex(self):
        obj = ClassFactory.create(tag=Tag.ELEMENT)
        self.assertTrue(obj.is_complex)

        obj = ClassFactory.create(tag=Tag.COMPLEX_TYPE)
        self.assertTrue(obj.is_complex)

        obj = ClassFactory.create(tag=Tag.SIMPLE_TYPE)
        self.assertFalse(obj.is_complex)
예제 #15
0
    def test_copy_inner_class_skip_non_forward_reference(self):
        source = ClassFactory.create()
        target = ClassFactory.create()
        attr = AttrFactory.create()
        attr_type = AttrTypeFactory.create()
        ClassUtils.copy_inner_class(source, target, attr, attr_type)

        self.assertFalse(attr_type.circular)
        self.assertEqual(0, len(target.inner))
예제 #16
0
    def test_process_dependency_type_with_dummy_type(
            self, mock_find_dependency, mock_reset_attribute_type):
        mock_find_dependency.return_value = ClassFactory.create(
            tag=Tag.ELEMENT)
        target = ClassFactory.create()
        attr = AttrFactory.create()
        attr_type = attr.types[0]

        self.processor.process_dependency_type(target, attr, attr_type)
        mock_reset_attribute_type.assert_called_once_with(attr_type, False)
예제 #17
0
    def test_process_complex_extension_ignores_extension(
            self, mock_should_remove_extension, mock_should_flatten_extension):
        mock_should_remove_extension.return_value = False
        mock_should_flatten_extension.return_value = False
        extension = ExtensionFactory.create()
        target = ClassFactory.create(extensions=[extension])
        source = ClassFactory.create()

        self.processor.process_complex_extension(source, target, extension)
        self.assertEqual(1, len(target.extensions))
예제 #18
0
    def test_copy_inner_class_check_circular_reference(self):
        source = ClassFactory.create()
        target = ClassFactory.create()
        attr = AttrFactory.create()
        attr_type = AttrTypeFactory.create(forward=True, qname=target.qname)
        source.inner.append(target)

        ClassUtils.copy_inner_class(source, target, attr, attr_type)
        self.assertTrue(attr_type.circular)
        self.assertEqual(0, len(target.inner))
예제 #19
0
    def test_copy_extensions(self):
        target = ClassFactory.create(extensions=ExtensionFactory.list(1))
        source = ClassFactory.create(extensions=ExtensionFactory.list(2))
        link_extension = ExtensionFactory.create()
        link_extension.restrictions.max_occurs = 2

        ClassUtils.copy_extensions(source, target, link_extension)

        self.assertEqual(3, len(target.extensions))
        self.assertEqual(2, target.extensions[1].restrictions.max_occurs)
        self.assertEqual(2, target.extensions[2].restrictions.max_occurs)
예제 #20
0
    def test_map_binding_operation(self, mock_operation_namespace,
                                   mock_map_binding_operation_messages):
        definitions = Definitions(location="foo.wsdl",
                                  target_namespace="xsdata")
        operation = BindingOperation(name="Add")
        operation.ns_map["foo"] = "bar"
        port_operation = PortTypeOperation()
        config = {"a": "one", "b": "two", "style": "rpc"}
        name = "Calc"
        namespace = "SomeNS"
        first = ClassFactory.create(qname="some_name_first",
                                    meta_name="Envelope")
        second = ClassFactory.create(qname="some_name_second",
                                     meta_name="Envelope")
        other = ClassFactory.create()
        service = ClassFactory.create(
            qname=build_qname("xsdata", "Calc_Add"),
            status=Status.PROCESSED,
            tag=Tag.BINDING_OPERATION,
            module="foo",
            package=None,
            ns_map={"foo": "bar"},
            attrs=[
                DefinitionsMapper.build_attr("a",
                                             str(DataType.STRING),
                                             native=True,
                                             default="one"),
                DefinitionsMapper.build_attr("b",
                                             str(DataType.STRING),
                                             native=True,
                                             default="two"),
                DefinitionsMapper.build_attr("style",
                                             str(DataType.STRING),
                                             native=True,
                                             default="rpc"),
                DefinitionsMapper.build_attr("first", first.qname),
                DefinitionsMapper.build_attr("second", second.qname),
            ],
        )
        mock_operation_namespace.return_value = namespace
        mock_map_binding_operation_messages.return_value = [
            first, second, other
        ]

        result = DefinitionsMapper.map_binding_operation(
            definitions, operation, port_operation, config, name)
        expected = [first, second, other, service]

        self.assertIsInstance(result, Generator)
        self.assertEqual(expected, list(result))
        mock_operation_namespace.assert_called_once_with(config)
        mock_map_binding_operation_messages.assert_called_once_with(
            definitions, operation, port_operation, service.name, "rpc",
            namespace)
예제 #21
0
    def test_process_complex_extension_copies_attributes(
            self, mock_compare_attributes, mock_should_flatten_extension):
        mock_should_flatten_extension.return_value = True
        extension = ExtensionFactory.create()
        target = ClassFactory.create()
        source = ClassFactory.create()

        self.processor.process_complex_extension(source, target, extension)
        mock_compare_attributes.assert_called_once_with(source, target)
        mock_should_flatten_extension.assert_called_once_with(
            source, target, extension)
예제 #22
0
    def test_find_dependency(self):
        attr_type = AttrTypeFactory.create(qname="a")

        self.assertIsNone(self.processor.find_dependency(attr_type))

        complex = ClassFactory.create(qname="a", tag=Tag.COMPLEX_TYPE)
        self.processor.container.add(complex)
        self.assertEqual(complex, self.processor.find_dependency(attr_type))

        simple = ClassFactory.create(qname="a", tag=Tag.SIMPLE_TYPE)
        self.processor.container.add(simple)
        self.assertEqual(simple, self.processor.find_dependency(attr_type))
예제 #23
0
    def test_set_circular_flag(self, mock_is_circular_dependency):
        source = ClassFactory.create()
        target = ClassFactory.create()
        attr = AttrFactory.create()
        attr_type = attr.types[0]

        mock_is_circular_dependency.return_value = True

        self.processor.set_circular_flag(source, target, attr_type)
        self.assertTrue(attr_type.circular)

        mock_is_circular_dependency.assert_called_once_with(
            source, target, set())
예제 #24
0
    def test_process_class_group_compound_fields(self,
                                                 mock_group_compound_fields):
        target = ClassFactory.create()
        inner = ClassFactory.create()
        target.inner.append(inner)

        self.container.config.output.compound_fields = True
        self.sanitizer.process_class(target)

        mock_group_compound_fields.assert_has_calls([
            mock.call(inner),
            mock.call(target),
        ])
예제 #25
0
    def test_rename_classes(self, mock_rename_class):
        classes = [
            ClassFactory.create(qname="_a", tag=Tag.ELEMENT),
            ClassFactory.create(qname="_A", tag=Tag.ELEMENT),
            ClassFactory.create(qname="a", tag=Tag.COMPLEX_TYPE),
        ]
        self.sanitizer.rename_classes(classes)

        mock_rename_class.assert_has_calls([
            mock.call(classes[0]),
            mock.call(classes[1]),
            mock.call(classes[2]),
        ])
예제 #26
0
    def test_should_flatten_extension(self):
        source = ClassFactory.create()
        target = ClassFactory.create()

        self.assertFalse(self.processor.should_flatten_extension(source, target))

        # Source has suffix attr and target has its own attrs
        source = ClassFactory.elements(1)
        source.attrs[0].index = sys.maxsize
        target.attrs.append(AttrFactory.create())
        self.assertTrue(self.processor.should_flatten_extension(source, target))

        # Target has suffix attr
        source = ClassFactory.create()
        target = ClassFactory.elements(1)
        target.attrs[0].index = sys.maxsize
        self.assertTrue(self.processor.should_flatten_extension(source, target))

        # Source is a simple type
        source = ClassFactory.create(attrs=[AttrFactory.create(tag=Tag.SIMPLE_TYPE)])
        target = ClassFactory.elements(1)
        self.assertTrue(self.processor.should_flatten_extension(source, target))

        # Sequential violation
        source = ClassFactory.elements(3)
        target = source.clone()
        self.assertFalse(self.processor.should_flatten_extension(source, target))

        for attr in target.attrs:
            attr.restrictions.sequential = True

        self.assertFalse(self.processor.should_flatten_extension(source, target))

        target.attrs = [target.attrs[1], target.attrs[0], target.attrs[2]]
        self.assertTrue(self.processor.should_flatten_extension(source, target))

        # Types violation
        target = source.clone()
        target.attrs[1].types = [
            AttrTypeFactory.native(DataType.INT),
            AttrTypeFactory.native(DataType.FLOAT),
        ]

        source.attrs[1].types = [
            AttrTypeFactory.native(DataType.INT),
            AttrTypeFactory.native(DataType.FLOAT),
            AttrTypeFactory.native(DataType.DECIMAL),
        ]
        self.assertFalse(self.processor.should_flatten_extension(source, target))
        target.attrs[1].types.append(AttrTypeFactory.native(DataType.QNAME))
        self.assertTrue(self.processor.should_flatten_extension(source, target))
예제 #27
0
    def test_find_inner(self, mock_process_class):
        obj = ClassFactory.create()
        first = ClassFactory.create(qname="{a}a")
        second = ClassFactory.create(qname="{a}b", status=Status.PROCESSED)
        obj.inner.extend((first, second))

        def process_class(x: Class):
            x.status = Status.PROCESSED

        mock_process_class.side_effect = process_class

        self.assertEqual(first, self.container.find_inner(obj, "{a}a"))
        self.assertEqual(second, self.container.find_inner(obj, "{a}b"))
        mock_process_class.assert_called_once_with(first)
예제 #28
0
    def test_build_envelope_fault_with_detail_messages(self):
        body = ClassFactory.create(qname="Body")
        target = ClassFactory.create()
        target.inner.append(body)

        port_type_operation = PortTypeOperation()
        port_type_operation.faults.append(PortTypeMessage(message="x:foo"))
        port_type_operation.faults.append(PortTypeMessage(message="x:bar"))

        definitions = Definitions()
        definitions.messages.append(
            Message(name="foo", parts=[Part(element="fooEl")]))
        definitions.messages.append(
            Message(name="bar", parts=[Part(element="barEl")]))

        DefinitionsMapper.build_envelope_fault(definitions,
                                               port_type_operation, target)
        expected_fault_attr = DefinitionsMapper.build_attr(
            "Fault",
            body.inner[0].qname,
            forward=True,
            namespace=target.namespace)
        str_qname = str(DataType.STRING)
        expected_fault_attrs = [
            DefinitionsMapper.build_attr(name,
                                         str_qname,
                                         native=True,
                                         namespace="")
            for name in ["faultcode", "faultstring", "faultactor"]
        ]

        expected_fault_attrs.append(
            DefinitionsMapper.build_attr("detail",
                                         body.inner[0].inner[0].qname,
                                         forward=True,
                                         namespace=""))

        expected_fault_detail_attrs = [
            DefinitionsMapper.build_attr(name,
                                         qname=name,
                                         namespace=target.namespace,
                                         native=False)
            for name in ["fooEl", "barEl"]
        ]

        self.assertEqual(1, len(body.attrs))
        self.assertEqual(expected_fault_attr, body.attrs[0])
        self.assertEqual(expected_fault_attrs, body.inner[0].attrs)
        self.assertEqual(expected_fault_detail_attrs,
                         body.inner[0].inner[0].attrs)
예제 #29
0
    def test_process(self, mock_create_substitutions, mock_process_attribute):
        def init_substitutions():
            self.processor.substitutions = {}

        mock_create_substitutions.side_effect = init_substitutions

        target = ClassFactory.create(
            attrs=[AttrFactory.enumeration(), AttrFactory.any(), AttrFactory.element()]
        )

        self.processor.process(target)
        self.processor.process(ClassFactory.create())
        mock_process_attribute.assert_called_once_with(target, target.attrs[2])
        mock_create_substitutions.assert_called_once()
예제 #30
0
    def test_process_attribute_with_group(self, mock_copy_group_attributes):
        complex_bar = ClassFactory.create(qname="bar", tag=Tag.COMPLEX_TYPE)
        group_bar = ClassFactory.create(qname="bar", tag=Tag.GROUP)
        group_attr = AttrFactory.attribute_group(name="bar")
        target = ClassFactory.create()
        target.attrs.append(group_attr)

        self.processor.container.add(complex_bar)
        self.processor.container.add(group_bar)
        self.processor.container.add(target)

        self.processor.process_attribute(target, group_attr)
        mock_copy_group_attributes.assert_called_once_with(
            group_bar, target, group_attr)