def test_copy_attributes(self, mock_clone_attribute, mock_copy_inner_classes): mock_clone_attribute.side_effect = lambda x, y, z: x.clone() target = ClassFactory.create(attrs=[ AttrFactory.create(name="foo:a"), AttrFactory.create(name="b") ]) source = ClassFactory.create(attrs=[ AttrFactory.create(name="c", index=sys.maxsize), AttrFactory.create(name="a"), AttrFactory.create(name="boo:b"), AttrFactory.create(name="d"), ]) extension = ExtensionFactory.create(type=AttrTypeFactory.create( name="foo:foo")) target.extensions.append(extension) ClassUtils.copy_attributes(source, target, extension) self.assertEqual(["foo:a", "b", "d", "c"], [attr.name for attr in target.attrs]) mock_copy_inner_classes.assert_called_once_with(source, target) mock_clone_attribute.assert_has_calls([ mock.call(source.attrs[0], extension.restrictions, "foo"), mock.call(source.attrs[3], extension.restrictions, "foo"), ])
def test_copy_group_attributes(self, mock_clone_attribute, mock_copy_inner_classes): mock_clone_attribute.side_effect = lambda x, y: x.clone() source = ClassFactory.elements(2) source.inner.append(ClassFactory.create()) target = ClassFactory.elements(3) attrs = list(target.attrs) attrs[1].name = "bar" ClassUtils.copy_group_attributes(source, target, target.attrs[1]) self.assertEqual(4, len(target.attrs)) self.assertEqual(source.attrs[0], target.attrs[1]) self.assertEqual(source.attrs[1], target.attrs[2]) mock_copy_inner_classes.assert_has_calls( [ mock.call(source, target, source.attrs[0]), mock.call(source, target, source.attrs[1]), ] ) mock_clone_attribute.assert_has_calls( [ mock.call(source.attrs[0], attrs[1].restrictions), mock.call(source.attrs[1], attrs[1].restrictions), ] )
def process_simple_dependency(cls, source: Class, target: Class, attr: Attr, attr_type: AttrType): """ Replace the given attribute type with the types of the single field source class. Ignore enumerations and gracefully handle dump types with no attributes. :raises: AnalyzerValueError if the source class has more than one attributes """ if source.is_enumeration: return total = len(source.attrs) if total == 0: cls.reset_attribute_type(attr_type) elif total == 1: source_attr = source.attrs[0] index = attr.types.index(attr_type) attr.types.pop(index) for source_attr_type in source_attr.types: clone_type = source_attr_type.clone() attr.types.insert(index, clone_type) index += 1 restrictions = source_attr.restrictions.clone() restrictions.merge(attr.restrictions) attr.restrictions = restrictions ClassUtils.copy_inner_classes(source, target) else: raise AnalyzerValueError( f"{source.type.__name__} with more than one attribute: `{source.name}`" )
def test_merge_attributes(self): target = ClassFactory.create( attrs=[ AttrFactory.element(name="a", index=10), AttrFactory.element(name="b", index=1), AttrFactory.element(name="c", index=2), AttrFactory.attribute(name="id", index=0), ] ) source = target.clone() target.attrs[0].restrictions.min_occurs = 2 target.attrs[0].restrictions.max_occurs = 3 source.attrs[1].restrictions.min_occurs = 3 source.attrs[1].restrictions.max_occurs = 4 source.attrs[3].restrictions.min_occurs = 3 source.attrs[3].restrictions.max_occurs = 4 source.attrs.append(AttrFactory.enumeration(name="d", index=4)) ClassUtils.merge_attributes(target, source) names = ["id", "b", "c", "d", "a"] min_occurs = [0, 0, 0, None, 0] max_occurs = [4, 4, 1, None, 3] self.assertEqual(names, [x.name for x in target.attrs]) self.assertEqual(min_occurs, [x.restrictions.min_occurs for x in target.attrs]) self.assertEqual(max_occurs, [x.restrictions.max_occurs for x in target.attrs])
def copy_attribute_properties(cls, source: Class, target: Class, attr: Attr, attr_type: AttrType): """ Replace the given attribute type with the types of the single field source class. Ignore enumerations and gracefully handle dump types with no attributes. :raises: AnalyzerValueError if the source class has more than one attributes """ source_attr = source.attrs[0] index = attr.types.index(attr_type) attr.types.pop(index) for source_attr_type in source_attr.types: clone_type = source_attr_type.clone() attr.types.insert(index, clone_type) index += 1 ClassUtils.copy_inner_class(source, target, attr, clone_type) restrictions = source_attr.restrictions.clone() restrictions.merge(attr.restrictions) attr.restrictions = restrictions attr.help = attr.help or source_attr.help if source.nillable: restrictions.nillable = True
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)
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))
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))
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)
def test_find_inner(self): obj = ClassFactory.create(qname="{a}parent") first = ClassFactory.create(qname="{a}a") second = ClassFactory.create(qname="{c}c") third = ClassFactory.enumeration(2, qname="{d}d") obj.inner.extend((first, second, third)) with self.assertRaises(CodeGenerationError) as cm: self.assertIsNone(ClassUtils.find_inner(obj, "nope")) self.assertEqual("Missing inner class {a}parent.nope", str(cm.exception)) self.assertEqual(first, ClassUtils.find_inner(obj, "{a}a")) self.assertEqual(second, ClassUtils.find_inner(obj, "{c}c")) self.assertEqual(third, ClassUtils.find_inner(obj, "{d}d"))
def test_copy_inner_classes(self, mock_copy_inner_class): source = ClassFactory.create() target = ClassFactory.create() attr = AttrFactory.create(types=AttrTypeFactory.list(3)) ClassUtils.copy_inner_classes(source, target, attr) mock_copy_inner_class.assert_has_calls( [ mock.call(source, target, attr, attr.types[0]), mock.call(source, target, attr, attr.types[1]), mock.call(source, target, attr, attr.types[2]), ] )
def test_copy_inner_class(self): source = ClassFactory.create() inner = ClassFactory.create(qname="a", module="b", package="c") target = ClassFactory.create() attr = AttrFactory.create() attr_type = AttrTypeFactory.create(forward=True, qname=inner.qname) source.inner.append(inner) ClassUtils.copy_inner_class(source, target, attr, attr_type) self.assertEqual(1, len(target.inner)) self.assertIsNot(inner, target.inner[0]) self.assertEqual(target.package, target.inner[0].package) self.assertEqual(target.module, target.inner[0].module) self.assertEqual(inner.qname, target.inner[0].qname)
def process_complex_extension(cls, source: Class, target: Class, ext: Extension): """ Complex flatten extension handler for primary classes eg ComplexType, Element. Compare source and target classes and either remove the extension completely, copy all source attributes to the target class or leave the extension alone. """ if cls.should_remove_extension(source, target): target.extensions.remove(ext) elif cls.should_flatten_extension(source, target): ClassUtils.copy_attributes(source, target, ext) else: ext.type.reference = id(source) logger.debug("Ignore extension: %s", ext.type.name)
def process_complex_extension(cls, source: Class, target: Class, ext: Extension): """ Complex flatten extension handler for primary classes eg ComplexType, Element. Compare source and target classes and either remove the extension completely, copy all source attributes to the target class or leave the extension alone. """ res = cls.compare_attributes(source, target) if res == cls.REMOVE_EXTENSION: target.extensions.remove(ext) elif res == cls.FLATTEN_EXTENSION: ClassUtils.copy_attributes(source, target, ext) else: logger.debug("Ignore extension: %s", ext.type.name)
def map(cls, element: AnyElement) -> List[Class]: """Map schema children elements to classes.""" assert element.qname is not None target_namespace, module = split_qname(element.qname) target = cls.build_class(element, target_namespace) return list(ClassUtils.flatten(target, module))
def validate_type_overrides(cls, source: Class, target: Class) -> bool: """Validate every override is using a subset of the parent attr types.""" for attr in target.attrs: src_attr = ClassUtils.find_attr(source, attr.name) if src_attr and any(tp not in src_attr.types for tp in attr.types): return False return True
def process_attribute(self, target: Class, attr: Attr): """ Find the source class the attribute refers to and copy its attributes to the target class. :raises AnalyzerValueError: if source class is not found. """ qname = attr.types[0].qname # group attributes have one type only. source = self.container.find(qname, condition=lambda x: x.type in (AttributeGroup, Group)) if not source: raise AnalyzerValueError(f"Group attribute not found: `{qname}`") if source is target: target.attrs.remove(attr) else: ClassUtils.copy_group_attributes(source, target, attr)
def process_simple_extension(cls, source: Class, target: Class, ext: Extension): """ Simple flatten extension handler for common classes eg SimpleType, Restriction. Steps: 1. If target is source: drop the extension. 2. If source is enumeration and target isn't create default value attribute. 3. If both source and target are enumerations copy all attributes. 4. If both source and target are not enumerations copy all attributes. 5. If target is enumeration: drop the extension. """ if source is target: target.extensions.remove(ext) elif source.is_enumeration and not target.is_enumeration: cls.add_default_attribute(target, ext) elif source.is_enumeration == target.is_enumeration: ClassUtils.copy_attributes(source, target, ext) else: # this is an enumeration target.extensions.remove(ext)
def process_complex_extension(cls, source: Class, target: Class, ext: Extension): """ Complex flatten extension handler for primary classes eg ComplexType, Element. Drop extension when: - source includes all target attributes Copy all attributes when: - source includes some of the target attributes - source has suffix attribute and target has at least one attribute - target has at least one suffix attribute - source is marked as strict type """ res = cls.compare_attributes(source, target) if res == cls.INCLUDES_ALL: target.extensions.remove(ext) elif (res == cls.INCLUDES_SOME or source.strict_type or (source.has_suffix_attr and len(target.attrs) > 0) or target.has_suffix_attr): ClassUtils.copy_attributes(source, target, ext)
def test_copy_inner_classes(self): source = ClassFactory.create( inner=ClassFactory.list(2, package="a", module="b")) target = ClassFactory.create() ClassUtils.copy_inner_classes(source, target) # All good copy all self.assertEqual(2, len(target.inner)) ClassUtils.copy_inner_classes(source, target) # Inner classes exist skip self.assertEqual(2, len(target.inner)) source.inner.append(target) attr = AttrFactory.create(types=[ AttrTypeFactory.create(name=target.name, forward=True), AttrTypeFactory.create(name=target.name, forward=False), AttrTypeFactory.create(name="foobar"), ]) target.attrs.append(attr) ClassUtils.copy_inner_classes(source, target) # Inner class matches target self.assertEqual(2, len(target.inner)) for inner in target.inner: self.assertEqual(target.package, inner.package) self.assertEqual(target.module, inner.module) self.assertTrue(attr.types[0].circular) self.assertFalse(attr.types[1].circular) self.assertFalse(attr.types[2].circular)
def process_xml_documents(self, uris: List[str]): """Process a list of xml resources.""" classes = [] parser = TreeParser() for uri in uris: input_stream = self.load_resource(uri) if input_stream: logger.info("Parsing document %s", os.path.basename(uri)) any_element: AnyElement = parser.from_bytes(input_stream) classes.extend(ElementMapper.map(any_element)) dirname = os.path.dirname(uris[0]) if uris else "" self.class_map[dirname] = ClassUtils.reduce(classes)
def process_json_documents(self, uris: List[str]): """Process a list of json resources.""" classes = [] for uri in uris: input_stream = self.load_resource(uri) if input_stream: data = json.load(io.BytesIO(input_stream)) logger.info("Parsing document %s", os.path.basename(uri)) name = self.config.output.package.split(".")[-1] classes.extend(DictMapper.map(data, name)) dirname = os.path.dirname(uris[0]) if uris else "" self.class_map[dirname] = ClassUtils.reduce(classes)
def test_clone_attribute(self): attr = AttrFactory.create( restrictions=Restrictions(length=1), types=[ AttrTypeFactory.create(qname="x"), AttrTypeFactory.create(qname="y"), AttrTypeFactory.xs_int(), ], ) restrictions = Restrictions(length=2) clone = ClassUtils.clone_attribute(attr, restrictions) self.assertEqual(2, clone.restrictions.length) self.assertIsNot(attr, clone)
def test_reduce(self, mock_merge_attributes): first = ClassFactory.elements(2) second = first.clone() second.attrs.append(AttrFactory.create()) third = second.clone() third.attrs.append(AttrFactory.create()) fourth = ClassFactory.create() actual = ClassUtils.reduce([first, second, third, fourth]) self.assertEqual([third, fourth], list(actual)) mock_merge_attributes.assert_has_calls( [ mock.call(third, first), mock.call(third, second), ] )
def test_clone_attribute(self): attr = AttrFactory.create( restrictions=RestrictionsFactory.create(length=1), types=[ AttrTypeFactory.create(name="foo:x"), AttrTypeFactory.create(name="y"), AttrTypeFactory.xs_int(), ], ) restrictions = RestrictionsFactory.create(length=2) prefix = "foo" clone = ClassUtils.clone_attribute(attr, restrictions, prefix) self.assertEqual(["foo:x", "foo:y", "integer"], [x.name for x in clone.types]) self.assertEqual(2, clone.restrictions.length) self.assertIsNot(attr, clone)
def process_attribute(self, target: Class, attr: Attr): """ Check if the given attribute matches any substitution class in order to clone it's attributes to the target class. The cloned attributes are placed below the attribute the are supposed to substitute. """ index = target.attrs.index(attr) assert self.substitutions is not None for attr_type in attr.types: for substitution in self.substitutions.get(attr_type.qname, []): clone = ClassUtils.clone_attribute(substitution, attr.restrictions) pos = collections.find(target.attrs, clone) index = pos + 1 if pos > -1 else index target.attrs.insert(index, clone) self.process_attribute(target, clone)
def test_flatten(self): target = ClassFactory.create( qname="{xsdata}root", attrs=AttrFactory.list(3), inner=ClassFactory.list(2) ) for attr in target.attrs: attr.types.extend([x.clone() for x in attr.types]) for tp in attr.types: tp.forward = True result = ClassUtils.flatten(target, "xsdata") actual = list(result) self.assertIsInstance(result, Generator) self.assertEqual(3, len(actual)) for obj in actual: self.assertEqual("xsdata", obj.module) for attr in target.attrs: self.assertEqual(1, len(attr.types)) self.assertFalse(attr.types[0].forward)
def merge_redefined_type(cls, source: Class, target: Class): """ Copy any attributes and extensions to redefined types from the original definitions. Redefined inheritance is optional search for self references in extensions and attribute groups. """ circular_extension = cls.find_circular_extension(target) circular_group = cls.find_circular_group(target) if circular_extension: ClassUtils.copy_attributes(source, target, circular_extension) ClassUtils.copy_extensions(source, target, circular_extension) if circular_group: ClassUtils.copy_group_attributes(source, target, circular_group)
def map(cls, data: Dict, name: str) -> List[Class]: """Convert a dictionary to a list of codegen classes.""" target = cls.build_class(data, name) return list(ClassUtils.flatten(target, name))
def find_inner(self, source: Class, qname: str) -> Class: inner = ClassUtils.find_inner(source, qname) if inner.status == Status.RAW: self.process_class(inner) return inner