Skip to content

Commit f576a8e

Browse files
committed
sw360_objects: clear interface for get and update, fix deduplication
1 parent 561764f commit f576a8e

File tree

4 files changed

+98
-92
lines changed

4 files changed

+98
-92
lines changed

sw360/sw360_objects.py

Lines changed: 80 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
import packageurl
1414

1515
"""Preview of High-Level, object oriented Python interface to the SW360 REST API.
16-
For now, this does NOT strive to be stable or complete. Feel free to use it as
17-
a more convenient abstraction for some (important) objects, but be prepared for
18-
changes."""
16+
For now, this only allows read access and does NOT strive to be stable or complete.
17+
Feel free to use it as a more convenient abstraction for some (important) objects,
18+
but be prepared for API changes."""
1919

2020

2121
class SW360Resource:
@@ -25,22 +25,22 @@ class SW360Resource:
2525

2626
all_resources = {}
2727

28-
def __new__(cls, json=None, resource_id=None, **kwargs):
28+
def __new__(cls, json=None, id_=None, **kwargs):
2929
"""Check if this resource already exists."""
3030
key = None
31-
if resource_id:
32-
key = (cls.__name__, resource_id)
31+
if id_:
32+
key = (cls.__name__, id_)
3333
elif json and "id" in json:
3434
key = (cls.__name__, json["id"])
3535

3636
if key:
37-
if key not in cls.all_releases:
37+
if key not in cls.all_resources:
3838
cls.all_resources[key] = super(SW360Resource, cls).__new__(cls)
3939
return cls.all_resources[key]
4040
else:
4141
return super(SW360Resource, cls).__new__(cls)
4242

43-
def __init__(self, json=None, resource_id=None, parent=None, users={}, sw360=None, **kwargs):
43+
def __init__(self, json=None, id_=None, parent=None, users={}, sw360=None, **kwargs):
4444
if not hasattr(self, "details"):
4545
self.details = {}
4646
"""All resource details which are not explicitely supported by the
@@ -51,9 +51,9 @@ def __init__(self, json=None, resource_id=None, parent=None, users={}, sw360=Non
5151
if not hasattr(self, "users"):
5252
self.users = {}
5353

54-
if resource_id and getattr(self, "id", resource_id) != resource_id:
55-
raise ValueError("Resource id mismatch", self.id, resource_id)
56-
self.id = resource_id
54+
if id_ and getattr(self, "id", id_) != id_:
55+
raise ValueError("Resource id mismatch", self.id, id_)
56+
self.id = id_
5757
"""All SW360 resource instances have an `id`. If it is set to `None`,
5858
the object is yet unknown to SW360 - otherwise, it stores the SW360
5959
id (release_id, component_id, etc.)."""
@@ -82,14 +82,16 @@ def __setattr__(self, name, value):
8282
super().__setattr__(name, value)
8383
if name == "id" and value is not None:
8484
key = (self.__class__.__name__, value)
85+
if key in self.all_resources and self.all_resources[key] != self:
86+
raise ValueError("Duplicate object detected for ", key)
8587
self.all_resources[key] = self
8688

8789
def _parse_release_list(self, json_list, parent=None, users={}):
8890
"""Parse a JSON list of releases, create according objects and add
8991
them to `container`."""
9092
releases = {}
9193
for release_json in json_list:
92-
release = Release(parent=parent, users=users, sw360=self.sw360)
94+
release = Release(id_=release_json["id"], parent=parent, users=users, sw360=self.sw360)
9395
release.from_json(release_json)
9496
releases[release.id] = release
9597
return releases
@@ -99,7 +101,9 @@ def _parse_attachment_list(self, json_list, parent=None):
99101
them to `container`."""
100102
attachments = {}
101103
for attachment_json in json_list:
102-
attachment = Attachment(parent=parent, sw360=self.sw360)
104+
# attachment id is not available here, so we need to extract it
105+
attachment_id = attachment_json["_links"]["self"]["href"].split("/")[-1]
106+
attachment = Attachment(id_=attachment_id, parent=parent, sw360=self.sw360)
103107
attachment.from_json(attachment_json)
104108
attachments[attachment.id] = attachment
105109
return attachments
@@ -109,15 +113,15 @@ def _parse_project_list(self, json_list, users={}):
109113
them to `container`."""
110114
projects = {}
111115
for project_json in json_list:
112-
project = Project(users=users, sw360=self.sw360)
116+
project = Project(id_=project_json["id"], users=users, sw360=self.sw360)
113117
project.from_json(project_json)
114118
projects[project.id] = project
115119
return projects
116120

117121
def _parse_link(self, key, links_key, links_value):
118122
"""Parse a _links or _embedded section in JSON"""
119123
if links_key == "sw360:component":
120-
self.parent = Component(component_id=links_value["href"].split("/")[-1], sw360=self.sw360)
124+
self.parent = Component(id_=links_value["href"].split("/")[-1], sw360=self.sw360)
121125
elif links_key == "sw360:downloadLink":
122126
self.download_link = links_value["href"]
123127
elif links_key == "sw360:attachments":
@@ -200,7 +204,7 @@ def __repr__(self):
200204
or k.endswith("_id")):
201205
repr_.append(f'{k}={v!r}')
202206
if k == "id":
203-
repr_.append(f'{self.__class__.__name__.lower()}_id={v!r}')
207+
repr_.append(f'id_={v!r}')
204208
return (f'{self.__class__.__name__}('
205209
+ ", ".join(repr_)
206210
+ ")")
@@ -209,10 +213,10 @@ def __repr__(self):
209213
class Release(SW360Resource):
210214
"""A release is the SW360 abstraction for a single version of a component.
211215
212-
You can either create it from a SW360 `json` object or by specifying the
213-
details via the constructor parameters, see list below. Only the most
214-
important attributes are supported, rest hast be provided via `kwargs` and
215-
is stored in the `details` attribute of instances.
216+
You can either create it by using `get`, from a SW360 `json` object or by
217+
creating a fresh instance. The list below describes supported attributes.
218+
Only the most important ones are supported, rest hast be provided via
219+
`kwargs` and is stored in the `details` attribute of instances.
216220
217221
For JSON parsing, please read documentation of from_json() method.
218222
@@ -222,19 +226,19 @@ class Release(SW360Resource):
222226
(instances of Release() or Project() with id as key)
223227
:param version: the actual version
224228
:param downloadurl: URL the release was downloaded from
225-
:param release_id: id of the release (if exists in SW360 already)
229+
:param id_: id of the release (if exists in SW360 already)
226230
:param sw360: your SW360 instance for interacting with the API
227231
:param kwargs: additional relase details as specified in the SW360 REST API
228232
:type json: SW360 JSON object
229233
:type parent: Component() object
230234
:type users: dictionary
231235
:type version: string
232236
:type downloadurl: string
233-
:type release_id: string
237+
:type id_: string
234238
:type sw360: instance from SW360 class
235239
:type kwargs: dictionary
236240
"""
237-
def __init__(self, json=None, release_id=None, parent=None, users={},
241+
def __init__(self, json=None, id_=None, parent=None, users={},
238242
name=None, version=None, downloadurl=None, sw360=None, **kwargs):
239243
self.attachments = {}
240244
self.external_ids = {}
@@ -243,7 +247,7 @@ def __init__(self, json=None, release_id=None, parent=None, users={},
243247
self.name = name
244248
self.version = version
245249
self.downloadurl = downloadurl
246-
super().__init__(json=json, resource_id=release_id, parent=parent, users=users,
250+
super().__init__(json=json, id_=id_, parent=parent, users=users,
247251
sw360=sw360, **kwargs)
248252

249253
def from_json(self, json):
@@ -263,14 +267,14 @@ def from_json(self, json):
263267
json,
264268
copy_attributes=("name", "version", "downloadurl", "externalIds"))
265269

266-
def get(self, sw360=None, id_=None):
267-
"""Retrieve/update release from SW360."""
268-
if sw360:
269-
self.sw360 = sw360
270-
if id_:
271-
self.id = id_
270+
@classmethod
271+
def get(cls, sw360, id_):
272+
"""Retrieve a release from SW360."""
273+
return Release(id_=id_, json=sw360.get_release(id_), sw360=sw360)
274+
275+
def update(self):
276+
"""update release from SW360."""
272277
self.from_json(self.sw360.get_release(self.id))
273-
return self
274278

275279
def __str__(self):
276280
return f'{self.name} {self.version} ({self.id})'
@@ -282,15 +286,15 @@ class Attachment(SW360Resource):
282286
("SOURCE_SELF"), clearing reports ("CLEARING_REPORT") or CLI files
283287
("COMPONENT_LICENSE_INFO_XML").
284288
285-
You can either create it from a SW360 `json` object or by specifying the
286-
details via the constructor parameters, see list below. Only the most
287-
important attributes are supported, rest hast be provided via `kwargs` and
288-
is stored in the `details` attribute of instances.
289+
You can either create it by using `get`, from a SW360 `json` object or by
290+
creating a fresh instance. The list below describes supported attributes.
291+
Only the most important ones are supported, rest hast be provided via
292+
`kwargs` and is stored in the `details` attribute of instances.
289293
290294
For JSON parsing, please read documentation of from_json() method.
291295
292296
:param json: create it from SW360 JSON object by calling from_json()
293-
:param attachment_id: SW360 id of the attachment (if it exists already)
297+
:param id_: SW360 id of the attachment (if it exists already)
294298
:param parent: SW360 resource (release, component or project) the attachment belongs to
295299
:param filename: the filename of the attachment
296300
:param sha1: SHA1 sum of the file to check its integrity
@@ -300,21 +304,21 @@ class Attachment(SW360Resource):
300304
:param sw360: your SW360 instance for interacting with the API
301305
:param kwargs: additional relase details as specified in the SW360 REST API
302306
:type json: SW360 JSON object
303-
:type attachment_id: string
307+
:type id_: string
304308
:type release_id: string
305309
:type filename: string
306310
:type sha1: string
307311
:type attachment_type: string
308312
:type sw360: instance from SW360 class
309313
:type kwargs: dictionary
310314
"""
311-
def __init__(self, json=None, attachment_id=None, parent=None,
315+
def __init__(self, json=None, id_=None, parent=None,
312316
filename=None, sha1=None, attachment_type=None, sw360=None, **kwargs):
313317
self.attachment_type = attachment_type
314318
self.filename = filename
315319
self.sha1 = sha1
316320
self.download_link = None
317-
super().__init__(json=json, resource_id=attachment_id, parent=parent,
321+
super().__init__(json=json, id_=id_, parent=parent,
318322
sw360=sw360, **kwargs)
319323

320324
def from_json(self, json):
@@ -338,14 +342,14 @@ def from_json(self, json):
338342
"checkedBy", "checkedTeam", "checkedComment", "checkedOn",
339343
"checkStatus"))
340344

341-
def get(self, sw360=None, id_=None):
342-
"""Retrieve/update attachment from SW360."""
343-
if sw360:
344-
self.sw360 = sw360
345-
if id_:
346-
self.id = id_
345+
@classmethod
346+
def get(cls, sw360, id_):
347+
"""Retrieve attachment info from SW360."""
348+
return Attachment(id_=id_, json=sw360.get_attachment(id_), sw360=sw360)
349+
350+
def update(self):
351+
"""update attachment info from SW360."""
347352
self.from_json(self.sw360.get_attachment(self.id))
348-
return self
349353

350354
def download(self, target_path, filename=None):
351355
"""download an attachment to local file.
@@ -368,15 +372,15 @@ class Component(SW360Resource):
368372
"""A component is the SW360 abstraction for a single software
369373
package/library/program/etc.
370374
371-
You can either create it from a SW360 `json` object or by specifying the
372-
details via the constructor parameters, see list below. Only the most
373-
important attributes are supported, rest hast be provided via `kwargs` and
374-
is stored in the `details` attribute of instances.
375+
You can either create it by using `get`, from a SW360 `json` object or by
376+
creating a fresh instance. The list below describes supported attributes.
377+
Only the most important ones are supported, rest hast be provided via
378+
`kwargs` and is stored in the `details` attribute of instances.
375379
376380
For JSON parsing, please read documentation of from_json() method.
377381
378382
:param json: create component from SW360 JSON object by calling from_json()
379-
:param component_id: id of the component (if exists in SW360 already)
383+
:param id_: id of the component (if exists in SW360 already)
380384
:param name: name of the component
381385
:param description: short description for component
382386
:param homepage: homepage of the component
@@ -385,15 +389,15 @@ class Component(SW360Resource):
385389
:param sw360: your SW360 instance for interacting with the API
386390
:param kwargs: additional component details as specified in the SW360 REST API
387391
:type json: SW360 JSON object
388-
:type component_id: string
392+
:type id_: string
389393
:type name: string
390394
:type description: string
391395
:type homepage: string
392396
:type component_type: string
393397
:type sw360: instance from SW360 class
394398
:type kwargs: dictionary
395399
"""
396-
def __init__(self, json=None, component_id=None, name=None, description=None,
400+
def __init__(self, json=None, id_=None, name=None, description=None,
397401
homepage=None, component_type=None, sw360=None, **kwargs):
398402
self.releases = {}
399403
self.attachments = {}
@@ -405,7 +409,7 @@ def __init__(self, json=None, component_id=None, name=None, description=None,
405409
self.homepage = homepage
406410
self.component_type = component_type
407411

408-
super().__init__(json=json, resource_id=component_id, sw360=sw360, **kwargs)
412+
super().__init__(json=json, id_=id_, sw360=sw360, **kwargs)
409413

410414
def from_json(self, json):
411415
"""Parse component JSON object from SW360 REST API. Information for
@@ -428,14 +432,14 @@ def from_json(self, json):
428432
copy_attributes=("name", "description", "homepage",
429433
"componentType", "externalIds"))
430434

431-
def get(self, sw360=None, id_=None):
432-
"""Retrieve/update component from SW360."""
433-
if sw360:
434-
self.sw360 = sw360
435-
if id_:
436-
self.id = id_
435+
@classmethod
436+
def get(cls, sw360, id_):
437+
"""Retrieve component from SW360."""
438+
return Component(id_=id_, json=sw360.get_component(id_), sw360=sw360)
439+
440+
def update(self):
441+
"""update component from SW360."""
437442
self.from_json(self.sw360.get_component(self.id))
438-
return self
439443

440444
def __str__(self):
441445
return f'{self.name} ({self.id})'
@@ -446,15 +450,15 @@ class Project(SW360Resource):
446450
used in a project/product. It can contain links to other `Project`s or
447451
`Release`s.
448452
449-
You can either create it from a SW360 `json` object or by specifying the
450-
details via the constructor parameters, see list below. Only the most
451-
important attributes are supported, rest hast be provided via `kwargs` and
452-
is stored in the `details` attribute of instances.
453+
You can either create it by using `get`, from a SW360 `json` object or by
454+
creating a fresh instance. The list below describes supported attributes.
455+
Only the most important ones are supported, rest hast be provided via
456+
`kwargs` and is stored in the `details` attribute of instances.
453457
454458
For JSON parsing, please read documentation of from_json() method.
455459
456460
:param json: create component from SW360 JSON object by calling from_json()
457-
:param project_id: id of the project (if exists in SW360 already)
461+
:param id_: id of the project (if exists in SW360 already)
458462
:param users: dictionary of SW360 resources which link to the project
459463
(instances of Project() with id as key)
460464
:param name: name of the project
@@ -468,7 +472,7 @@ class Project(SW360Resource):
468472
:param sw360: your SW360 instance for interacting with the API
469473
:param kwargs: additional project details as specified in the SW360 REST API
470474
:type json: SW360 JSON object
471-
:type project_id: string
475+
:type id_: string
472476
:type name: string
473477
:type version: string
474478
:type description: string
@@ -477,7 +481,7 @@ class Project(SW360Resource):
477481
:type sw360: instance from SW360 class
478482
:type kwargs: dictionary
479483
"""
480-
def __init__(self, json=None, project_id=None, users={},
484+
def __init__(self, json=None, id_=None, users={},
481485
name=None, version=None, description=None, visibility=None, project_type=None,
482486
sw360=None, **kwargs):
483487
self.releases = {}
@@ -489,7 +493,7 @@ def __init__(self, json=None, project_id=None, users={},
489493
self.description = description
490494
self.visibility = visibility
491495
self.project_type = project_type
492-
super().__init__(json=json, resource_id=project_id, users=users,
496+
super().__init__(json=json, id_=id_, users=users,
493497
sw360=sw360, **kwargs)
494498

495499
def from_json(self, json):
@@ -512,14 +516,14 @@ def from_json(self, json):
512516
copy_attributes=("name", "description", "version", "visibility",
513517
"projectType", "externalIds"))
514518

515-
def get(self, sw360=None, id_=None):
516-
"""Retrieve/update project from SW360."""
517-
if sw360:
518-
self.sw360 = sw360
519-
if id_:
520-
self.id = id_
519+
@classmethod
520+
def get(cls, sw360, id_):
521+
"""Retrieve project from SW360."""
522+
return Project(id_=id_, json=sw360.get_project(id_), sw360=sw360)
523+
524+
def update(self):
525+
"""update project from SW360."""
521526
self.from_json(self.sw360.get_project(self.id))
522-
return self
523527

524528
def __str__(self):
525529
return f'{self.name} {self.version} ({self.id})'

0 commit comments

Comments
 (0)