2121import warnings
2222from datetime import datetime
2323from enum import Enum
24- from typing import Iterable , Optional , Set
24+ from typing import Any , Iterable , Optional , Tuple , TypeVar
25+
26+ from sortedcontainers import SortedSet
2527
2628from ..exception .model import (
2729 InvalidLocaleTypeException ,
@@ -57,7 +59,44 @@ def sha1sum(filename: str) -> str:
5759 return h .hexdigest ()
5860
5961
60- class DataFlow (Enum ):
62+ _T = TypeVar ('_T' )
63+
64+
65+ class ComparableTuple (Tuple [Optional [_T ], ...]):
66+ """
67+ Allows comparison of tuples, allowing for None values.
68+ """
69+
70+ def __lt__ (self , other : Any ) -> bool :
71+ for s , o in zip (self , other ):
72+ if s == o :
73+ continue
74+ if s is None :
75+ return False
76+ if o is None :
77+ return True
78+ if s < o :
79+ return True
80+ if s > o :
81+ return False
82+ return False
83+
84+ def __gt__ (self , other : Any ) -> bool :
85+ for s , o in zip (self , other ):
86+ if s == o :
87+ continue
88+ if s is None :
89+ return True
90+ if o is None :
91+ return False
92+ if s < o :
93+ return False
94+ if s > o :
95+ return True
96+ return False
97+
98+
99+ class DataFlow (str , Enum ):
61100 """
62101 This is our internal representation of the dataFlowType simple type within the CycloneDX standard.
63102
@@ -132,7 +171,7 @@ def __repr__(self) -> str:
132171 return f'<DataClassification flow={ self .flow } >'
133172
134173
135- class Encoding (Enum ):
174+ class Encoding (str , Enum ):
136175 """
137176 This is our internal representation of the encoding simple type within the CycloneDX standard.
138177
@@ -208,14 +247,20 @@ def __eq__(self, other: object) -> bool:
208247 return hash (other ) == hash (self )
209248 return False
210249
250+ def __lt__ (self , other : Any ) -> bool :
251+ if isinstance (other , AttachedText ):
252+ return ComparableTuple ((self .content_type , self .content , self .encoding )) < \
253+ ComparableTuple ((other .content_type , other .content , other .encoding ))
254+ return NotImplemented
255+
211256 def __hash__ (self ) -> int :
212257 return hash ((self .content , self .content_type , self .encoding ))
213258
214259 def __repr__ (self ) -> str :
215260 return f'<AttachedText content-type={ self .content_type } , encoding={ self .encoding } >'
216261
217262
218- class HashAlgorithm (Enum ):
263+ class HashAlgorithm (str , Enum ):
219264 """
220265 This is our internal representation of the hashAlg simple type within the CycloneDX standard.
221266
@@ -320,14 +365,19 @@ def __eq__(self, other: object) -> bool:
320365 return hash (other ) == hash (self )
321366 return False
322367
368+ def __lt__ (self , other : Any ) -> bool :
369+ if isinstance (other , HashType ):
370+ return ComparableTuple ((self .alg , self .content )) < ComparableTuple ((other .alg , other .content ))
371+ return NotImplemented
372+
323373 def __hash__ (self ) -> int :
324374 return hash ((self .alg , self .content ))
325375
326376 def __repr__ (self ) -> str :
327377 return f'<HashType { self .alg .name } :{ self .content } >'
328378
329379
330- class ExternalReferenceType (Enum ):
380+ class ExternalReferenceType (str , Enum ):
331381 """
332382 Enum object that defines the permissible 'types' for an External Reference according to the CycloneDX schema.
333383
@@ -378,6 +428,11 @@ def __eq__(self, other: object) -> bool:
378428 return hash (other ) == hash (self )
379429 return False
380430
431+ def __lt__ (self , other : Any ) -> bool :
432+ if isinstance (other , XsUri ):
433+ return self ._uri < other ._uri
434+ return NotImplemented
435+
381436 def __hash__ (self ) -> int :
382437 return hash (self ._uri )
383438
@@ -402,7 +457,7 @@ def __init__(self, *, reference_type: ExternalReferenceType, url: XsUri, comment
402457 self .url = url
403458 self .comment = comment
404459 self .type = reference_type
405- self .hashes = set (hashes or [])
460+ self .hashes = SortedSet (hashes or [])
406461
407462 @property
408463 def url (self ) -> XsUri :
@@ -450,7 +505,7 @@ def type(self, type_: ExternalReferenceType) -> None:
450505 self ._type = type_
451506
452507 @property
453- def hashes (self ) -> Set [HashType ]:
508+ def hashes (self ) -> "SortedSet [HashType]" :
454509 """
455510 The hashes of the external reference (if applicable).
456511
@@ -461,13 +516,19 @@ def hashes(self) -> Set[HashType]:
461516
462517 @hashes .setter
463518 def hashes (self , hashes : Iterable [HashType ]) -> None :
464- self ._hashes = set (hashes )
519+ self ._hashes = SortedSet (hashes )
465520
466521 def __eq__ (self , other : object ) -> bool :
467522 if isinstance (other , ExternalReference ):
468523 return hash (other ) == hash (self )
469524 return False
470525
526+ def __lt__ (self , other : Any ) -> bool :
527+ if isinstance (other , ExternalReference ):
528+ return ComparableTuple ((self ._type , self ._url , self ._comment )) < \
529+ ComparableTuple ((other ._type , other ._url , other ._comment ))
530+ return NotImplemented
531+
471532 def __hash__ (self ) -> int :
472533 return hash ((
473534 self ._type , self ._url , self ._comment ,
@@ -566,6 +627,11 @@ def __eq__(self, other: object) -> bool:
566627 return hash (other ) == hash (self )
567628 return False
568629
630+ def __lt__ (self , other : Any ) -> bool :
631+ if isinstance (other , License ):
632+ return ComparableTuple ((self .id , self .name )) < ComparableTuple ((other .id , other .name ))
633+ return NotImplemented
634+
569635 def __hash__ (self ) -> int :
570636 return hash ((self .id , self .name , self .text , self .url ))
571637
@@ -633,6 +699,11 @@ def __eq__(self, other: object) -> bool:
633699 return hash (other ) == hash (self )
634700 return False
635701
702+ def __lt__ (self , other : Any ) -> bool :
703+ if isinstance (other , LicenseChoice ):
704+ return ComparableTuple ((self .license , self .expression )) < ComparableTuple ((other .license , other .expression ))
705+ return NotImplemented
706+
636707 def __hash__ (self ) -> int :
637708 return hash ((self .license , self .expression ))
638709
@@ -690,6 +761,11 @@ def __eq__(self, other: object) -> bool:
690761 return hash (other ) == hash (self )
691762 return False
692763
764+ def __lt__ (self , other : Any ) -> bool :
765+ if isinstance (other , Property ):
766+ return ComparableTuple ((self .name , self .value )) < ComparableTuple ((other .name , other .value ))
767+ return NotImplemented
768+
693769 def __hash__ (self ) -> int :
694770 return hash ((self .name , self .value ))
695771
@@ -763,6 +839,12 @@ def __eq__(self, other: object) -> bool:
763839 return hash (other ) == hash (self )
764840 return False
765841
842+ def __lt__ (self , other : Any ) -> bool :
843+ if isinstance (other , NoteText ):
844+ return ComparableTuple ((self .content , self .content_type , self .encoding )) < \
845+ ComparableTuple ((other .content , other .content_type , other .encoding ))
846+ return NotImplemented
847+
766848 def __hash__ (self ) -> int :
767849 return hash ((self .content , self .content_type , self .encoding ))
768850
@@ -830,6 +912,11 @@ def __eq__(self, other: object) -> bool:
830912 return hash (other ) == hash (self )
831913 return False
832914
915+ def __lt__ (self , other : Any ) -> bool :
916+ if isinstance (other , Note ):
917+ return ComparableTuple ((self .locale , self .text )) < ComparableTuple ((other .locale , other .text ))
918+ return NotImplemented
919+
833920 def __hash__ (self ) -> int :
834921 return hash ((self .text , self .locale ))
835922
@@ -902,11 +989,17 @@ def __eq__(self, other: object) -> bool:
902989 return hash (other ) == hash (self )
903990 return False
904991
992+ def __lt__ (self , other : Any ) -> bool :
993+ if isinstance (other , OrganizationalContact ):
994+ return ComparableTuple ((self .name , self .email , self .phone )) < \
995+ ComparableTuple ((other .name , other .email , other .phone ))
996+ return NotImplemented
997+
905998 def __hash__ (self ) -> int :
906999 return hash ((self .name , self .phone , self .email ))
9071000
9081001 def __repr__ (self ) -> str :
909- return f'<OrganizationalContact name={ self .name } >'
1002+ return f'<OrganizationalContact name={ self .name } , email= { self . email } , phone= { self . phone } >'
9101003
9111004
9121005class OrganizationalEntity :
@@ -925,8 +1018,8 @@ def __init__(self, *, name: Optional[str] = None, urls: Optional[Iterable[XsUri]
9251018 'One of name, urls or contacts must be supplied for an OrganizationalEntity - none supplied.'
9261019 )
9271020 self .name = name
928- self .url = set (urls or [])
929- self .contact = set (contacts or [])
1021+ self .url = SortedSet (urls or [])
1022+ self .contact = SortedSet (contacts or [])
9301023
9311024 @property
9321025 def name (self ) -> Optional [str ]:
@@ -943,7 +1036,7 @@ def name(self, name: Optional[str]) -> None:
9431036 self ._name = name
9441037
9451038 @property
946- def url (self ) -> Set [XsUri ]:
1039+ def url (self ) -> "SortedSet [XsUri]" :
9471040 """
9481041 Get a list of URLs of the organization. Multiple URLs are allowed.
9491042
@@ -954,10 +1047,10 @@ def url(self) -> Set[XsUri]:
9541047
9551048 @url .setter
9561049 def url (self , urls : Iterable [XsUri ]) -> None :
957- self ._url = set (urls )
1050+ self ._url = SortedSet (urls )
9581051
9591052 @property
960- def contact (self ) -> Set [OrganizationalContact ]:
1053+ def contact (self ) -> "SortedSet [OrganizationalContact]" :
9611054 """
9621055 Get a list of contact person at the organization. Multiple contacts are allowed.
9631056
@@ -968,7 +1061,7 @@ def contact(self) -> Set[OrganizationalContact]:
9681061
9691062 @contact .setter
9701063 def contact (self , contacts : Iterable [OrganizationalContact ]) -> None :
971- self ._contact = set (contacts )
1064+ self ._contact = SortedSet (contacts )
9721065
9731066 def __eq__ (self , other : object ) -> bool :
9741067 if isinstance (other , OrganizationalEntity ):
@@ -998,8 +1091,8 @@ def __init__(self, *, vendor: Optional[str] = None, name: Optional[str] = None,
9981091 self .vendor = vendor
9991092 self .name = name
10001093 self .version = version
1001- self .hashes = set (hashes or [])
1002- self .external_references = set (external_references or [])
1094+ self .hashes = SortedSet (hashes or [])
1095+ self .external_references = SortedSet (external_references or [])
10031096
10041097 @property
10051098 def vendor (self ) -> Optional [str ]:
@@ -1044,7 +1137,7 @@ def version(self, version: Optional[str]) -> None:
10441137 self ._version = version
10451138
10461139 @property
1047- def hashes (self ) -> Set [HashType ]:
1140+ def hashes (self ) -> "SortedSet [HashType]" :
10481141 """
10491142 The hashes of the tool (if applicable).
10501143
@@ -1055,10 +1148,10 @@ def hashes(self) -> Set[HashType]:
10551148
10561149 @hashes .setter
10571150 def hashes (self , hashes : Iterable [HashType ]) -> None :
1058- self ._hashes = set (hashes )
1151+ self ._hashes = SortedSet (hashes )
10591152
10601153 @property
1061- def external_references (self ) -> Set [ExternalReference ]:
1154+ def external_references (self ) -> "SortedSet [ExternalReference]" :
10621155 """
10631156 External References provide a way to document systems, sites, and information that may be relevant but which
10641157 are not included with the BOM.
@@ -1070,13 +1163,19 @@ def external_references(self) -> Set[ExternalReference]:
10701163
10711164 @external_references .setter
10721165 def external_references (self , external_references : Iterable [ExternalReference ]) -> None :
1073- self ._external_references = set (external_references )
1166+ self ._external_references = SortedSet (external_references )
10741167
10751168 def __eq__ (self , other : object ) -> bool :
10761169 if isinstance (other , Tool ):
10771170 return hash (other ) == hash (self )
10781171 return False
10791172
1173+ def __lt__ (self , other : Any ) -> bool :
1174+ if isinstance (other , Tool ):
1175+ return ComparableTuple ((self .vendor , self .name , self .version )) < \
1176+ ComparableTuple ((other .vendor , other .name , other .version ))
1177+ return NotImplemented
1178+
10801179 def __hash__ (self ) -> int :
10811180 return hash ((self .vendor , self .name , self .version , tuple (self .hashes ), tuple (self .external_references )))
10821181
@@ -1150,6 +1249,12 @@ def __eq__(self, other: object) -> bool:
11501249 return hash (other ) == hash (self )
11511250 return False
11521251
1252+ def __lt__ (self , other : Any ) -> bool :
1253+ if isinstance (other , IdentifiableAction ):
1254+ return ComparableTuple ((self .timestamp , self .name , self .email )) < \
1255+ ComparableTuple ((other .timestamp , other .name , other .email ))
1256+ return NotImplemented
1257+
11531258 def __hash__ (self ) -> int :
11541259 return hash ((self .timestamp , self .name , self .email ))
11551260
@@ -1187,6 +1292,11 @@ def __eq__(self, other: object) -> bool:
11871292 return hash (other ) == hash (self )
11881293 return False
11891294
1295+ def __lt__ (self , other : Any ) -> bool :
1296+ if isinstance (other , Copyright ):
1297+ return self .text < other .text
1298+ return NotImplemented
1299+
11901300 def __hash__ (self ) -> int :
11911301 return hash (self .text )
11921302
0 commit comments