@@ -23,18 +23,50 @@ class SW360Resource:
2323 release etc.
2424 """
2525
26- def __init__ (self , json = None , resource_id = None , sw360 = None , ** kwargs ):
27- self .details = {}
26+ all_resources = {}
27+
28+ def __new__ (cls , json = None , resource_id = None , ** kwargs ):
29+ """Check if this resource already exists."""
30+ key = None
31+ if resource_id :
32+ key = (cls .__name__ , resource_id )
33+ elif json and "id" in json :
34+ key = (cls .__name__ , json ["id" ])
35+
36+ if key :
37+ if key not in cls .all_releases :
38+ cls .all_resources [key ] = super (SW360Resource , cls ).__new__ (cls )
39+ return cls .all_resources [key ]
40+ else :
41+ return super (SW360Resource , cls ).__new__ (cls )
42+
43+ def __init__ (self , json = None , resource_id = None , parent = None , users = {}, sw360 = None , ** kwargs ):
44+ if not hasattr (self , "details" ):
45+ self .details = {}
2846 """All resource details which are not explicitely supported by the
2947 constructor parameters of the derived objexts are stored in the
3048 `details` attribute. Shall use names and types as specified in SW360
3149 REST API."""
3250
51+ if not hasattr (self , "users" ):
52+ self .users = {}
53+
54+ if resource_id and getattr (self , "id" , resource_id ) != resource_id :
55+ raise ValueError ("Resource id mismatch" , self .id , resource_id )
3356 self .id = resource_id
3457 """All SW360 resource instances have an `id`. If it is set to `None`,
3558 the object is yet unknown to SW360 - otherwise, it stores the SW360
3659 id (release_id, component_id, etc.)."""
3760
61+ if parent and hasattr (self , "parent" ):
62+ if self .parent .id != parent .id :
63+ raise ValueError ("Resource parent mismatch" , self .parent .id , parent .id )
64+ self .parent = parent
65+
66+ for k , v in users .items ():
67+ # merge new users with existing ones
68+ self .users [k ] = v
69+
3870 self .sw360 = sw360
3971 """SW360 api object"""
4072
@@ -44,43 +76,66 @@ def __init__(self, json=None, resource_id=None, sw360=None, **kwargs):
4476 if json is not None :
4577 self .from_json (json )
4678
47- def _parse_release_list (self , json_list , component_id = None ):
79+ def __setattr__ (self , name , value ):
80+ # assure that the resource is registered in the global resource list
81+ # even if the id is set later
82+ super ().__setattr__ (name , value )
83+ if name == "id" and value is not None :
84+ key = (self .__class__ .__name__ , value )
85+ self .all_resources [key ] = self
86+
87+ def _parse_release_list (self , json_list , parent = None , users = {}):
4888 """Parse a JSON list of releases, create according objects and add
4989 them to `container`."""
5090 releases = {}
5191 for release_json in json_list :
52- release = Release (component_id = component_id , sw360 = self .sw360 )
92+ release = Release (parent = parent , users = users , sw360 = self .sw360 )
5393 release .from_json (release_json )
5494 releases [release .id ] = release
5595 return releases
5696
57- def _parse_attachment_list (self , json_list , resources = [] ):
97+ def _parse_attachment_list (self , json_list , parent = None ):
5898 """Parse a JSON list of releases, create according objects and add
5999 them to `container`."""
60100 attachments = {}
61101 for attachment_json in json_list :
62- attachment = Attachment (resources = resources , sw360 = self .sw360 )
102+ attachment = Attachment (parent = parent , sw360 = self .sw360 )
63103 attachment .from_json (attachment_json )
64104 attachments [attachment .id ] = attachment
65105 return attachments
66106
107+ def _parse_project_list (self , json_list , users = {}):
108+ """Parse a JSON list of projects, create according objects and add
109+ them to `container`."""
110+ projects = {}
111+ for project_json in json_list :
112+ project = Project (users = users , sw360 = self .sw360 )
113+ project .from_json (project_json )
114+ projects [project .id ] = project
115+ return projects
116+
67117 def _parse_link (self , key , links_key , links_value ):
68118 """Parse a _links or _embedded section in JSON"""
69119 if links_key == "sw360:component" :
70- self .component_id = links_value ["href" ].split ("/" )[- 1 ]
120+ self .parent = Component ( component_id = links_value ["href" ].split ("/" )[- 1 ], sw360 = self . sw360 )
71121 elif links_key == "sw360:downloadLink" :
72122 self .download_link = links_value ["href" ]
73123 elif links_key == "sw360:attachments" :
74124 self .attachments = self ._parse_attachment_list (
75125 links_value ,
76- resources = [ self ] )
126+ parent = self )
77127 elif links_key == "sw360:releases" :
78- component_id = None
79128 if isinstance (self , Component ):
80- component_id = self .id
129+ parent = self
130+ users = {}
131+ else :
132+ parent = None
133+ users = {self .id : self }
81134 self .releases = self ._parse_release_list (
82- links_value ,
83- component_id = component_id )
135+ links_value , parent = parent , users = users )
136+ elif links_key == "sw360:projects" :
137+ self .projects = self ._parse_project_list (
138+ links_value , {self .id : self })
84139 elif links_key == "self" :
85140 self .id = links_value ["href" ].split ("/" )[- 1 ]
86141 else :
@@ -162,35 +217,38 @@ class Release(SW360Resource):
162217 For JSON parsing, please read documentation of from_json() method.
163218
164219 :param json: create release from SW360 JSON object by calling from_json()
165- :param component_id: SW360 id of the component the release belongs to
220+ :param parent: SW360 component the release belongs to
221+ :param users: dictionary of SW360 resources which link to the release
222+ (instances of Release() or Project() with id as key)
166223 :param version: the actual version
167224 :param downloadurl: URL the release was downloaded from
168225 :param release_id: id of the release (if exists in SW360 already)
169226 :param sw360: your SW360 instance for interacting with the API
170227 :param kwargs: additional relase details as specified in the SW360 REST API
171228 :type json: SW360 JSON object
172- :type component_id: string
229+ :type parent: Component() object
230+ :type users: dictionary
173231 :type version: string
174232 :type downloadurl: string
175233 :type release_id: string
176234 :type sw360: instance from SW360 class
177235 :type kwargs: dictionary
178236 """
179- def __init__ (self , json = None , release_id = None , component_id = None ,
237+ def __init__ (self , json = None , release_id = None , parent = None , users = {} ,
180238 name = None , version = None , downloadurl = None , sw360 = None , ** kwargs ):
181239 self .attachments = {}
182240 self .external_ids = {}
183241 self .purls = []
184242
185243 self .name = name
186244 self .version = version
187- self .component_id = component_id
188245 self .downloadurl = downloadurl
189- super ().__init__ (json , release_id , sw360 = sw360 , ** kwargs )
246+ super ().__init__ (json = json , resource_id = release_id , parent = parent , users = users ,
247+ sw360 = sw360 , ** kwargs )
190248
191249 def from_json (self , json ):
192250 """Parse release JSON object from SW360 REST API. The component it
193- belongs to will be extracted and stored in the `component_id `
251+ belongs to will be extracted and stored in the `parent `
194252 attribute.
195253
196254 SW360 external ids will be stored in the `external_ids` attribute.
@@ -214,12 +272,6 @@ def get(self, sw360=None, id_=None):
214272 self .from_json (self .sw360 .get_release (self .id ))
215273 return self
216274
217- def get_component (self , sw360 = None ):
218- """Retrieve/update component of this release."""
219- if sw360 :
220- self .sw360 = sw360
221- return Component ().get (self .sw360 , self .component_id )
222-
223275 def __str__ (self ):
224276 return f'{ self .name } { self .version } ({ self .id } )'
225277
@@ -239,8 +291,7 @@ class Attachment(SW360Resource):
239291
240292 :param json: create it from SW360 JSON object by calling from_json()
241293 :param attachment_id: SW360 id of the attachment (if it exists already)
242- :param resources: dictionary of SW360 resource objects the attachment belongs to
243- (instances of Release(), Component() or Project() with id as key)
294+ :param parent: SW360 resource (release, component or project) the attachment belongs to
244295 :param filename: the filename of the attachment
245296 :param sha1: SHA1 sum of the file to check its integrity
246297 :param attachment_type: one of "DOCUMENT", "SOURCE", "SOURCE_SELF"
@@ -257,14 +308,14 @@ class Attachment(SW360Resource):
257308 :type sw360: instance from SW360 class
258309 :type kwargs: dictionary
259310 """
260- def __init__ (self , json = None , attachment_id = None , resources = {} ,
311+ def __init__ (self , json = None , attachment_id = None , parent = None ,
261312 filename = None , sha1 = None , attachment_type = None , sw360 = None , ** kwargs ):
262- self .resources = resources
263313 self .attachment_type = attachment_type
264314 self .filename = filename
265315 self .sha1 = sha1
266316 self .download_link = None
267- super ().__init__ (json , attachment_id , sw360 , ** kwargs )
317+ super ().__init__ (json = json , resource_id = attachment_id , parent = parent ,
318+ sw360 = sw360 , ** kwargs )
268319
269320 def from_json (self , json ):
270321 """Parse attachment JSON object from SW360 REST API. For now, we don't
@@ -296,16 +347,6 @@ def get(self, sw360=None, id_=None):
296347 self .from_json (self .sw360 .get_attachment (self .id ))
297348 return self
298349
299- def get_releases (self , sw360 = None ):
300- """Retrieve/update releases of this attachment."""
301- if sw360 :
302- self .sw360 = sw360
303- releases = [
304- Release ().get (self .sw360 , id_ )
305- for id_ in self .releases
306- ]
307- return releases
308-
309350 def download (self , target_path , filename = None ):
310351 """download an attachment to local file.
311352
@@ -364,7 +405,7 @@ def __init__(self, json=None, component_id=None, name=None, description=None,
364405 self .homepage = homepage
365406 self .component_type = component_type
366407
367- super ().__init__ (json , component_id , sw360 , ** kwargs )
408+ super ().__init__ (json = json , resource_id = component_id , sw360 = sw360 , ** kwargs )
368409
369410 def from_json (self , json ):
370411 """Parse component JSON object from SW360 REST API. Information for
@@ -401,7 +442,7 @@ def __str__(self):
401442
402443
403444class Project (SW360Resource ):
404- """A project is SW360 abstraction for a collection of software components
445+ """A project is SW360 abstraction for a collection of software releases
405446 used in a project/product. It can contain links to other `Project`s or
406447 `Release`s.
407448
@@ -414,6 +455,8 @@ class Project(SW360Resource):
414455
415456 :param json: create component from SW360 JSON object by calling from_json()
416457 :param project_id: id of the project (if exists in SW360 already)
458+ :param users: dictionary of SW360 resources which link to the project
459+ (instances of Project() with id as key)
417460 :param name: name of the project
418461 :param version: version of the project
419462 :param description: short description for project
@@ -434,8 +477,8 @@ class Project(SW360Resource):
434477 :type sw360: instance from SW360 class
435478 :type kwargs: dictionary
436479 """
437- def __init__ (self , json = None , project_id = None , name = None , version = None ,
438- description = None , visibility = None , project_type = None ,
480+ def __init__ (self , json = None , project_id = None , users = {} ,
481+ name = None , version = None , description = None , visibility = None , project_type = None ,
439482 sw360 = None , ** kwargs ):
440483 self .releases = {}
441484 self .external_ids = {}
@@ -446,7 +489,8 @@ def __init__(self, json=None, project_id=None, name=None, version=None,
446489 self .description = description
447490 self .visibility = visibility
448491 self .project_type = project_type
449- super ().__init__ (json , project_id , sw360 , ** kwargs )
492+ super ().__init__ (json = json , resource_id = project_id , users = users ,
493+ sw360 = sw360 , ** kwargs )
450494
451495 def from_json (self , json ):
452496 """Parse project JSON object from SW360 REST API. Information for
0 commit comments