88
99import re
1010import os
11+ import json
12+ import packageurl
1113
1214"""Preview of High-Level, object oriented Python interface to the SW360 REST API.
1315For now, this does NOT strive to be stable or complete. Feel free to use it as
@@ -84,18 +86,49 @@ def _parse_link(self, key, links_key, links_value):
8486 self .details .setdefault (key , {})
8587 self .details [key ][links_key ] = links_value
8688
89+ def _parse_purls (self , purl_value ):
90+ """Parse package url strings"""
91+ purls = []
92+ if type (purl_value ) is str :
93+ if purl_value .startswith ("[" ):
94+ # as of 2022-04, SW360 returns arrays as JSON string...
95+ purl_value = json .loads (purl_value )
96+ else :
97+ purl_value = purl_value .split ()
98+
99+ for purl_string in purl_value :
100+ if purl_string .startswith ("pkg:" ):
101+ try :
102+ purl = packageurl .PackageURL .from_string (purl_string )
103+ purls .append (purl )
104+ except ValueError :
105+ pass
106+ return purls
107+
87108 _camel_case_pattern = re .compile (r'(?<!^)(?=[A-Z])' )
88109
89110 def from_json (self , json , copy_attributes = list (), snake_case = True ):
90111 """`copy_attributes` will be copied as-is between this instance's
91112 attributes and JSON members. If `snake_case` is set, more Python-ish
92113 snake_case names will be used (project_type instead of projectType).
93114 """
115+ # delete purl list as we add purls from different external ids below
116+ self .purls = []
94117 for key , value in json .items ():
95118 if key in copy_attributes :
96119 if snake_case :
97120 key = self ._camel_case_pattern .sub ('_' , key ).lower ()
98- self .__setattr__ (key , value )
121+ if key == "external_ids" :
122+ for id_type , id_value in value .items ():
123+ # detect purls independent from id_type - it should be
124+ # 'package-url', but some use "purl", "purl.id", etc.
125+ purls = self ._parse_purls (id_value )
126+ if len (purls ):
127+ self .purls += purls
128+ continue
129+ self .external_ids [id_type ] = id_value
130+ else :
131+ self .__setattr__ (key , value )
99132 elif key in ("_links" , "_embedded" ):
100133 for links_key , links_value in value .items ():
101134 self ._parse_link (key , links_key , links_value )
@@ -145,6 +178,8 @@ class Release(SW360Resource):
145178 def __init__ (self , json = None , release_id = None , component_id = None ,
146179 name = None , version = None , downloadurl = None , sw360 = None , ** kwargs ):
147180 self .attachments = {}
181+ self .external_ids = {}
182+ self .purls = []
148183
149184 self .name = name
150185 self .version = version
@@ -157,14 +192,17 @@ def from_json(self, json):
157192 belongs to will be extracted and stored in the `component_id`
158193 attribute.
159194
195+ SW360 external ids will be stored in the `external_ids` attribute.
196+ If valid package URLs (https://github.com/package-url/purl-spec) are found
197+ in the external ids, they will be stored in the `purls` attribute as
198+ packageurl.PackageURL instances.
199+
160200 All details not directly supported by this class will be stored as-is
161- in the `details` instance attribute. For now, this also includes
162- external ids which will be stored as-is in `details['externalIds'].
163- Please note that this might change in future if better abstractions
164- will be added in this Python library."""
201+ in the `details` instance attribute. Please note that this might
202+ change in future if more abstractions will be added here."""
165203 super ().from_json (
166204 json ,
167- copy_attributes = ("name" , "version" , "downloadurl" ))
205+ copy_attributes = ("name" , "version" , "downloadurl" , "externalIds" ))
168206
169207 def get (self , sw360 = None , id_ = None ):
170208 """Retrieve/update release from SW360."""
@@ -232,6 +270,11 @@ def from_json(self, json):
232270 support parsing the resource the attachment belongs to, so this needs
233271 to be set via constructur.
234272
273+ SW360 external ids will be stored in the `external_ids` attribute.
274+ If valid package URLs (https://github.com/package-url/purl-spec) are found
275+ in the external ids, they will be stored in the `purls` attribute as
276+ packageurl.PackageURL instances.
277+
235278 All details not directly supported by this class will be stored as-is
236279 in the `details` instance attribute.
237280 Please note that this might change in future if more abstractions
@@ -312,11 +355,14 @@ def __init__(self, json=None, component_id=None, name=None, description=None,
312355 homepage = None , component_type = None , sw360 = None , ** kwargs ):
313356 self .releases = {}
314357 self .attachments = {}
358+ self .external_ids = {}
359+ self .purls = []
315360
316361 self .name = name
317362 self .description = description
318363 self .homepage = homepage
319364 self .component_type = component_type
365+
320366 super ().__init__ (json , component_id , sw360 , ** kwargs )
321367
322368 def from_json (self , json ):
@@ -325,16 +371,20 @@ def from_json(self, json):
325371 and stored in the `releases` instance attribue. Please note that
326372 the REST API will only provide basic information for the releases.
327373
374+ SW360 external ids will be stored in the `external_ids` attribute.
375+ If valid package URLs (https://github.com/package-url/purl-spec) are found
376+ in the external ids, they will be stored in the `purls` attribute as
377+ packageurl.PackageURL instances.
378+
328379 All details not directly supported by this class will be
329380 stored as-is in the `details` instance attribute. For now, this also
330- includes vendor information and external ids which will be stored
331- as-is in `details['_embedded']['sw360:vendors']` and
332- `details['externalIds']. Please note that this might change in future
333- if better abstractions will be added in this Python library."""
381+ includes vendor information which will be stored as-is in
382+ `details['_embedded']['sw360:vendors']`. Please note that this might
383+ change in future if more abstractions will be added here."""
334384 super ().from_json (
335385 json ,
336386 copy_attributes = ("name" , "description" , "homepage" ,
337- "componentType" ))
387+ "componentType" , "externalIds" ))
338388
339389 def get (self , sw360 = None , id_ = None ):
340390 """Retrieve/update component from SW360."""
@@ -401,15 +451,19 @@ def from_json(self, json):
401451 and stored in the `releases` instance attribue. Please note that
402452 the REST API will only provide basic information for the releases.
403453
454+ SW360 external ids will be stored in the `external_ids` attribute.
455+ If valid package URLs (https://github.com/package-url/purl-spec) are found
456+ in the external ids, they will be stored in the `purls` attribute as
457+ packageurl.PackageURL instances.
458+
404459 All details not directly supported by this class will be
405460 stored as-is in the `details` instance attribute. For now, this also
406- includes linked projects and external ids. Please note that this might
407- change in future if better abstractions will be added in this Python
408- library."""
461+ includes linked projects. Please note that this might change in future
462+ if better abstractions will be added here."""
409463 super ().from_json (
410464 json ,
411465 copy_attributes = ("name" , "description" , "version" , "visibility" ,
412- "projectType" ))
466+ "projectType" , "externalIds" ))
413467
414468 def get (self , sw360 = None , id_ = None ):
415469 """Retrieve/update project from SW360."""
0 commit comments