@@ -253,8 +253,6 @@ def __eq__(self, other: object) -> bool:
253253 def __lt__ (self , other : Any ) -> bool :
254254 if isinstance (other , DisjunctiveLicense ):
255255 return self .__comparable_tuple () < other .__comparable_tuple ()
256- if isinstance (other , LicenseExpressionDetailed ):
257- return False # self after any LicenseExpressionDetailed
258256 if isinstance (other , LicenseExpression ):
259257 return False # self after any LicenseExpression
260258 return NotImplemented
@@ -266,118 +264,6 @@ def __repr__(self) -> str:
266264 return f'<License id={ self ._id !r} , name={ self ._name !r} >'
267265
268266
269- @serializable .serializable_class (
270- name = 'expression' ,
271- ignore_unknown_during_deserialization = True
272- )
273- class LicenseExpression :
274- """
275- This is our internal representation of `licenseType`'s expression type that can be used in multiple places within
276- a CycloneDX BOM document.
277-
278- .. note::
279- See the CycloneDX Schema definition:
280- https://cyclonedx.org/docs/1.7/json/#components_items_licenses_items_expression
281- """
282-
283- def __init__ (
284- self , value : str , * ,
285- bom_ref : Optional [Union [str , BomRef ]] = None ,
286- acknowledgement : Optional [LicenseAcknowledgement ] = None ,
287- ) -> None :
288- self ._bom_ref = _bom_ref_from_str (bom_ref )
289- self ._value = value
290- self ._acknowledgement = acknowledgement
291-
292- @property
293- @serializable .view (SchemaVersion1Dot5 )
294- @serializable .view (SchemaVersion1Dot6 )
295- @serializable .view (SchemaVersion1Dot7 )
296- @serializable .type_mapping (BomRef )
297- @serializable .xml_attribute ()
298- @serializable .xml_name ('bom-ref' )
299- @serializable .json_name ('bom-ref' )
300- def bom_ref (self ) -> BomRef :
301- """
302- An optional identifier which can be used to reference the component elsewhere in the BOM. Every bom-ref MUST be
303- unique within the BOM.
304-
305- Returns:
306- `BomRef`
307- """
308- return self ._bom_ref
309-
310- @property
311- @serializable .xml_name ('.' )
312- @serializable .xml_string (serializable .XmlStringSerializationType .NORMALIZED_STRING )
313- @serializable .json_name ('expression' )
314- def value (self ) -> str :
315- """
316- Value of this LicenseExpression.
317-
318- Returns:
319- `str`
320- """
321- return self ._value
322-
323- @value .setter
324- def value (self , value : str ) -> None :
325- self ._value = value
326-
327- @property
328- @serializable .view (SchemaVersion1Dot6 )
329- @serializable .view (SchemaVersion1Dot7 )
330- @serializable .xml_attribute ()
331- def acknowledgement (self ) -> Optional [LicenseAcknowledgement ]:
332- """
333- Declared licenses and concluded licenses represent two different stages in the licensing process within
334- software development.
335-
336- Declared licenses refer to the initial intention of the software authors regarding the
337- licensing terms under which their code is released. On the other hand, concluded licenses are the result of a
338- comprehensive analysis of the project's codebase to identify and confirm the actual licenses of the components
339- used, which may differ from the initially declared licenses. While declared licenses provide an upfront
340- indication of the licensing intentions, concluded licenses offer a more thorough understanding of the actual
341- licensing within a project, facilitating proper compliance and risk management. Observed licenses are defined
342- in evidence.licenses. Observed licenses form the evidence necessary to substantiate a concluded license.
343-
344- Returns:
345- `LicenseAcknowledgement` or `None`
346- """
347- return self ._acknowledgement
348-
349- @acknowledgement .setter
350- def acknowledgement (self , acknowledgement : Optional [LicenseAcknowledgement ]) -> None :
351- self ._acknowledgement = acknowledgement
352-
353- def __comparable_tuple (self ) -> _ComparableTuple :
354- return _ComparableTuple ((
355- self ._acknowledgement ,
356- self ._value ,
357- self ._bom_ref .value ,
358- ))
359-
360- def __hash__ (self ) -> int :
361- return hash (self .__comparable_tuple ())
362-
363- def __eq__ (self , other : object ) -> bool :
364- if isinstance (other , LicenseExpression ):
365- return self .__comparable_tuple () == other .__comparable_tuple ()
366- return False
367-
368- def __lt__ (self , other : Any ) -> bool :
369- if isinstance (other , LicenseExpression ):
370- return self .__comparable_tuple () < other .__comparable_tuple ()
371- if isinstance (other , LicenseExpressionDetailed ):
372- return False # self after any LicenseExpressionDetailed
373- if isinstance (other , DisjunctiveLicense ):
374- return True # self before any DisjunctiveLicense
375- return NotImplemented
376-
377- def __repr__ (self ) -> str :
378- return f'<LicenseExpression value={ self ._value !r} >'
379-
380-
381267@serializable .serializable_class (ignore_unknown_during_deserialization = True )
382268class ExpressionDetails :
383269 """
@@ -494,27 +380,30 @@ def __repr__(self) -> str:
494380 return f'<ExpressionDetails bom-ref={ self .bom_ref !r} , license_identifier={ self .license_identifier } >'
495381
496382
497- @serializable .serializable_class (ignore_unknown_during_deserialization = True )
498- class LicenseExpressionDetailed :
383+ @serializable .serializable_class (
384+ name = 'expression' ,
385+ ignore_unknown_during_deserialization = True
386+ )
387+ class LicenseExpression :
499388 """
500- Specifies the details and attributes related to a software license.
501- It must be a valid SPDX license expression, along with additional properties such as license acknowledgment .
389+ This is our internal representation of `licenseType`'s expression type that can be used in multiple places within
390+ a CycloneDX BOM document .
502391
503392 .. note::
504393 See the CycloneDX Schema definition:
505- https://cyclonedx.org/docs/1.7/json/#components_items_licenses_items_oneOf_i1_expressionDetails
394+ https://cyclonedx.org/docs/1.7/json/#components_items_licenses_items_expression
506395 """
507396
508397 def __init__ (
509- self , expression : str , * ,
510- expression_details : Optional [Iterable [ExpressionDetails ]] = None ,
398+ self , value : str , * ,
511399 bom_ref : Optional [Union [str , BomRef ]] = None ,
512400 acknowledgement : Optional [LicenseAcknowledgement ] = None ,
401+ expression_details : Optional [Iterable [ExpressionDetails ]] = None ,
513402 properties : Optional [Iterable [Property ]] = None ,
514403 ) -> None :
515404 self ._bom_ref = _bom_ref_from_str (bom_ref )
516- self .expression = expression
517- self .acknowledgement = acknowledgement
405+ self ._value = value
406+ self ._acknowledgement = acknowledgement
518407 self .expression_details = expression_details or []
519408 self .properties = properties or []
520409
@@ -537,21 +426,21 @@ def bom_ref(self) -> BomRef:
537426 return self ._bom_ref
538427
539428 @property
429+ @serializable .xml_name ('.' )
540430 @serializable .xml_string (serializable .XmlStringSerializationType .NORMALIZED_STRING )
541- @serializable .xml_attribute ( )
542- def expression (self ) -> str :
431+ @serializable .json_name ( 'expression' )
432+ def value (self ) -> str :
543433 """
544- A valid SPDX license expression.
545- Refer to https://spdx.org/specifications for syntax requirements.
434+ Value of this LicenseExpression.
546435
547436 Returns:
548437 `str`
549438 """
550- return self ._expression
439+ return self ._value
551440
552- @expression .setter
553- def expression (self , expression : str ) -> None :
554- self ._expression = expression
441+ @value .setter
442+ def value (self , value : str ) -> None :
443+ self ._value = value
555444
556445 @property
557446 @serializable .view (SchemaVersion1Dot6 )
@@ -629,7 +518,7 @@ def properties(self, properties: Iterable[Property]) -> None:
629518 def __comparable_tuple (self ) -> _ComparableTuple :
630519 return _ComparableTuple ((
631520 self ._acknowledgement ,
632- self ._expression ,
521+ self ._value ,
633522 self ._bom_ref .value ,
634523 _ComparableTuple (self .expression_details ),
635524 _ComparableTuple (self .properties ),
@@ -639,28 +528,25 @@ def __hash__(self) -> int:
639528 return hash (self .__comparable_tuple ())
640529
641530 def __eq__ (self , other : object ) -> bool :
642- if isinstance (other , LicenseExpressionDetailed ):
531+ if isinstance (other , LicenseExpression ):
643532 return self .__comparable_tuple () == other .__comparable_tuple ()
644533 return False
645534
646535 def __lt__ (self , other : Any ) -> bool :
647- if isinstance (other , LicenseExpressionDetailed ):
648- return self .__comparable_tuple () < other .__comparable_tuple ()
649536 if isinstance (other , LicenseExpression ):
650- return True # self before any LicenseExpression
537+ return self . __comparable_tuple () < other . __comparable_tuple ()
651538 if isinstance (other , DisjunctiveLicense ):
652- return False # self after any DisjunctiveLicense
539+ return True # self before any DisjunctiveLicense
653540 return NotImplemented
654541
655542 def __repr__ (self ) -> str :
656- return f'<LicenseExpressionDetailed expression ={ self ._expression !r} >'
543+ return f'<LicenseExpression value ={ self ._value !r} >'
657544
658545
659- License = Union [LicenseExpression , LicenseExpressionDetailed , DisjunctiveLicense ]
546+ License = Union [LicenseExpression , DisjunctiveLicense ]
660547"""TypeAlias for a union of supported license models.
661548
662549- :class:`LicenseExpression`
663- - :class:`LicenseExpressionDetailed`
664550- :class:`DisjunctiveLicense`
665551"""
666552
@@ -708,25 +594,35 @@ def __supports_expression_details(view: Any) -> bool:
708594 return False
709595
710596 @staticmethod
711- def __transpile_license_expression_details_xml (
712- expression_detailed : LicenseExpressionDetailed ,
597+ def __serialize_license_expression_details_xml (
598+ license_expression : LicenseExpression ,
713599 view : Optional [type [serializable .ViewType ]],
714600 xmlns : Optional [str ]
715601 ) -> Element :
716- normalized : Element = expression_detailed .as_xml ( # type:ignore[attr-defined]
717- view_ = view , as_string = False , element_name = 'expression' , xmlns = xmlns )
602+ elem : Element = license_expression .as_xml ( # type:ignore[attr-defined]
603+ view_ = view , as_string = False , element_name = 'expression-detailed ' , xmlns = xmlns )
718604
719- ns_prefix = f'{{{ xmlns } }}' if xmlns else ''
720- details = normalized .findall (f'./{ ns_prefix } details' )
721- for details_elem in details :
722- normalized .remove (details_elem )
723-
724- expression_value = normalized .get (f'{ ns_prefix } expression' )
605+ expression_value = elem .text
725606 if expression_value :
726- normalized .text = expression_value
727- del normalized .attrib [f'{ ns_prefix } expression' ]
607+ elem .set (f'{{{ xmlns } }}expression' if xmlns else 'expression' , expression_value )
608+ elem .text = None
609+
610+ return elem
611+
612+ @staticmethod
613+ def __deserialize_license_expression_details_xml (
614+ li : Element ,
615+ default_ns : Optional [str ]
616+ ) -> LicenseExpression :
617+ expression_value = li .get ('expression' )
618+ if not expression_value :
619+ raise CycloneDxDeserializationException (f'unexpected content: { li !r} ' )
728620
729- return normalized
621+ license_expression = LicenseExpression .from_xml ( # type:ignore[attr-defined]
622+ li , default_ns )
623+ license_expression .value = expression_value
624+
625+ return license_expression
730626
731627 @classmethod
732628 def json_normalize (cls , o : LicenseRepository , * ,
@@ -735,12 +631,6 @@ def json_normalize(cls, o: LicenseRepository, *,
735631 if len (o ) == 0 :
736632 return None
737633
738- expression_detailed = next ((li for li in o if isinstance (li , LicenseExpressionDetailed )), None )
739- if expression_detailed :
740- if not cls .__supports_expression_details (view ):
741- warn ('LicenseExpressionDetailed is not supported in schema versions < 1.7; ignoring expressionDetails' )
742- return [json_loads (expression_detailed .as_json (view_ = view ))] # type:ignore[attr-defined]
743-
744634 expression = next ((li for li in o if isinstance (li , LicenseExpression )), None )
745635 if expression :
746636 # mixed license expression and license? this is an invalid constellation according to schema!
@@ -764,10 +654,6 @@ def json_denormalize(cls, o: list[dict[str, Any]],
764654 if 'license' in li :
765655 repo .add (DisjunctiveLicense .from_json ( # type:ignore[attr-defined]
766656 li ['license' ]))
767- elif 'expressionDetails' in li :
768- repo .add (LicenseExpressionDetailed .from_json ( # type:ignore[attr-defined]
769- li
770- ))
771657 elif 'expression' in li :
772658 repo .add (LicenseExpression .from_json ( # type:ignore[attr-defined]
773659 li
@@ -786,22 +672,19 @@ def xml_normalize(cls, o: LicenseRepository, *,
786672 return None
787673 elem = Element (element_name )
788674
789- expression_detailed = next ((li for li in o if isinstance (li , LicenseExpressionDetailed )), None )
790- if expression_detailed :
791- if cls .__supports_expression_details (view ):
792- elem .append (expression_detailed .as_xml ( # type:ignore[attr-defined]
793- view_ = view , as_string = False , element_name = 'expression-detailed' , xmlns = xmlns ))
794- else :
795- warn ('LicenseExpressionDetailed is not supported in schema versions < 1.7; ignoring details' )
796- elem .append (cls .__transpile_license_expression_details_xml (expression_detailed , view , xmlns ))
797-
798675 expression = next ((li for li in o if isinstance (li , LicenseExpression )), None )
799676 if expression :
800677 # mixed license expression and license? this is an invalid constellation according to schema!
801678 # see https://github.com/CycloneDX/specification/pull/205
802679 # but models need to allow it for backwards compatibility with JSON CDX < 1.5
803- elem .append (expression .as_xml ( # type:ignore[attr-defined]
804- view_ = view , as_string = False , element_name = 'expression' , xmlns = xmlns ))
680+
681+ if expression .expression_details and cls .__supports_expression_details (view ):
682+ elem .append (cls .__serialize_license_expression_details_xml (expression , view , xmlns ))
683+ else :
684+ if expression .expression_details :
685+ warn ('LicenseExpression details are not supported in schema versions < 1.7; skipping serialization' )
686+ elem .append (expression .as_xml ( # type:ignore[attr-defined]
687+ view_ = view , as_string = False , element_name = 'expression' , xmlns = xmlns ))
805688 else :
806689 elem .extend (
807690 li .as_xml ( # type:ignore[attr-defined]
@@ -821,12 +704,11 @@ def xml_denormalize(cls, o: Element,
821704 if tag == 'license' :
822705 repo .add (DisjunctiveLicense .from_xml ( # type:ignore[attr-defined]
823706 li , default_ns ))
824- elif tag == 'expression-detailed' :
825- repo .add (LicenseExpressionDetailed .from_xml ( # type:ignore[attr-defined]
826- li , default_ns ))
827707 elif tag == 'expression' :
828708 repo .add (LicenseExpression .from_xml ( # type:ignore[attr-defined]
829709 li , default_ns ))
710+ elif tag == 'expression-detailed' :
711+ repo .add (cls .__deserialize_license_expression_details_xml (li , default_ns ))
830712 else :
831713 raise CycloneDxDeserializationException (f'unexpected: { li !r} ' )
832714 return repo
0 commit comments