def copy_attributes(cls, source: Class, target: Class, extension: Extension): """ Copy the attributes and inner classes from the source class to the target class and remove the extension that links the two classes together. The new attributes are prepended in the list unless if they are supposed to be last in a sequence. """ prefix = text.prefix(extension.type.name) target.extensions.remove(extension) target_attr_names = {text.suffix(attr.name) for attr in target.attrs} index = 0 for attr in source.attrs: if text.suffix(attr.name) not in target_attr_names: clone = cls.clone_attribute(attr, extension.restrictions, prefix) if attr.index == sys.maxsize: target.attrs.append(clone) continue target.attrs.insert(index, clone) index += 1 cls.copy_inner_classes(source, target)
def map_port(cls, definitions: Definitions, port: ServicePort) -> Iterator[Class]: """Step 2: Match a ServicePort to a Binding and PortType object and delegate the process to the next entry point.""" binding = definitions.find_binding(text.suffix(port.binding)) port_type = definitions.find_port_type(text.suffix(binding.type)) elements = collections.concat(binding.extended_elements, port.extended_elements) config = cls.attributes(elements) yield from cls.map_binding(definitions, binding, port_type, config)
def sanitize_attribute_name(cls, attrs: List[Attr], index: int): """Check if the attribute at the given index has a duplicate name and prepend if exists the attribute namespace.""" current = attrs[index] current.name = text.suffix(current.name) exists = any( attr is not current and current.name == text.suffix(attr.name) for attr in attrs) if exists and current.namespace: current.name = f"{current.namespace}_{current.name}"
def map_binding_message_parts(cls, definitions: Definitions, message: str, extended: AnyElement, ns_map: Dict) -> Iterator[Attr]: """Find a Message instance and map its parts to attributes according to the the extensible element..""" parts = [] if "part" in extended.attributes: parts.append(extended.attributes["part"]) elif "parts" in extended.attributes: parts.extend(extended.attributes["parts"].split()) if "message" in extended.attributes: message_name = local_name(extended.attributes["message"]) else: message_name = text.suffix(message) definition_message = definitions.find_message(message_name) message_parts = definition_message.parts if parts: message_parts = [ part for part in message_parts if part.name in parts ] yield from cls.build_parts_attributes(message_parts, ns_map)
def build_envelope_fault( cls, definitions: Definitions, port_type_operation: PortTypeOperation, target: Class, ): """Build inner fault class with default fields.""" ns_map: Dict = {} body = next(inner for inner in target.inner if inner.name == "Body") fault_class = cls.build_inner_class(body, "Fault", target.namespace) detail_attrs: List[Attr] = [] for fault in port_type_operation.faults: message = definitions.find_message(text.suffix(fault.message)) detail_attrs.extend( cls.build_parts_attributes(message.parts, ns_map)) default_fields = ["faultcode", "faultstring", "faultactor"] if detail_attrs: detail = cls.build_inner_class(fault_class, "detail", namespace="") detail.attrs.extend(detail_attrs) else: default_fields.append("detail") collections.prepend( fault_class.attrs, *[ cls.build_attr(f, str(DataType.STRING), native=True, namespace="") for f in default_fields ], )
def process_attribute_name(cls, attr: Attr): """ Sanitize attribute name in preparation for duplicate attribute names handler. Steps: 1. Remove non alpha numerical values 2. Handle Enum negative numerical values 3. Remove namespaces prefixes 4. Ensure name not empty 5. Ensure name starts with a letter """ if attr.is_enumeration: attr.name = attr.default if re.match(r"^-\d*\.?\d+$", attr.name): attr.name = f"value_minus_{attr.name}" else: attr.name = re.sub("[^0-9a-zA-Z]", " ", attr.name).strip() else: attr.name = re.sub("[^0-9a-zA-Z]", " ", text.suffix(attr.name)).strip() if not attr.name: attr.name = "value" elif not attr.name[0].isalpha(): attr.name = f"value_{attr.name}"
def merge_redefined_classes(cls, classes: List[Class]): """Merge original and redefined classes.""" grouped: Dict[str, List[Class]] = defaultdict(list) for item in classes: grouped[f"{item.type.__name__}{item.source_qname()}"].append(item) for items in grouped.values(): if len(items) == 1: continue winner: Class = items.pop() for item in items: classes.remove(item) self_extension = next( (ext for ext in winner.extensions if text.suffix(ext.type.name) == winner.name), None, ) if not self_extension: continue cls.copy_attributes(item, winner, self_extension) for looser_ext in item.extensions: new_ext = looser_ext.clone() new_ext.restrictions.merge(self_extension.restrictions) winner.extensions.append(new_ext)
def build_class_attribute( self, target: Class, obj: ElementBase, parent_restrictions: Restrictions ): """Generate and append an attribute field to the target class.""" types = self.build_class_attribute_types(target, obj) restrictions = Restrictions.from_element(obj) if obj.class_name in (Tag.ELEMENT, Tag.ANY): restrictions.merge(parent_restrictions) if restrictions.prohibited: return name = obj.real_name target.ns_map.update(obj.ns_map) target.attrs.append( Attr( index=obj.index, name=name, local_name=text.suffix(name), default=obj.default_value, fixed=obj.is_fixed, types=types, tag=obj.class_name, help=obj.display_help, namespace=self.element_namespace(obj), restrictions=restrictions, ) )
def find_circular_group(cls, target: Class) -> Optional[Attr]: """Search for any target class attributes that is a circular reference.""" for attr in target.attrs: if text.suffix(attr.name) == target.name: return attr return None
def find_circular_extension(cls, target: Class) -> Optional[Extension]: """Search for any target class extensions that is a circular reference.""" for ext in target.extensions: if text.suffix(ext.type.name) == target.name: return ext return None
def attribute_name(cls, name: str) -> str: """ Strip reference prefix and turn to snake case. If the name is one of the python reserved words append the prefix _value """ local_name = text.suffix(name) return text.snake_case(safe_snake(local_name))
def process_attribute(cls, target: Class, attr: Attr, parents: List[str]): """Normalize attribute properties.""" attr.name = cls.attribute_name(attr.name) attr.display_type = cls.attribute_display_type(attr, parents) attr.default = cls.attribute_default(attr, target.ns_map) attr.xml_type = cls.xml_type_map.get(attr.tag) if attr.local_name: attr.local_name = text.suffix(attr.local_name)
def copy_attributes(cls, source: Class, target: Class, extension: Extension): prefix = text.prefix(extension.type.name) target.extensions.remove(extension) target_attr_names = {text.suffix(attr.name) for attr in target.attrs} index = 0 for attr in source.attrs: if text.suffix(attr.name) not in target_attr_names: clone = cls.clone_attribute(attr, extension.restrictions, prefix) if attr.index == sys.maxsize: target.attrs.append(clone) continue target.attrs.insert(index, clone) index += 1 cls.copy_inner_classes(source, target)
def rename_attr_dependencies(self, attr: Attr, reference: int, replace: str): """Search and replace the old qualified attribute type name with the new one in the attr types, choices and default value.""" for attr_type in attr.types: if attr_type.reference == reference: attr_type.qname = replace if isinstance(attr.default, str) and attr.default.startswith("@enum@"): member = text.suffix(attr.default, "::") attr.default = f"@enum@{replace}::{member}" for choice in attr.choices: self.rename_attr_dependencies(choice, reference, replace)
def real_name(self) -> str: """ Return the real name for this element by looking by looking either to the name or ref attribute value. :raises SchemaValueError: when instance has no name/ref attribute. """ name = getattr(self, "name", None) or getattr(self, "ref", None) if name: return text.suffix(name) raise SchemaValueError( f"Schema class `{self.class_name}` unknown real name.")
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)), )
def attribute_default(attr: Attr, ns_map: Optional[Dict] = None) -> Any: """Generate the field default value/factory for the given attribute.""" if attr.is_list: return "list" if attr.is_map: return "dict" if not isinstance(attr.default, str): return attr.default data_types = { attr_type.native_code: attr_type.native_type for attr_type in attr.types if attr_type.native } local_types = list(set(data_types.values())) default_value = to_python(local_types, attr.default, ns_map, in_order=False) if isinstance(default_value, str): if DataType.NMTOKENS.code in data_types: default_value = quoteattr(" ".join( filter(None, map(str.strip, re.split(r"\s+", default_value))))) elif default_value.startswith("@enum@"): source, enumeration = default_value[6:].split("::", 1) attr_type = next(attr_type for attr_type in attr.types if text.suffix(attr_type.name) == source) if attr_type.alias: source = attr_type.alias default_value = f"{class_name(source)}.{constant_name(enumeration)}" else: default_value = quoteattr(default_value) elif isinstance(default_value, float) and math.isinf(default_value): default_value = f"float('{default_value}')" elif isinstance(default_value, Decimal): default_value = repr(default_value) elif isinstance(default_value, QName): default_value = ( f'QName("{default_value.namespace}", "{default_value.localname}")') return default_value
def attribute_name(name: str) -> str: """Apply python conventions for instance variable names.""" return text.snake_case(utils.safe_snake(text.suffix(name)))
def type_name(cls, attr_type: AttrType) -> str: """Convert xsd types to python or apply class name conventions after stripping any reference prefix.""" return attr_type.native_name or cls.class_name(text.suffix(attr_type.name))
def type_name(attr_type: AttrType) -> str: """Return native python type name or apply class name conventions.""" return attr_type.native_name or class_name(text.suffix(attr_type.name))
def is_xsi_type(self) -> bool: return (QNames.XSI_TYPE.namespace == self.namespace and QNames.XSI_TYPE.localname == text.suffix(self.name))
def is_xsi_type(self) -> bool: """Return whether this attribute qualified name is equal to xsi:type.""" return (QNames.XSI_TYPE.namespace == self.namespace and QNames.XSI_TYPE.localname == text.suffix(self.name))