Skip to content

Commit e997ba3

Browse files
committed
feat(sw360_objects): add Project class plus refactoring
Refactor the from_json() methods to one common version.
1 parent 5987785 commit e997ba3

2 files changed

Lines changed: 140 additions & 70 deletions

File tree

sw360/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from .sw360_api import SW360 # noqa: F401
1313
from .sw360error import SW360Error # noqa: F401
1414
from .sw360oauth2 import SW360OAuth2 # noqa: F401
15-
from .sw360_objects import Component, Release
15+
from .sw360_objects import Component, Release, Attachment, Project
1616

1717
__all__ = ["SW360", "SW360Error", "SW360OAuth2",
18-
"Component", "Release", "Attachment"]
18+
"Component", "Release", "Attachment", "Project"]

sw360/sw360_objects.py

Lines changed: 138 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
# Licensed as Siemens Inner Source, see top-level License.md file for details.
77
# -------------------------------------------------------------------------------
88

9+
import re
10+
911
"""Preview of High-Level, object oriented Python interface to the SW360 REST API.
1012
For now, this does NOT strive to be stable or complete. Feel free to use it as
1113
a more convenient abstraction for some (important) objects, but be prepared for
@@ -35,8 +37,63 @@ def __init__(self, json=None, resource_id=None, **kwargs):
3537
if json is not None:
3638
self.from_json(json)
3739

38-
def from_json(self, json):
39-
pass
40+
def _parse_release_list(self, json_list, component_id=None):
41+
"""Parse a JSON list of releases, create according objects and add
42+
them to `container`."""
43+
releases = {}
44+
for release_json in json_list:
45+
release = Release(component_id=component_id)
46+
release.from_json(release_json)
47+
releases[release.id] = release
48+
return releases
49+
50+
def _parse_attachment_list(self, json_list, resources=[]):
51+
"""Parse a JSON list of releases, create according objects and add
52+
them to `container`."""
53+
attachments = {}
54+
for attachment_json in json_list:
55+
attachment = Attachment(resources=resources)
56+
attachment.from_json(attachment_json)
57+
attachments[attachment.id] = attachment
58+
return attachments
59+
60+
def _parse_link(self, key, links_key, links_value):
61+
"""Parse a _links or _embedded section in JSON"""
62+
if links_key == "sw360:component":
63+
self.component_id = links_value["href"].split("/")[-1]
64+
elif links_key == "sw360:downloadLink":
65+
self.download_link = links_value["href"]
66+
elif links_key == "sw360:attachments":
67+
self.attachments = self._parse_attachment_list(
68+
links_value,
69+
resources=[self])
70+
elif links_key == "sw360:releases":
71+
self.releases = self._parse_release_list(
72+
links_value,
73+
component_id=self.id)
74+
elif links_key == "self":
75+
self.id = links_value["href"].split("/")[-1]
76+
else:
77+
self.details.setdefault(key, {})
78+
self.details[key][links_key] = links_value
79+
80+
_camel_case_pattern = re.compile(r'(?<!^)(?=[A-Z])')
81+
82+
def from_json(self, json, copy_attributes=list(), snake_case=True):
83+
"""`copy_attributes` will be copied as-is between this instance's
84+
attributes and JSON members. If `snake_case` is set, more Python-ish
85+
snake_case names will be used (project_type instead of projectType).
86+
"""
87+
for key, value in json.items():
88+
if key in copy_attributes:
89+
if snake_case:
90+
key = self._camel_case_pattern.sub('_', key).lower()
91+
self.__setattr__(key, value)
92+
elif key in ("_links", "_embedded"):
93+
for links_key, links_value in value.items():
94+
self._parse_link(key, links_key, links_value)
95+
else:
96+
self.details[key] = value
4097

4198

4299
class Release(SW360Resource):
@@ -81,28 +138,9 @@ def from_json(self, json):
81138
external ids which will be stored as-is in `details['externalIds'].
82139
Please note that this might change in future if better abstractions
83140
will be added in this Python library."""
84-
attachments = []
85-
for key, value in json.items():
86-
if key in ("name", "version", "downloadurl"):
87-
self.__setattr__(key, value)
88-
elif key in ("_links", "_embedded"):
89-
for links_key, links_value in value.items():
90-
if links_key == "sw360:component":
91-
self.component_id = links_value["href"].split("/")[-1]
92-
elif links_key == "sw360:attachments":
93-
attachments = links_value
94-
elif links_key == "self":
95-
self.id = links_value["href"].split("/")[-1]
96-
else:
97-
self.details.setdefault(key, {})
98-
self.details[key][links_key] = links_value
99-
else:
100-
self.details[key] = value
101-
102-
for attachment_json in attachments:
103-
attachment = Attachment(resources=[self])
104-
attachment.from_json(attachment_json)
105-
self.attachments[attachment.id] = attachment
141+
super().from_json(
142+
json,
143+
copy_attributes=("name", "version", "downloadurl"))
106144

107145
def __repr__(self):
108146
"""Representation string."""
@@ -146,6 +184,7 @@ def __init__(self, json=None, attachment_id=None, resources={},
146184
self.filename = filename
147185
self.sha1 = sha1
148186
self.attachment_type = attachment_type
187+
self.download_link = None
149188
super().__init__(json, attachment_id, **kwargs)
150189

151190
def from_json(self, json):
@@ -155,22 +194,14 @@ def from_json(self, json):
155194
156195
All details not directly supported by this class will be stored as-is
157196
in the `details` instance attribute.
158-
Please note that this might change in future if better abstractions
197+
Please note that this might change in future if more abstractions
159198
will be added in this Python library."""
160-
for key, value in json.items():
161-
if key in ("filename", "sha1"):
162-
self.__setattr__(key, value)
163-
elif key == "attachmentType":
164-
self.attachment_type = value
165-
elif key == "_links":
166-
for links_key, links_value in value.items():
167-
if links_key == "self":
168-
self.id = links_value["href"].split("/")[-1]
169-
else:
170-
self.details.setdefault(key, {})
171-
self.details[key][links_key] = links_value
172-
else:
173-
self.details[key] = value
199+
super().from_json(
200+
json,
201+
copy_attributes=("filename", "sha1", "attachmentType",
202+
"createdBy", "createdTeam", "createdComment", "createdOn",
203+
"checkedBy", "checkedTeam", "checkedComment", "checkedOn",
204+
"checkStatus"))
174205

175206
def __repr__(self):
176207
"""Representation string."""
@@ -227,37 +258,76 @@ def from_json(self, json):
227258
as-is in `details['_embedded']['sw360:vendors']` and
228259
`details['externalIds']. Please note that this might change in future
229260
if better abstractions will be added in this Python library."""
230-
releases = []
231-
attachments = []
232-
for key, value in json.items():
233-
if key in ("name", "description", "homepage"):
234-
self.__setattr__(key, value)
235-
elif key == "componentType":
236-
self.component_type = value
237-
elif key in ("_links", "_embedded"):
238-
for links_key, links_value in value.items():
239-
if key == "_links" and links_key == "self":
240-
self.id = links_value["href"].split("/")[-1]
241-
elif links_key == "sw360:releases":
242-
releases = links_value
243-
elif links_key == "sw360:attachments":
244-
attachments = links_value
245-
else:
246-
self.details.setdefault(key, {})
247-
self.details[key][links_key] = links_value
248-
else:
249-
self.details[key] = value
261+
super().from_json(
262+
json,
263+
copy_attributes=("name", "description", "homepage",
264+
"componentType"))
250265

251-
for release_json in releases:
252-
release = Release(component_id=self.id)
253-
release.from_json(release_json)
254-
self.releases[release.id] = release
266+
def __repr__(self):
267+
"""Representation string."""
268+
return "<Component %s id:%s>" % (self.name, self.id)
255269

256-
for attachment_json in attachments:
257-
attachment = Attachment(resources=[self])
258-
attachment.from_json(attachment_json)
259-
self.attachments[attachment.id] = attachment
270+
271+
class Project(SW360Resource):
272+
"""A project is SW360 abstraction for a collection of software components
273+
used in a project/product. It can contain links to other `Project`s or
274+
`Release`s.
275+
276+
You can either create it from a SW360 `json` object or by specifying the
277+
details via the constructor parameters, see list below. Only the most
278+
important attributes are supported, rest hast be provided via `kwargs` and
279+
is stored in the `details` attribute of instances.
280+
281+
For JSON parsing, please read documentation of from_json() method.
282+
283+
:param json: create component from SW360 JSON object by calling from_json()
284+
:param project_id: id of the project (if exists in SW360 already)
285+
:param name: name of the project
286+
:param version: version of the project
287+
:param description: short description for project
288+
:param visibility: project visibility in SW360, one of "PRIVATE",
289+
"ME_AND_MODERATORS", "BUISNESSUNIT_AND_MODERATORS",
290+
"EVERYONE"
291+
:param project_type: one of "CUSTOMER", "INTERNAL", "PRODUCT", "SERVICE",
292+
"INNER_SOURCE"
293+
:param kwargs: additional project details as specified in the SW360 REST API
294+
:type json: SW360 JSON object
295+
:type project_id: string
296+
:type name: string
297+
:type version: string
298+
:type description: string
299+
:type visibility: string
300+
:type project_type: string
301+
:type kwargs: dictionary
302+
"""
303+
def __init__(self, json=None, project_id=None, name=None, version=None,
304+
description=None, visibility=None, project_type=None,
305+
**kwargs):
306+
self.releases = {}
307+
308+
self.name = name
309+
self.version = version
310+
self.description = description
311+
self.visibility = visibility
312+
self.project_type = project_type
313+
super().__init__(json, project_id, **kwargs)
314+
315+
def from_json(self, json):
316+
"""Parse project JSON object from SW360 REST API. Information for
317+
its releases will be extracted, Release() objects created for them
318+
and stored in the `releases` instance attribue. Please note that
319+
the REST API will only provide basic information for the releases.
320+
321+
All details not directly supported by this class will be
322+
stored as-is in the `details` instance attribute. For now, this also
323+
includes linked projects and external ids. Please note that this might
324+
change in future if better abstractions will be added in this Python
325+
library."""
326+
super().from_json(
327+
json,
328+
copy_attributes=("name", "description", "version", "visibility",
329+
"projectType"))
260330

261331
def __repr__(self):
262332
"""Representation string."""
263-
return "<Component %s id:%s>" % (self.name, self.id)
333+
return "<Project %s id:%s>" % (self.name, self.id)

0 commit comments

Comments
 (0)