16
16
#
17
17
# SPDX-License-Identifier: Apache-2.0
18
18
# Copyright (c) OWASP Foundation. All Rights Reserved.
19
-
20
19
from datetime import datetime , timezone
21
- from typing import cast , List , Optional
20
+ from typing import Iterable , Optional , Set
22
21
from uuid import uuid4 , UUID
23
22
24
23
from . import ExternalReference , ThisTool , Tool
@@ -35,39 +34,28 @@ class BomMetaData:
35
34
See the CycloneDX Schema for Bom metadata: https://cyclonedx.org/docs/1.3/#type_metadata
36
35
"""
37
36
38
- def __init__ (self , * , tools : Optional [List [Tool ]] = None ) -> None :
37
+ def __init__ (self , * , tools : Optional [Iterable [Tool ]] = None ) -> None :
39
38
self .timestamp = datetime .now (tz = timezone .utc )
40
- self .tools = tools if tools else []
39
+ self .tools = set ( tools or [])
41
40
42
41
if not self .tools :
43
- self .add_tool (ThisTool )
42
+ self .tools . add (ThisTool )
44
43
45
44
self .component : Optional [Component ] = None
46
45
47
46
@property
48
- def tools (self ) -> List [Tool ]:
47
+ def tools (self ) -> Set [Tool ]:
49
48
"""
50
49
Tools used to create this BOM.
51
50
52
51
Returns:
53
- `List ` of `Tool` objects where there are any, else an empty `List` .
52
+ `Set ` of `Tool` objects.
54
53
"""
55
54
return self ._tools
56
55
57
56
@tools .setter
58
- def tools (self , tools : List [Tool ]) -> None :
59
- self ._tools = tools
60
-
61
- def add_tool (self , tool : Tool ) -> None :
62
- """
63
- Add a Tool definition to this Bom Metadata. The `cyclonedx-python-lib` is automatically added - you do not need
64
- to add this yourself.
65
-
66
- Args:
67
- tool:
68
- Instance of `Tool` that represents the tool you are using.
69
- """
70
- self ._tools .append (tool )
57
+ def tools (self , tools : Iterable [Tool ]) -> None :
58
+ self ._tools = set (tools )
71
59
72
60
@property
73
61
def timestamp (self ) -> datetime :
@@ -114,9 +102,7 @@ def __eq__(self, other: object) -> bool:
114
102
115
103
def __hash__ (self ) -> int :
116
104
return hash ((
117
- self .timestamp ,
118
- tuple ([hash (tool ) for tool in set (sorted (self .tools , key = hash ))]) if self .tools else None ,
119
- self .component
105
+ self .timestamp , self .tools , self .component
120
106
))
121
107
122
108
def __repr__ (self ) -> str :
@@ -146,11 +132,12 @@ def from_parser(parser: BaseParser) -> 'Bom':
146
132
`cyclonedx.model.bom.Bom`: A Bom instance that represents the valid data held in the supplied parser.
147
133
"""
148
134
bom = Bom ()
149
- bom .add_components (parser .get_components ())
135
+ bom .components . update (parser .get_components ())
150
136
return bom
151
137
152
- def __init__ (self , * , components : Optional [List [Component ]] = None , services : Optional [List [Service ]] = None ,
153
- external_references : Optional [List [ExternalReference ]] = None ) -> None :
138
+ def __init__ (self , * , components : Optional [Iterable [Component ]] = None ,
139
+ services : Optional [Iterable [Service ]] = None ,
140
+ external_references : Optional [Iterable [ExternalReference ]] = None ) -> None :
154
141
"""
155
142
Create a new Bom that you can manually/programmatically add data to later.
156
143
@@ -159,9 +146,9 @@ def __init__(self, *, components: Optional[List[Component]] = None, services: Op
159
146
"""
160
147
self .uuid = uuid4 ()
161
148
self .metadata = BomMetaData ()
162
- self .components = components
163
- self .services = services
164
- self .external_references = external_references
149
+ self .components = set ( components or [])
150
+ self .services = set ( services or [])
151
+ self .external_references = set ( external_references or [])
165
152
166
153
@property
167
154
def uuid (self ) -> UUID :
@@ -195,60 +182,22 @@ def metadata(self, metadata: BomMetaData) -> None:
195
182
self ._metadata = metadata
196
183
197
184
@property
198
- def components (self ) -> Optional [ List [ Component ] ]:
185
+ def components (self ) -> Set [ Component ]:
199
186
"""
200
187
Get all the Components currently in this Bom.
201
188
202
189
Returns:
203
- List of all Components in this Bom or `None`
190
+ Set of `Component` in this Bom
204
191
"""
205
192
return self ._components
206
193
207
194
@components .setter
208
- def components (self , components : Optional [List [Component ]]) -> None :
209
- self ._components = components
210
-
211
- def add_component (self , component : Component ) -> None :
212
- """
213
- Add a Component to this Bom instance.
214
-
215
- Args:
216
- component:
217
- `cyclonedx.model.component.Component` instance to add to this Bom.
218
-
219
- Returns:
220
- None
221
- """
222
- if not self .components :
223
- self .components = [component ]
224
- elif not self .has_component (component = component ):
225
- self .components .append (component )
226
-
227
- def add_components (self , components : List [Component ]) -> None :
228
- """
229
- Add multiple Components at once to this Bom instance.
230
-
231
- Args:
232
- components:
233
- List of `cyclonedx.model.component.Component` instances to add to this Bom.
234
-
235
- Returns:
236
- None
237
- """
238
- self .components = (self ._components or []) + components
239
-
240
- def component_count (self ) -> int :
241
- """
242
- Returns the current count of Components within this Bom.
243
-
244
- Returns:
245
- The number of Components in this Bom as `int`.
246
- """
247
- return len (self ._components ) if self ._components else 0
195
+ def components (self , components : Iterable [Component ]) -> None :
196
+ self ._components = set (components )
248
197
249
198
def get_component_by_purl (self , purl : Optional [str ]) -> Optional [Component ]:
250
199
"""
251
- Get a Component already in the Bom by it's PURL
200
+ Get a Component already in the Bom by its PURL
252
201
253
202
Args:
254
203
purl:
@@ -257,11 +206,8 @@ def get_component_by_purl(self, purl: Optional[str]) -> Optional[Component]:
257
206
Returns:
258
207
`Component` or `None`
259
208
"""
260
- if not self ._components :
261
- return None
262
-
263
209
if purl :
264
- found = list (filter (lambda x : x .purl == purl , cast ( List [ Component ], self .components ) ))
210
+ found = list (filter (lambda x : x .purl == purl , self .components ))
265
211
if len (found ) == 1 :
266
212
return found [0 ]
267
213
@@ -287,107 +233,35 @@ def has_component(self, component: Component) -> bool:
287
233
Returns:
288
234
`bool` - `True` if the supplied Component is part of this Bom, `False` otherwise.
289
235
"""
290
- if not self .components :
291
- return False
292
236
return component in self .components
293
237
294
238
@property
295
- def services (self ) -> Optional [ List [ Service ] ]:
239
+ def services (self ) -> Set [ Service ]:
296
240
"""
297
241
Get all the Services currently in this Bom.
298
242
299
243
Returns:
300
- List of `Service` in this Bom or `None`
244
+ Set of `Service` in this BOM
301
245
"""
302
246
return self ._services
303
247
304
248
@services .setter
305
- def services (self , services : Optional [List [Service ]]) -> None :
306
- self ._services = services
307
-
308
- def add_service (self , service : Service ) -> None :
309
- """
310
- Add a Service to this Bom instance.
311
-
312
- Args:
313
- service:
314
- `cyclonedx.model.service.Service` instance to add to this Bom.
315
-
316
- Returns:
317
- None
318
- """
319
- if not self .services :
320
- self .services = [service ]
321
- elif not self .has_service (service = service ):
322
- self .services .append (service )
323
-
324
- def add_services (self , services : List [Service ]) -> None :
325
- """
326
- Add multiple Services at once to this Bom instance.
327
-
328
- Args:
329
- services:
330
- List of `cyclonedx.model.service.Service` instances to add to this Bom.
331
-
332
- Returns:
333
- None
334
- """
335
- self .services = (self .services or []) + services
336
-
337
- def has_service (self , service : Service ) -> bool :
338
- """
339
- Check whether this Bom contains the provided Service.
340
-
341
- Args:
342
- service:
343
- The instance of `cyclonedx.model.service.Service` to check if this Bom contains.
344
-
345
- Returns:
346
- `bool` - `True` if the supplied Service is part of this Bom, `False` otherwise.
347
- """
348
- if not self .services :
349
- return False
350
-
351
- return service in self .services
352
-
353
- def service_count (self ) -> int :
354
- """
355
- Returns the current count of Services within this Bom.
356
-
357
- Returns:
358
- The number of Services in this Bom as `int`.
359
- """
360
- if not self .services :
361
- return 0
362
-
363
- return len (self .services )
249
+ def services (self , services : Iterable [Service ]) -> None :
250
+ self ._services = set (services )
364
251
365
252
@property
366
- def external_references (self ) -> Optional [ List [ ExternalReference ] ]:
253
+ def external_references (self ) -> Set [ ExternalReference ]:
367
254
"""
368
255
Provides the ability to document external references related to the BOM or to the project the BOM describes.
369
256
370
257
Returns:
371
- List of `ExternalReference` else `None `
258
+ Set of `ExternalReference`
372
259
"""
373
260
return self ._external_references
374
261
375
262
@external_references .setter
376
- def external_references (self , external_references : Optional [List [ExternalReference ]]) -> None :
377
- self ._external_references = external_references
378
-
379
- def add_external_reference (self , external_reference : ExternalReference ) -> None :
380
- """
381
- Add an external reference to this Bom.
382
-
383
- Args:
384
- external_reference:
385
- `ExternalReference` to add to this Bom.
386
-
387
- Returns:
388
- None
389
- """
390
- self .external_references = (self .external_references or []) + [external_reference ]
263
+ def external_references (self , external_references : Iterable [ExternalReference ]) -> None :
264
+ self ._external_references = set (external_references )
391
265
392
266
def has_vulnerabilities (self ) -> bool :
393
267
"""
@@ -397,12 +271,7 @@ def has_vulnerabilities(self) -> bool:
397
271
`bool` - `True` if at least one `cyclonedx.model.component.Component` has at least one Vulnerability,
398
272
`False` otherwise.
399
273
"""
400
- if self .components :
401
- for c in self .components :
402
- if c .has_vulnerabilities ():
403
- return True
404
-
405
- return False
274
+ return any (c .has_vulnerabilities () for c in self .components )
406
275
407
276
def __eq__ (self , other : object ) -> bool :
408
277
if isinstance (other , Bom ):
@@ -411,9 +280,7 @@ def __eq__(self, other: object) -> bool:
411
280
412
281
def __hash__ (self ) -> int :
413
282
return hash ((
414
- self .uuid , self .metadata ,
415
- tuple ([hash (c ) for c in set (sorted (self .components , key = hash ))]) if self .components else None ,
416
- tuple ([hash (s ) for s in set (sorted (self .services , key = hash ))]) if self .services else None
283
+ self .uuid , self .metadata , tuple (self .components ), tuple (self .services ), tuple (self .external_references )
417
284
))
418
285
419
286
def __repr__ (self ) -> str :
0 commit comments