Skip to content

Commit 9fe0273

Browse files
authored
Merge pull request #29 from sachinshaji/master
Introduce session and re-try for all api responses
2 parents bfa224d + 58647ee commit 9fe0273

File tree

10 files changed

+239
-135
lines changed

10 files changed

+239
-135
lines changed

sw360/attachments.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -219,13 +219,14 @@ def _upload_resource_attachment(self, resource_type: str, resource_id: str, uplo
219219
"application/json",
220220
),
221221
}
222-
response = requests.post(url, headers=self.api_headers, files=file_data) # type: ignore
223-
if response.status_code == HTTPStatus.ACCEPTED:
224-
logger.warning(
225-
f"Attachment upload was accepted by {url} but might not be visible yet: {response.text}"
226-
)
227-
if not response.ok:
228-
raise SW360Error(response, url)
222+
response = self.api_post_multipart(url, files=file_data)
223+
if response is not None:
224+
if response.status_code == HTTPStatus.ACCEPTED:
225+
logger.warning(
226+
f"Attachment upload was accepted by {url} but might not be visible yet: {response.text}"
227+
)
228+
if not response.ok:
229+
raise SW360Error(response, url)
229230

230231
def upload_release_attachment(self, release_id: str, upload_file: str, upload_type: str = "SOURCE",
231232
upload_comment: str = "") -> None:

sw360/base.py

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# SPDX-License-Identifier: MIT
88
# -------------------------------------------------------------------------------
99

10-
from typing import Any, Dict, Optional, Tuple
10+
from typing import Any, Dict, Optional, Tuple, Union, List
1111

1212
import requests
1313

@@ -73,6 +73,122 @@ def api_get(self, url: str = "") -> Optional[Dict[str, Any]]:
7373

7474
raise SW360Error(response, url)
7575

76+
def api_post_multipart(self, url: str = "", files: Dict[str, Any] = {}) -> Optional[requests.Response]:
77+
"""
78+
Send a multipart POST request to the specified URL with the provided file data.
79+
80+
:param url: The URL to send the multipart POST request to.
81+
:type url: str
82+
:param files: The dictionary containing file data to be sent in the request.
83+
:type files: Dict[str, Any]
84+
:return: The JSON response received from the server, if any.
85+
:rtype: Optional[Dict[str, Any]]
86+
:raises SW360Error: If the HTTP response indicates an error.
87+
"""
88+
89+
if (not self.force_no_session) and self.session is None:
90+
raise SW360Error(message="login_api needs to be called first")
91+
92+
if self.force_no_session:
93+
response = requests.post(url, headers=self.api_headers, files=files)
94+
else:
95+
if self.session:
96+
response = self.session.post(url, files=files)
97+
98+
if response.ok:
99+
if response.status_code == 204: # 204 = no content
100+
return None
101+
return response
102+
103+
raise SW360Error(response, url)
104+
105+
def api_post(
106+
self,
107+
url: str = "",
108+
json: Optional[Union[Dict[str, Any], List[Dict[str, Any]]]] = None
109+
) -> Optional[requests.Response]:
110+
111+
"""
112+
Send a POST request to the specified URL with the provided json data.
113+
114+
:param url: The URL to send the POST request to.
115+
:type url: str
116+
:param json: The dictionary containing json data to be sent in the request.
117+
:type json: Dict[str, Any]
118+
:return: The JSON response received from the server, if any.
119+
:rtype: Optional[Dict[str, Any]]
120+
:raises SW360Error: If the HTTP response indicates an error.
121+
"""
122+
123+
if (not self.force_no_session) and self.session is None:
124+
raise SW360Error(message="login_api needs to be called first")
125+
126+
if self.force_no_session:
127+
response = requests.post(url, headers=self.api_headers, json=json)
128+
else:
129+
if self.session:
130+
response = self.session.post(url, json=json)
131+
132+
if response.ok:
133+
if response.status_code == 204: # 204 = no content
134+
return None
135+
return response
136+
137+
raise SW360Error(response, url)
138+
139+
def api_patch(self, url: str = "", json: Dict[str, Any] = {}) -> Optional[Dict[str, Any]]:
140+
"""
141+
Send a PATCH request to the specified URL with the provided json data.
142+
143+
:param url: The URL to send the PATCH request to.
144+
:type url: str
145+
:param json: The dictionary containing json data to be sent in the request.
146+
:type json: Dict[str, Any]
147+
:return: The JSON response received from the server, if any.
148+
:rtype: Optional[Dict[str, Any]]
149+
:raises SW360Error: If the HTTP response indicates an error.
150+
"""
151+
if (not self.force_no_session) and self.session is None:
152+
raise SW360Error(message="login_api needs to be called first")
153+
154+
if self.force_no_session:
155+
response = requests.patch(url, headers=self.api_headers, json=json)
156+
else:
157+
if self.session:
158+
response = self.session.patch(url, json=json)
159+
160+
if response.ok:
161+
if response.status_code == 204: # 204 = no content
162+
return None
163+
return response.json()
164+
165+
raise SW360Error(response, url)
166+
167+
def api_delete(self, url: str = "") -> Optional[requests.Response]:
168+
"""Send a DELETE request to the specified `url` of the REST API and return JSON response.
169+
170+
:param url: The URL to which the DELETE request will be sent.
171+
:type url: str
172+
:return: JSON data returned by the API, or None if the response is empty.
173+
:rtype: Optional[Dict[str, Any]]
174+
:raises SW360Error: If the API responds with a non-success HTTP status code.
175+
"""
176+
if (not self.force_no_session) and self.session is None:
177+
raise SW360Error(message="login_api needs to be called first")
178+
179+
if self.force_no_session:
180+
response = requests.delete(url, headers=self.api_headers)
181+
else:
182+
if self.session:
183+
response = self.session.delete(url)
184+
185+
if response.ok:
186+
if response.status_code == 204: # 204 = no content
187+
return None
188+
return response
189+
190+
raise SW360Error(response, url)
191+
76192
# type checking: not for Python 3.8: tuple[Optional[Any], Dict[str, Dict[str, str]], bool]
77193
def _update_external_ids(self, current_data: Dict[str, Any], ext_id_name: str, ext_id_value: str,
78194
update_mode: str) -> Tuple[Optional[Any], Dict[str, Dict[str, str]], bool]:

sw360/components.py

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111

1212
from typing import Any, Dict, List, Optional
1313

14-
import requests
15-
1614
from .base import BaseMixin
1715
from .sw360error import SW360Error
1816

@@ -197,13 +195,12 @@ def create_new_component(self, name: str, description: str, component_type: str,
197195
component_details[param] = locals()[param]
198196
component_details["componentType"] = component_type
199197

200-
response = requests.post(
201-
url, json=component_details, headers=self.api_headers
202-
)
203-
if response.ok:
204-
return response.json()
205-
206-
raise SW360Error(response, url)
198+
response = self.api_post(
199+
url, json=component_details)
200+
if response is not None:
201+
if response.ok:
202+
return response.json()
203+
return None
207204

208205
def update_component(self, component: Dict[str, Any], component_id: str) -> Optional[Dict[str, Any]]:
209206
"""Update an existing component
@@ -223,14 +220,7 @@ def update_component(self, component: Dict[str, Any], component_id: str) -> Opti
223220
raise SW360Error(message="No component id provided!")
224221

225222
url = self.url + "resource/api/components/" + component_id
226-
response = requests.patch(
227-
url, json=component, headers=self.api_headers,
228-
)
229-
230-
if response.ok:
231-
return response.json()
232-
233-
raise SW360Error(response, url)
223+
return self.api_patch(url, json=component)
234224

235225
def update_component_external_id(self, ext_id_name: str, ext_id_value: str,
236226
component_id: str, update_mode: str = "none") -> Optional[Dict[str, Any]]:
@@ -282,13 +272,11 @@ def delete_component(self, component_id: str) -> Optional[Dict[str, Any]]:
282272
raise SW360Error(message="No component id provided!")
283273

284274
url = self.url + "resource/api/components/" + component_id
285-
response = requests.delete(
286-
url, headers=self.api_headers,
287-
)
288-
if response.ok:
289-
return response.json()
290-
291-
raise SW360Error(response, url)
275+
response = self.api_delete(url)
276+
if response is not None:
277+
if response.ok:
278+
return response.json()
279+
return None
292280

293281
def get_users_of_component(self, component_id: str) -> Optional[Dict[str, Any]]:
294282
"""Get information of about the users of a component

sw360/license.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,13 @@ def create_new_license(
5050
license_details["text"] = text
5151
license_details["checked"] = checked
5252

53-
response = requests.post(url, json=license_details, headers=self.api_headers)
54-
if response.ok:
55-
return response.json()
56-
53+
response = self.api_post(url, json=license_details)
54+
if response is not None:
55+
if response.ok:
56+
return response.json()
5757
raise SW360Error(response, url)
5858

59-
def delete_license(self, license_shortname: str) -> bool:
59+
def delete_license(self, license_shortname: str) -> Optional[bool]:
6060
"""Delete an existing license
6161
6262
API endpoint: PATCH /licenses
@@ -73,14 +73,11 @@ def delete_license(self, license_shortname: str) -> bool:
7373

7474
url = self.url + "resource/api/licenses/" + license_shortname
7575
print(url)
76-
response = requests.delete(
77-
url, headers=self.api_headers,
78-
)
79-
80-
if response.ok:
81-
return True
82-
83-
raise SW360Error(response, url)
76+
response = self.api_delete(url)
77+
if response is not None:
78+
if response.ok:
79+
return True
80+
return None
8481

8582
def download_license_info(
8683
self, project_id: str, filename: str, generator: str = "XhtmlGenerator", variant: str = "DISCLOSURE"

sw360/project.py

Lines changed: 27 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111

1212
from typing import Any, Dict, List, Optional
1313

14-
import requests
15-
1614
from .base import BaseMixin
1715
from .sw360error import SW360Error
1816

@@ -305,13 +303,11 @@ def create_new_project(self, name: str, project_type: str, visibility: Any,
305303
project_details["projectType"] = project_type
306304

307305
url = self.url + "resource/api/projects"
308-
response = requests.post(
309-
url, json=project_details, headers=self.api_headers
310-
)
311-
312-
if response.ok:
313-
return response.json()
314-
306+
response = self.api_post(
307+
url, json=project_details)
308+
if response is not None:
309+
if response.ok:
310+
return response.json()
315311
raise SW360Error(response, url)
316312

317313
def update_project(self, project: Dict[str, Any], project_id: str,
@@ -345,14 +341,12 @@ def update_project(self, project: Dict[str, Any], project_id: str,
345341
nsp["projectRelationship"] = sp.get("relation", "CONTAINED")
346342
project["linkedProjects"][pid] = nsp
347343

348-
response = requests.patch(url, json=project, headers=self.api_headers)
344+
return self.api_patch(url, json=project)
349345

350-
if response.ok:
351-
return response.json()
352-
353-
raise SW360Error(response, url)
354-
355-
def update_project_releases(self, releases: List[Dict[str, Any]], project_id: str, add: bool = False) -> bool:
346+
def update_project_releases(
347+
self,
348+
releases: List[Dict[str, Any]], project_id: str, add: bool = False
349+
) -> Optional[bool]:
356350
"""Update the releases of an existing project. If `add` is True,
357351
given `releases` are added to the project, otherwise, the existing
358352
releases will be replaced.
@@ -383,12 +377,11 @@ def update_project_releases(self, releases: List[Dict[str, Any]], project_id: st
383377
releases = old_releases + list(releases)
384378

385379
url = self.url + "resource/api/projects/" + project_id + "/releases"
386-
response = requests.post(url, json=releases, headers=self.api_headers)
387-
388-
if response.ok:
389-
return True
390-
391-
raise SW360Error(response, url)
380+
response = self.api_post(url, json=releases)
381+
if response is not None:
382+
if response.ok:
383+
return True
384+
return None
392385

393386
def update_project_external_id(self, ext_id_name: str, ext_id_value: str,
394387
project_id: str, update_mode: str = "none") -> Any:
@@ -441,13 +434,11 @@ def delete_project(self, project_id: str) -> Optional[Dict[str, Any]]:
441434
raise SW360Error(message="No project id provided!")
442435

443436
url = self.url + "resource/api/projects/" + project_id
444-
response = requests.delete(
445-
url, headers=self.api_headers
446-
)
447-
if response.ok:
448-
return response.json()
449-
450-
raise SW360Error(response, url)
437+
response = self.api_delete(url)
438+
if response is not None:
439+
if response.ok:
440+
return response.json()
441+
return None
451442

452443
def get_users_of_project(self, project_id: str) -> Optional[Dict[str, Any]]:
453444
"""Get information of about users of a project
@@ -486,14 +477,12 @@ def duplicate_project(self, project_id: str, new_version: str) -> Optional[Dict[
486477
project_details["clearingState"] = "OPEN"
487478

488479
url = self.url + "resource/api/projects/duplicate/" + project_id
489-
response = requests.post(
490-
url, json=project_details, headers=self.api_headers
491-
)
492-
493-
if response.ok:
494-
return response.json()
495-
496-
raise SW360Error(response, url)
480+
response = self.api_post(
481+
url, json=project_details)
482+
if response is not None:
483+
if response.ok:
484+
return response.json()
485+
return None
497486

498487
def update_project_release_relationship(
499488
self, project_id: str, release_id: str, new_state: str,
@@ -528,9 +517,4 @@ def update_project_release_relationship(
528517
relation["comment"] = comment
529518

530519
url = self.url + "resource/api/projects/" + project_id + "/release/" + release_id
531-
response = requests.patch(url, json=relation, headers=self.api_headers)
532-
533-
if response.ok:
534-
return response.json()
535-
536-
raise SW360Error(response, url)
520+
return self.api_patch(url, json=relation)

0 commit comments

Comments
 (0)