Skip to content

XML serialize helpers should also support iterables of elements #170

@jloehel

Description

@jloehel

py-serializable version: 2.0.0
python version: 3.13.3
os: linux

What is the problem?

I try to implement the missing identity element of componentEvidenceType into cyclonedx-python-lib. Since version 1.6 of the schema the identity has maxOccurs="unbounded" that means something like this is possible in 1.6:

<evidence>
  <identity>
   <field>purl</field>
  </identity>
  <identity>
   <field>name</field>
  </identity>
</evidence>

I am developing now a helper with a custom xml_normalize function. The problem I have now is that only:

  • None
  • a single Element
  • a string which is used as value

are supported:

v_ser = prop_info.custom_type.xml_normalize(
v, view=view_, element_name=new_key, xmlns=xmlns, prop_info=prop_info, ctx=self.__class__)
if v_ser is None:
pass # skip the element
elif isinstance(v_ser, Element):
this_e.append(v_ser)
else:
SubElement(this_e, new_key).text = _xs_string_mod_apply(str(v_ser),

How to improve this?

It would be great to support also List[Element]. Maybe something like that:

elif isinstance(ver_ser, List):
    for element in ver_ser:
        this_e.append(element)

With this simple approach something like this is possible:

   988     @classmethod                                                           
   989     def xml_normalize(                                                     
   990         cls, o: SortedSet[Identity], *,                                    
   991         element_name: str,                                                 
   992         view: Optional[type['ViewType']],                                  
   993         xmlns: Optional[str],                                              
   994         **__: Any                                                          
~  995     ) -> Optional[list[Element]]:                                          
~  996         if view is not None:                                               
+  997             schema_version: BaseSchemaVersion = cast(BaseSchemaVersion, view())
+  998             if schema_version.schema_version_enum < SchemaVersion.V1_6 and len(o) > 1:
+  999                 raise SerializationOfUnexpectedValueException(                                                                                                
+ 1000                     "The schema version 1.5 only supports a single identity object" 
+ 1001                 )                                                          
+ 1002             else:                                                          
+ 1003                 return [                                                   
E 1004                     identity.as_xml(                                       
+ 1005                         view_=view,                                        
+ 1006                         as_string=False,                                   
+ 1007                         element_name=element_name,                         
+ 1008                         xmlns=xmlns,                                       
+ 1009                     )                                                      
+ 1010                     for identity in o                                      
+ 1011                 ]                                                          
+ 1012         return None 
>>> from cyclonedx.schema.schema import SchemaVersion1Dot4, SchemaVersion1Dot5, SchemaVersion1Dot6
>>> from cyclonedx.model.component import ComponentEvidence, Identity, IdentityField
>>> foo = ComponentEvidence(identity=Identity(field=IdentityField.PURL))
>>> foo.as_xml(view_=SchemaVersion1Dot6)
'<componentEvidence><identity><field>purl</field></identity></componentEvidence>'
>>> foo.as_xml(view_=SchemaVersion1Dot5)
'<componentEvidence><identity><field>purl</field></identity></componentEvidence>'
>>> bar = ComponentEvidence(identity=[Identity(field=IdentityField.PURL), Identity(field=IdentityField.NAME)])
>>> bar.as_xml(view_=SchemaVersion1Dot6)
'<componentEvidence><identity><field>name</field></identity><identity><field>purl</field></identity></componentEvidence>'
>>> bar.as_xml(view_=SchemaVersion1Dot5)
Traceback (most recent call last):
  File "<python-input-7>", line 1, in <module>
    bar.as_xml(view_=SchemaVersion1Dot5)
    ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jloehel/projects/github.com/jloehel/cyclonedx-python-lib/.venv/lib64/python3.13/site-packages/py_serializable/__init__.py", line 508, in as_xml
    v_ser = prop_info.custom_type.xml_normalize(
        v, view=view_, element_name=new_key, xmlns=xmlns, prop_info=prop_info, ctx=self.__class__)
  File "/home/jloehel/projects/github.com/jloehel/cyclonedx-python-lib/cyclonedx/model/component.py", line 999, in xml_normalize
    raise SerializationOfUnexpectedValueException(
        "The schema version 1.5 only supports a single identity object"
    )
cyclonedx.exception.serialization.SerializationOfUnexpectedValueException: The schema version 1.5 only supports a single identity object

edit: Maybe it's better to consider the helper for prop_info.is_array because

@serializable.xml_array(serializable.XmlArraySerializationType.FLAT, child_name='identity')

is doing the right thing. I just wanna raise an exception in a special case.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions