99
1010import re
1111import os
12+ import json
13+ import packageurl
1214
1315"""Preview of High-Level, object oriented Python interface to the SW360 REST API.
1416For now, this does NOT strive to be stable or complete. Feel free to use it as
@@ -85,18 +87,49 @@ def _parse_link(self, key, links_key, links_value):
8587 self .details .setdefault (key , {})
8688 self .details [key ][links_key ] = links_value
8789
90+ def _parse_purls (self , purl_value ):
91+ """Parse package url strings"""
92+ purls = []
93+ if type (purl_value ) is str :
94+ if purl_value .startswith ("[" ):
95+ # as of 2022-04, SW360 returns arrays as JSON string...
96+ purl_value = json .loads (purl_value )
97+ else :
98+ purl_value = purl_value .split ()
99+
100+ for purl_string in purl_value :
101+ if purl_string .startswith ("pkg:" ):
102+ try :
103+ purl = packageurl .PackageURL .from_string (purl_string )
104+ purls .append (purl )
105+ except ValueError :
106+ pass
107+ return purls
108+
88109 _camel_case_pattern = re .compile (r'(?<!^)(?=[A-Z])' )
89110
90111 def from_json (self , json , copy_attributes = list (), snake_case = True ):
91112 """`copy_attributes` will be copied as-is between this instance's
92113 attributes and JSON members. If `snake_case` is set, more Python-ish
93114 snake_case names will be used (project_type instead of projectType).
94115 """
116+ # delete purl list as we add purls from different external ids below
117+ self .purls = []
95118 for key , value in json .items ():
96119 if key in copy_attributes :
97120 if snake_case :
98121 key = self ._camel_case_pattern .sub ('_' , key ).lower ()
99- self .__setattr__ (key , value )
122+ if key == "external_ids" :
123+ for id_type , id_value in value .items ():
124+ # detect purls independent from id_type - it should be
125+ # 'package-url', but some use "purl", "purl.id", etc.
126+ purls = self ._parse_purls (id_value )
127+ if len (purls ):
128+ self .purls += purls
129+ continue
130+ self .external_ids [id_type ] = id_value
131+ else :
132+ self .__setattr__ (key , value )
100133 elif key in ("_links" , "_embedded" ):
101134 for links_key , links_value in value .items ():
102135 self ._parse_link (key , links_key , links_value )
@@ -146,6 +179,8 @@ class Release(SW360Resource):
146179 def __init__ (self , json = None , release_id = None , component_id = None ,
147180 name = None , version = None , downloadurl = None , sw360 = None , ** kwargs ):
148181 self .attachments = {}
182+ self .external_ids = {}
183+ self .purls = []
149184
150185 self .name = name
151186 self .version = version
@@ -158,14 +193,17 @@ def from_json(self, json):
158193 belongs to will be extracted and stored in the `component_id`
159194 attribute.
160195
196+ SW360 external ids will be stored in the `external_ids` attribute.
197+ If valid package URLs (https://github.com/package-url/purl-spec) are found
198+ in the external ids, they will be stored in the `purls` attribute as
199+ packageurl.PackageURL instances.
200+
161201 All details not directly supported by this class will be stored as-is
162- in the `details` instance attribute. For now, this also includes
163- external ids which will be stored as-is in `details['externalIds'].
164- Please note that this might change in future if better abstractions
165- will be added in this Python library."""
202+ in the `details` instance attribute. Please note that this might
203+ change in future if more abstractions will be added here."""
166204 super ().from_json (
167205 json ,
168- copy_attributes = ("name" , "version" , "downloadurl" ))
206+ copy_attributes = ("name" , "version" , "downloadurl" , "externalIds" ))
169207
170208 def get (self , sw360 = None , id_ = None ):
171209 """Retrieve/update release from SW360."""
@@ -233,6 +271,11 @@ def from_json(self, json):
233271 support parsing the resource the attachment belongs to, so this needs
234272 to be set via constructur.
235273
274+ SW360 external ids will be stored in the `external_ids` attribute.
275+ If valid package URLs (https://github.com/package-url/purl-spec) are found
276+ in the external ids, they will be stored in the `purls` attribute as
277+ packageurl.PackageURL instances.
278+
236279 All details not directly supported by this class will be stored as-is
237280 in the `details` instance attribute.
238281 Please note that this might change in future if more abstractions
@@ -313,11 +356,14 @@ def __init__(self, json=None, component_id=None, name=None, description=None,
313356 homepage = None , component_type = None , sw360 = None , ** kwargs ):
314357 self .releases = {}
315358 self .attachments = {}
359+ self .external_ids = {}
360+ self .purls = []
316361
317362 self .name = name
318363 self .description = description
319364 self .homepage = homepage
320365 self .component_type = component_type
366+
321367 super ().__init__ (json , component_id , sw360 , ** kwargs )
322368
323369 def from_json (self , json ):
@@ -326,16 +372,20 @@ def from_json(self, json):
326372 and stored in the `releases` instance attribue. Please note that
327373 the REST API will only provide basic information for the releases.
328374
375+ SW360 external ids will be stored in the `external_ids` attribute.
376+ If valid package URLs (https://github.com/package-url/purl-spec) are found
377+ in the external ids, they will be stored in the `purls` attribute as
378+ packageurl.PackageURL instances.
379+
329380 All details not directly supported by this class will be
330381 stored as-is in the `details` instance attribute. For now, this also
331- includes vendor information and external ids which will be stored
332- as-is in `details['_embedded']['sw360:vendors']` and
333- `details['externalIds']. Please note that this might change in future
334- if better abstractions will be added in this Python library."""
382+ includes vendor information which will be stored as-is in
383+ `details['_embedded']['sw360:vendors']`. Please note that this might
384+ change in future if more abstractions will be added here."""
335385 super ().from_json (
336386 json ,
337387 copy_attributes = ("name" , "description" , "homepage" ,
338- "componentType" ))
388+ "componentType" , "externalIds" ))
339389
340390 def get (self , sw360 = None , id_ = None ):
341391 """Retrieve/update component from SW360."""
@@ -388,6 +438,8 @@ def __init__(self, json=None, project_id=None, name=None, version=None,
388438 description = None , visibility = None , project_type = None ,
389439 sw360 = None , ** kwargs ):
390440 self .releases = {}
441+ self .external_ids = {}
442+ self .purls = []
391443
392444 self .name = name
393445 self .version = version
@@ -402,15 +454,19 @@ def from_json(self, json):
402454 and stored in the `releases` instance attribue. Please note that
403455 the REST API will only provide basic information for the releases.
404456
457+ SW360 external ids will be stored in the `external_ids` attribute.
458+ If valid package URLs (https://github.com/package-url/purl-spec) are found
459+ in the external ids, they will be stored in the `purls` attribute as
460+ packageurl.PackageURL instances.
461+
405462 All details not directly supported by this class will be
406463 stored as-is in the `details` instance attribute. For now, this also
407- includes linked projects and external ids. Please note that this might
408- change in future if better abstractions will be added in this Python
409- library."""
464+ includes linked projects. Please note that this might change in future
465+ if better abstractions will be added here."""
410466 super ().from_json (
411467 json ,
412468 copy_attributes = ("name" , "description" , "version" , "visibility" ,
413- "projectType" ))
469+ "projectType" , "externalIds" ))
414470
415471 def get (self , sw360 = None , id_ = None ):
416472 """Retrieve/update project from SW360."""
0 commit comments