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]) ])
def build(self, clazz: Type, parent_ns: Optional[str] = None) -> XmlMeta: """ Fetch from cache or build the binding metadata for the given class and parent namespace. :param clazz: A dataclass type :param parent_ns: The inherited parent namespace """ if clazz not in self.cache: # Ensure the given type is a dataclass. if not is_dataclass(clazz): raise XmlContextError(f"Object {clazz} is not a dataclass.") # Fetch the dataclass meta settings and make sure we don't inherit # the parent class meta. meta = clazz.Meta if "Meta" in clazz.__dict__ else None name = getattr(meta, "name", None) or self.local_name( clazz.__name__) nillable = getattr(meta, "nillable", False) namespace = getattr(meta, "namespace", parent_ns) module = sys.modules[clazz.__module__] source_namespace = getattr(module, "__NAMESPACE__", None) self.cache[clazz] = XmlMeta( clazz=clazz, qname=build_qname(namespace, name), source_qname=build_qname(source_namespace, name), nillable=nillable, vars=list(self.get_type_hints(clazz, namespace)), ) return self.cache[clazz]
def test_build_parts_attributes(self, mock_warning): p_one = Part(element="a:bar") p_one.ns_map["a"] = "great" p_two = Part(name="arg0", type="xs:string") p_two.ns_map["xs"] = Namespace.XS.uri p_three = Part(name="arg1", type="b:cafe") p_three.ns_map["b"] = "boo" p_four = Part(name="arg2") ns_map = {} parts = [p_one, p_two, p_three, p_four] result = DefinitionsMapper.build_parts_attributes(parts, ns_map) expected = [ DefinitionsMapper.build_attr("bar", build_qname("great", "bar"), namespace="great", native=False), DefinitionsMapper.build_attr("arg0", str(DataType.STRING), namespace="", native=True), DefinitionsMapper.build_attr("arg1", build_qname("boo", "cafe"), namespace="", native=False), ] self.assertIsInstance(result, Generator) self.assertEqual(expected, list(result)) mock_warning.assert_called_once_with("Skip untyped message part %s", "arg2")
def test_build_with_meta_namespace(self, mock_get_type_hints): namespace = Product.Meta.namespace result = self.ctx.build(Product, None) self.assertEqual(build_qname(namespace, "product"), result.qname) self.assertEqual(build_qname(namespace, "product"), result.source_qname) mock_get_type_hints.assert_called_once_with(Product, namespace)
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()))
def test_create_substitution(self): item = ClassFactory.elements(1, qname=build_qname("foo", "bar")) actual = self.processor.create_substitution(item) expected = AttrFactory.create( name=item.name, default=None, types=[AttrType(qname=build_qname("foo", "bar"))], tag=item.type.__name__, ) self.assertEqual(expected, actual)
def copy_inner_class(cls, source: Class, target: Class, attr: Attr, attr_type: AttrType): """ Check if the given attr type is a forward reference and copy its inner class from the source to the target class. Checks: 1. Update type if inner class in a circular reference 2. Copy inner class, rename it if source is a simple type. """ if not attr_type.forward: return # This will fail if no inner class is found, too strict??? inner = ClassUtils.find_inner(source, attr_type.qname) if inner is target: attr_type.circular = True else: clone = inner.clone() clone.package = target.package clone.module = target.module # Simple type, update the name if clone.name == "@value": namespace, _ = split_qname(clone.qname) clone.qname = attr_type.qname = build_qname( namespace, attr.name) target.inner.append(clone)
def build_inner_class(cls, target: Class, name: str, namespace: Optional[str] = None) -> Class: """ Build or retrieve an inner class for the given target class by the given name. This helper will also create a forward reference attribute for the parent class. """ inner = first(inner for inner in target.inner if inner.name == name) if not inner: inner = Class( qname=build_qname(name), tag=Tag.BINDING_MESSAGE, module=target.module, ns_map=target.ns_map.copy(), ) attr = cls.build_attr(name, inner.qname, forward=True, namespace=namespace) target.inner.append(inner) target.attrs.append(attr) return inner
def test_promote(self): target = ClassFactory.elements(2) inner = ClassFactory.enumeration(3) target.inner.append(inner) target.inner.append(ClassFactory.simple_type()) # Irrelevant attr_type = AttrTypeFactory.create(qname=inner.qname, forward=True) target.attrs[0].types.append(attr_type.clone()) target.attrs[1].types.append(attr_type.clone()) self.container.add(target) self.assertEqual(3, len(self.container.data)) self.processor.process(target) new_qname = build_qname(inner.target_namespace, f"{target.name}_{inner.name}") self.assertEqual(4, len(self.container.data)) new_inner = self.container.find(new_qname) self.assertEqual(1, len(target.inner)) self.assertNotEqual(new_inner.qname, inner.qname) self.assertEqual(new_inner.attrs, inner.attrs) self.assertEqual(new_inner.qname, target.attrs[0].types[1].qname) self.assertEqual(new_inner.qname, target.attrs[1].types[1].qname) self.assertFalse(target.attrs[0].types[1].forward) self.assertFalse(target.attrs[1].types[1].forward)
def test_build_with_parent_ns(self, mock_get_type_hints): result = self.ctx.build(ProductType, "http://xsdata") self.assertEqual(build_qname("http://xsdata", "ProductType"), str(result.qname)) mock_get_type_hints.assert_called_once_with(ProductType, "http://xsdata")
def build_class( cls, obj: ElementBase, container: str, module: str, target_namespace: Optional[str], ) -> Class: """Build and return a class instance.""" instance = Class( qname=build_qname(target_namespace, obj.real_name), abstract=obj.is_abstract, namespace=cls.element_namespace(obj, target_namespace), mixed=obj.is_mixed, nillable=obj.is_nillable, tag=obj.class_name, container=container, help=obj.display_help, ns_map=obj.ns_map, module=module, default=obj.default_value, fixed=obj.is_fixed, substitutions=cls.build_substitutions(obj, target_namespace), ) cls.build_class_extensions(obj, instance) cls.build_class_attributes(obj, instance) return instance
def parse_any_attribute(cls, value: str, ns_map: Dict) -> str: """Attempt to parse any attribute.""" prefix, suffix = text.split(value) if prefix and prefix in ns_map and not suffix.startswith("//"): value = build_qname(ns_map[prefix], suffix) return value
def build_parts_attributes(cls, parts: List[Part], ns_map: Dict) -> Iterator[Attr]: """ Build attributes for the given list of parts. :param parts: List of parts :param ns_map: Namespace prefix-URI map """ for part in parts: if part.element: prefix, type_name = text.split(part.element) name = type_name elif part.type: prefix, type_name = text.split(part.type) name = part.name else: logger.warning("Skip untyped message part %s", part.name) continue ns_map.update(part.ns_map) namespace = part.ns_map.get(prefix) type_qname = build_qname(namespace, type_name) native = namespace == Namespace.XS.uri namespace = "" if part.type else namespace yield cls.build_attr(name, type_qname, namespace=namespace, native=native)
def test_apply_aliases(self): self.resolver.aliases = { build_qname("xsdata", "d"): "IamD", build_qname("xsdata", "a"): "IamA", } type_a = AttrTypeFactory.create(qname="{xsdata}a") type_b = AttrTypeFactory.create(qname="{xsdata}b") type_c = AttrTypeFactory.create(qname="{xsdata}c") type_d = AttrTypeFactory.create(qname="{xsdata}d") obj = ClassFactory.create( qname="a", attrs=[ AttrFactory.create(name="a", types=[type_a]), AttrFactory.create(name="b", types=[type_b]), AttrFactory.create(name="c", types=[type_a, type_d]), ], inner=[ ClassFactory.create( qname="b", attrs=[ AttrFactory.create(name="c", types=[type_c]), AttrFactory.create(name="d", types=[type_d]), ], ) ], ) self.resolver.apply_aliases(obj) self.assertEqual(3, len(obj.attrs)) self.assertEqual(1, len(obj.attrs[0].types)) self.assertEqual(1, len(obj.attrs[1].types)) self.assertEqual(2, len(obj.attrs[2].types)) self.assertEqual("IamA", obj.attrs[0].types[0].alias) self.assertIsNone(obj.attrs[1].types[0].alias) self.assertEqual("IamA", obj.attrs[2].types[0].alias) self.assertEqual("IamD", obj.attrs[2].types[1].alias) self.assertEqual(1, len(obj.inner)) self.assertEqual(2, len(obj.inner[0].attrs)) self.assertEqual(1, len(obj.inner[0].attrs[0].types)) self.assertEqual(1, len(obj.inner[0].attrs[1].types)) self.assertIsNone(obj.inner[0].attrs[0].types[0].alias) self.assertEqual("IamD", obj.inner[0].attrs[1].types[0].alias)
def map_port_type_message(cls, message: PortTypeMessage, namespace: Optional[str]) -> Iterator[Attr]: """Build an attribute for the given port type message.""" prefix, name = text.split(message.message) source_namespace = message.ns_map.get(prefix) yield cls.build_attr(name, qname=build_qname(source_namespace, name), namespace=namespace)
def xsi_type(cls, attrs: Dict, ns_map: Dict) -> Optional[str]: """Parse the xsi:type attribute if present.""" xsi_type = attrs.get(QNames.XSI_TYPE) if not xsi_type: return None namespace, name = QNameConverter.resolve(xsi_type, ns_map) return build_qname(namespace, name)
def test_build_qname(self): self.assertEqual("{a}b", build_qname("a", "b")) self.assertEqual("b", build_qname("", "b")) self.assertEqual("b", build_qname(None, "b")) self.assertEqual("b", build_qname("b", "")) self.assertEqual("b", build_qname("b")) self.assertEqual("b", build_qname("b", None)) with self.assertRaises(ValueError): build_qname(None, None)
def test_build_class( self, mock_real_name, mock_display_help, mock_is_nillable, mock_is_abstract, mock_substitutions, mock_build_class_extensions, mock_build_class_attributes, mock_element_namespace, ): mock_real_name.return_value = "name" mock_display_help.return_value = "sos" mock_is_abstract.return_value = True mock_is_nillable.return_value = True mock_substitutions.return_value = ["foo", "sm:bar"] mock_element_namespace.return_value = "foo:name" element = Element() element.ns_map["sm"] = "sm_ns" result = SchemaMapper.build_class(element, "container", "module", "target_ns") mock_build_class_attributes.assert_called_once_with(element, result) mock_build_class_extensions.assert_called_once_with(element, result) mock_element_namespace.assert_called_once_with(element, "target_ns") expected = ClassFactory.create( qname=build_qname("target_ns", "name"), type=Element, help="sos", abstract=True, nillable=True, namespace="foo:name", ns_map=element.ns_map, package=None, module="module", substitutions=[ build_qname("target_ns", "foo"), build_qname("sm_ns", "bar"), ], container="container", ) self.assertEqual(expected, result)
def next_qname(self, namespace: str, name: str) -> str: """Append the next available index number for the given namespace and local name.""" index = 0 reserved = set(map(alnum, self.container.data.keys())) while True: index += 1 qname = build_qname(namespace, f"{name}_{index}") if alnum(qname) not in reserved: return qname
def get_type_hints(self, clazz: Type, parent_ns: Optional[str]) -> Iterator[XmlVar]: """ Build the model fields binding metadata. :param clazz: The requested dataclass type :param parent_ns: The inherited parent namespace """ type_hints = get_type_hints(clazz) default_xml_type = self.default_xml_type(clazz) for var in fields(clazz): tokens = var.metadata.get("tokens", False) xml_type = var.metadata.get("type") local_name = var.metadata.get("name") namespace = var.metadata.get("namespace") choices = var.metadata.get("choices", EMPTY_SEQUENCE) mixed = var.metadata.get("mixed", False) nillable = var.metadata.get("nillable", False) sequential = var.metadata.get("sequential", False) type_hint = type_hints[var.name] types = self.real_types(type_hint) any_type = object in types element_list = self.is_element_list(type_hint, tokens) is_class = any(is_dataclass(clazz) for clazz in types) xml_type = xml_type or (XmlType.ELEMENT if is_class else default_xml_type) local_name = local_name or self.local_name(var.name, xml_type) namespaces = self.resolve_namespaces(xml_type, namespace, parent_ns) default_namespace = self.default_namespace(namespaces) choice_vars = list( self.build_choices(clazz, var.name, parent_ns, choices)) qname = build_qname(default_namespace, local_name) default_value = self.default_value(var) yield XmlVar( xml_type=xml_type, name=var.name, qname=qname, init=var.init, mixed=mixed, tokens=tokens, any_type=any_type, nillable=nillable, dataclass=is_class, sequential=sequential, list_element=element_list, default=default_value, types=types, choices=choice_vars, namespaces=namespaces, )
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)
def test_children_extensions(self): complex_type = ComplexType( attributes=[Attribute() for _ in range(2)], simple_content=SimpleContent(restriction=Restriction(base="bk:b")), complex_content=ComplexContent(extension=Extension(base="bk:c")), ) complex_type.simple_content.restriction.index = 4 complex_type.complex_content.extension.index = 7 item = ClassFactory.create(ns_map={"bk": "book"}) children = SchemaMapper.children_extensions(complex_type, item) expected = list( map( ExtensionFactory.create, [ AttrTypeFactory.create(qname=build_qname("book", "b")), AttrTypeFactory.create(qname=build_qname("book", "c")), ], )) self.assertIsInstance(children, GeneratorType) self.assertEqual(expected, list(children))
def startElementNS(self, name: Tuple[Optional[str], str], qname: Any, attrs: Dict): """ Start element notification receiver. The receiver will flush any previous active element, append a new data frame to collect data content for the next active element and notify the main parser to prepare for next binding instruction. Converts name and attribute keys to fully qualified tags to respect the main parser api, eg (foo, bar) -> {foo}bar :param name: Namespace-name tuple :param qname: Not used """ attrs = { build_qname(key[0], key[1]): value for key, value in attrs.items() } self.start(build_qname(name[0], name[1]), attrs, self.ns_map) self.ns_map = {}
def test_map_port_type_message(self): port_type_message = PortTypeMessage(message="foo:bar") port_type_message.ns_map["foo"] = "foobar" target_namespace = "xsdata" actual = DefinitionsMapper.map_port_type_message( port_type_message, target_namespace) expected = DefinitionsMapper.build_attr("bar", qname=build_qname( "foobar", "bar"), namespace=target_namespace) self.assertIsInstance(actual, Generator) self.assertEqual([expected], list(actual))
def endElementNS(self, name: Tuple, qname: Any): """ End element notification receiver. The receiver will flush any previous active element and set the next element to be flushed. Converts name and attribute keys to fully qualified tags to respect the ain parser api, eg (foo, bar) -> {foo}bar :param name: Namespace-name tuple :param qname: Not used """ self.end(build_qname(name[0], name[1]))
def build_class(cls, element: AnyElement, target_namespace: Optional[str]) -> Class: assert element.qname is not None namespace, name = split_qname(element.qname) target = Class( qname=build_qname(target_namespace, name), namespace=cls.select_namespace(namespace, target_namespace), tag=Tag.ELEMENT, module="", ) children = [c for c in element.children if isinstance(c, AnyElement)] sequential_set = cls.sequential_names(children) for key, value in element.attributes.items(): attr_type = cls.build_attribute_type(key, value) cls.build_attribute(target, key, attr_type, target_namespace, Tag.ATTRIBUTE) for child in children: assert child.qname is not None if child.tail: target.mixed = True if child.attributes or child.children: inner = cls.build_class(child, target_namespace) attr_type = AttrType(qname=inner.qname, forward=True) target.inner.append(inner) else: attr_type = cls.build_attribute_type(child.qname, child.text) cls.build_attribute( target, child.qname, attr_type, target_namespace, Tag.ELEMENT, child.qname in sequential_set, ) if element.text: attr_type = cls.build_attribute_type("value", element.text) cls.build_attribute(target, "value", attr_type, None, Tag.SIMPLE_TYPE) return target
def create( cls, qname: Optional[str] = None, meta_name: Optional[str] = None, namespace: Optional[str] = None, tag: Optional[str] = None, abstract: bool = False, mixed: bool = False, nillable: bool = False, extensions: Optional[List[Extension]] = None, substitutions: Optional[List[str]] = None, attrs: Optional[List[Attr]] = None, inner: Optional[List[Class]] = None, ns_map: Optional[Dict] = None, package: Optional[str] = None, module: str = "tests", status: Status = Status.RAW, container: Optional[str] = None, default: Any = None, fixed: bool = False, **kwargs: Any, ) -> Class: if not qname: qname = build_qname("xsdata", f"class_{cls.next_letter()}") if ns_map is None: ns_map = copy.deepcopy(DEFAULT_NS_MAP) return Class( qname=qname, meta_name=meta_name, namespace=namespace, abstract=abstract, mixed=mixed, nillable=nillable, tag=tag or random.choice(cls.tags), extensions=extensions or [], substitutions=substitutions or [], attrs=attrs or [], inner=inner or [], package=package, module=module, ns_map=ns_map, status=status, container=container, default=default, fixed=fixed, **kwargs, )
def build_data_type(cls, target: Class, name: str, forward: bool = False) -> AttrType: """Create an attribute type for the target class.""" prefix, suffix = text.split(name) namespace = target.ns_map.get(prefix, target.target_namespace) qname = build_qname(namespace, suffix) datatype = DataType.from_qname(qname) return AttrType( qname=qname, native=datatype is not None, forward=forward, )
def build_choices( self, clazz: Type, parent_name: str, parent_namespace: Optional[str], choices: List[Dict], ): existing_types = set() globalns = sys.modules[clazz.__module__].__dict__ for choice in choices: xml_type = XmlType.WILDCARD if choice.get( "wildcard") else XmlType.ELEMENT namespace = choice.get("namespace") tokens = choice.get("tokens", False) nillable = choice.get("nillable", False) format_str = choice.get("format", None) default_value = choice.get("default_factory", choice.get("default")) types = self.real_types(_eval_type(choice["type"], globalns, None)) is_class = any(is_dataclass(clazz) for clazz in types) any_type = xml_type == XmlType.ELEMENT and object in types derived = any(True for tp in types if tp in existing_types) or any_type namespaces = self.resolve_namespaces(xml_type, namespace, parent_namespace) default_namespace = self.default_namespace(namespaces) qname = build_qname(default_namespace, choice.get("name", "any")) existing_types.update(types) yield XmlVar( xml_type=xml_type, name=parent_name, qname=qname, tokens=tokens, format=format_str, derived=derived, any_type=any_type, nillable=nillable, dataclass=is_class, default=default_value, types=types, namespaces=namespaces, )
def build_message_class(cls, definitions: Definitions, port_type_message: PortTypeMessage) -> Class: """Step 6.2: Build the input/output message class of an rpc style operation.""" message_name = text.suffix(port_type_message.message) definition_message = definitions.find_message(message_name) ns_map = definition_message.ns_map.copy() return Class( qname=build_qname(definitions.target_namespace, message_name), status=Status.PROCESSED, tag=Tag.ELEMENT, module=definitions.module, ns_map=ns_map, attrs=list( cls.build_parts_attributes(definition_message.parts, ns_map)), )