Skip to content

Commit dacf44e

Browse files
authored
Merge pull request #127 from fossology/feat/api-v2
feat(v2): support API version v2 by default
2 parents 24b8e5e + 5b0725a commit dacf44e

File tree

7 files changed

+143
-52
lines changed

7 files changed

+143
-52
lines changed

fossology/__init__.py

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,13 @@
2525

2626

2727
def fossology_token(
28-
url, username, password, token_name, token_scope=TokenScope.READ, token_expire=None
28+
url,
29+
username,
30+
password,
31+
token_name,
32+
token_scope=TokenScope.READ,
33+
token_expire=None,
34+
version="v2",
2935
):
3036
"""Generate an API token using username/password
3137
@@ -35,7 +41,7 @@ def fossology_token(
3541
3642
>>> from fossology import fossology_token # doctest: +SKIP
3743
>>> from fossology.obj import TokenScope # doctest: +SKIP
38-
>>> token = fossology_token("https://fossology.example.com", "Me", "MyPassword", "MyToken") # doctest: +SKIP
44+
>>> token = fossology_token("https://fossology.example.com/repo", "Me", "MyPassword", "MyToken", version="v2") # doctest: +SKIP
3945
4046
4147
:param url: the URL of the Fossology server
@@ -44,30 +50,46 @@ def fossology_token(
4450
:param name: the name of the token
4551
:param scope: the scope of the token (default: TokenScope.READ)
4652
:param expire: the expire date of the token, e.g. 2019-12-25 (default: max. 30 days)
53+
:param version: the version of the API to use (default: "v2")
4754
:type url: string
4855
:type username: string
4956
:type password: string
5057
:type name: string
5158
:type scope: TokenScope
5259
:type expire: string
60+
:type version: string
5361
:return: the new token
5462
:rtype: string
5563
:raises AuthenticationError: if the username or password is incorrect
5664
:raises FossologyApiError: if another error occurs
5765
"""
58-
data = {
59-
"username": username,
60-
"password": password,
61-
"token_name": token_name,
62-
"token_scope": token_scope.value,
63-
}
66+
if version == "v2":
67+
data = {
68+
"username": username,
69+
"password": password,
70+
"tokenName": token_name,
71+
"tokenScope": token_scope.value,
72+
}
73+
else:
74+
data = {
75+
"username": username,
76+
"password": password,
77+
"token_name": token_name,
78+
"token_scope": token_scope.value,
79+
}
6480
if token_expire:
65-
data["token_expire"] = token_expire
81+
if version == "v2":
82+
data["tokenExpire"] = token_expire
83+
else:
84+
data["token_expire"] = token_expire
6685
else:
6786
now = date.today()
68-
data["token_expire"] = str(now + timedelta(days=30))
87+
if version == "v2":
88+
data["tokenExpire"] = str(now + timedelta(days=30))
89+
else:
90+
data["token_expire"] = str(now + timedelta(days=30))
6991
try:
70-
response = requests.post(url + "/api/v1/tokens", data=data)
92+
response = requests.post(url + "/api/" + version + "/tokens", data=data)
7193
if response.status_code == 201:
7294
token = response.json()["Authorization"]
7395
return token.replace("Bearer ", "")
@@ -96,19 +118,20 @@ class Fossology(
96118
97119
:param url: URL of the Fossology instance
98120
:param token: The API token generated using the Fossology UI
121+
:param version: the version of the API to use (default: "v2")
99122
:type url: str
100123
:type token: str
124+
:type version: str
101125
:raises FossologyApiError: if a REST call failed
102126
:raises AuthenticationError: if the user couldn't be authenticated
103127
"""
104128

105-
def __init__(self, url, token, name=None):
129+
def __init__(self, url, token, version="v2"):
106130
self.host = url
107131
self.token = token
108132
self.users = list()
109133
self.folders = list()
110-
111-
self.api = f"{self.host}/api/v2"
134+
self.api = f"{self.host}/api/{version}"
112135
self.session = requests.Session()
113136
self.session.headers.update({"Authorization": f"Bearer {self.token}"})
114137
self.info = self.get_info()

fossology/obj.py

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,13 @@ def __str__(self):
571571

572572
@classmethod
573573
def from_json(cls, json_dict):
574+
for key in ("viewInfo", "metaInfo", "packageInfo", "tagInfo", "reuseInfo"):
575+
try:
576+
json_dict[key.replace("I", "_i")] = json_dict[key]
577+
del json_dict[key]
578+
except KeyError:
579+
pass
580+
574581
return cls(**json_dict)
575582

576583

@@ -586,6 +593,9 @@ class Upload(object):
586593
:param description: further information about the upload
587594
:param uploadname: the name of the upload (default: the name of the upload file)
588595
:param uploaddate: the date of the upload
596+
:param assignee: the user who is assigned to the upload
597+
:param assigneeDate: the date of the assignment
598+
:param closingDate: the date of the closing
589599
:param hash: the hash data of the uploaded file
590600
:param kwargs: handle any other upload information provided by the fossology instance
591601
:type folderid: int
@@ -594,6 +604,9 @@ class Upload(object):
594604
:type description: string
595605
:type uploadname: string
596606
:type uploaddate: string
607+
:type assignee: string
608+
:type assigneeDate: string
609+
:type closingDate: string
597610
:type hash: Hash
598611
:type kwargs: key word argument
599612
"""
@@ -606,8 +619,9 @@ def __init__(
606619
description,
607620
uploadname,
608621
uploaddate,
609-
filesize=None,
610-
filesha1=None,
622+
assignee=None,
623+
assigneeDate=None,
624+
closingDate=None,
611625
hash=None,
612626
**kwargs,
613627
):
@@ -617,30 +631,26 @@ def __init__(
617631
self.description = description
618632
self.uploadname = uploadname
619633
self.uploaddate = uploaddate
620-
if filesize and filesha1:
621-
self.filesize = filesize
622-
self.filesha1 = filesha1
623-
self.hash = None
624-
else:
625-
self.filesize = None
626-
self.filesha1 = None
627-
self.hash = Hash.from_json(hash)
634+
self.assignee = (assignee,)
635+
self.assigneeDate = (assigneeDate,)
636+
self.closeDate = (closingDate,)
637+
self.hash = Hash.from_json(hash)
628638
self.additional_info = kwargs
629639

630640
def __str__(self):
631-
if self.filesize:
632-
return (
633-
f"Upload '{self.uploadname}' ({self.id}, {self.filesize}B, {self.filesha1}) "
634-
f"in folder {self.foldername} ({self.folderid})"
635-
)
636-
else:
637-
return (
638-
f"Upload '{self.uploadname}' ({self.id}, {self.hash.size}B, {self.hash.sha1}) "
639-
f"in folder {self.foldername} ({self.folderid})"
640-
)
641+
return (
642+
f"Upload '{self.uploadname}' ({self.id}, {self.hash.size}B, {self.hash.sha1}) "
643+
f"in folder {self.foldername} ({self.folderid})"
644+
)
641645

642646
@classmethod
643647
def from_json(cls, json_dict):
648+
for key in ("folderId", "folderName", "uploadName", "uploadDate"):
649+
try:
650+
json_dict[key.lower()] = json_dict[key]
651+
del json_dict[key]
652+
except KeyError:
653+
pass
644654
return cls(**json_dict)
645655

646656

fossology/uploads.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ def list_uploads_parameters(
3535
status: ClearingStatus | None = None,
3636
assignee: str | None = None,
3737
since: str | None = None,
38+
group: str | None = None,
39+
limit: str | None = None,
3840
) -> dict:
3941
"""Helper function to list of query parameters for GET /uploads endpoint"""
4042
date_pattern = re.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}")
@@ -56,6 +58,10 @@ def list_uploads_parameters(
5658
)
5759
else:
5860
params["since"] = since
61+
if group:
62+
params["groupName"] = group
63+
if limit:
64+
params["limit"] = limit
5965
return params
6066

6167

@@ -285,16 +291,10 @@ def upload_file(
285291
upload = self.detail_upload(
286292
response.json()["message"], group, wait_time
287293
)
288-
if upload.filesize:
289-
logger.info(
290-
f"Upload {upload.uploadname} ({upload.filesize}) "
291-
f"has been uploaded on {upload.uploaddate}"
292-
)
293-
else:
294-
logger.info(
295-
f"Upload {upload.uploadname} ({upload.hash.size}) "
296-
f"has been uploaded on {upload.uploaddate}"
297-
)
294+
logger.info(
295+
f"Upload {upload.uploadname} ({upload.hash.size}) "
296+
f"has been uploaded on {upload.uploaddate}"
297+
)
298298
return upload
299299
except TryAgain:
300300
description = f"Upload of {source} failed"
@@ -391,6 +391,7 @@ def upload_licenses(
391391
headers = {}
392392
if group:
393393
headers["groupName"] = group
394+
params["groupName"] = group # type: ignore
394395

395396
response = self.session.get(
396397
f"{self.api}/uploads/{upload.id}/licenses", params=params, headers=headers
@@ -545,6 +546,8 @@ def list_uploads(
545546
status=status,
546547
assignee=assignee,
547548
since=since,
549+
group=group,
550+
limit=page_size,
548551
)
549552
uploads_list = list()
550553
if all_pages:
@@ -554,6 +557,7 @@ def list_uploads(
554557
x_total_pages = page
555558
while page <= x_total_pages:
556559
headers["page"] = str(page)
560+
params["page"] = str(page)
557561
response = self.session.get(
558562
f"{self.api}/uploads", headers=headers, params=params
559563
)
@@ -643,8 +647,10 @@ def move_upload(self, upload: Upload, folder: Folder, action: str):
643647
:raises FossologyApiError: if the REST call failed
644648
:raises AuthorizationError: if the REST call is not authorized
645649
"""
646-
headers = {"folderId": str(folder.id), "action": action}
647-
response = self.session.put(f"{self.api}/uploads/{upload.id}", headers=headers)
650+
params = {"folderId": str(folder.id), "action": action}
651+
response = self.session.put(
652+
f"{self.api}/uploads/{upload.id}", headers=params, params=params
653+
)
648654

649655
if response.status_code == 202:
650656
logger.info(
@@ -771,7 +777,7 @@ def upload_permissions(
771777
:raises AuthorizationError: if the REST call is not authorized
772778
"""
773779
response = self.session.get(f"{self.api}/uploads/{upload.id}/perm-groups")
774-
780+
print(response.request.url)
775781
if response.status_code == 200:
776782
return UploadPermGroups.from_json(response.json())
777783

tests/conftest.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,22 @@ def foss_token(foss_server: str) -> str:
164164
@pytest.fixture(scope="session")
165165
def foss(foss_server: str, foss_token: str, foss_agents: Agents) -> fossology.Fossology:
166166
try:
167-
foss = fossology.Fossology(foss_server, foss_token, "fossy")
167+
foss = fossology.Fossology(foss_server, foss_token)
168+
except (FossologyApiError, AuthenticationError) as error:
169+
exit(error.message)
170+
171+
# Configure all license agents besides 'ojo'
172+
foss.user.agents = foss_agents
173+
yield foss
174+
foss.close()
175+
176+
177+
@pytest.fixture(scope="session")
178+
def foss_v1(
179+
foss_server: str, foss_token: str, foss_agents: Agents
180+
) -> fossology.Fossology:
181+
try:
182+
foss = fossology.Fossology(foss_server, foss_token, version="v1")
168183
except (FossologyApiError, AuthenticationError) as error:
169184
exit(error.message)
170185

@@ -215,6 +230,24 @@ def upload(
215230
time.sleep(5)
216231

217232

233+
@pytest.fixture(scope="function")
234+
def upload_v1(
235+
foss_v1: fossology.Fossology,
236+
test_file_path: str,
237+
) -> Generator:
238+
upload = foss_v1.upload_file(
239+
foss_v1.rootFolder,
240+
file=test_file_path,
241+
description="Test upload via fossology-python lib",
242+
access_level=AccessLevel.PUBLIC,
243+
wait_time=5,
244+
)
245+
jobs_lookup(foss_v1, upload)
246+
yield upload
247+
foss_v1.delete_upload(upload)
248+
time.sleep(5)
249+
250+
218251
@pytest.fixture(scope="session")
219252
def upload_with_jobs(
220253
foss: fossology.Fossology, test_file_path: str, foss_schedule_agents: dict

tests/test_items.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ def test_item_info(foss: Fossology, upload_with_jobs: Upload):
1616
assert info.meta_info
1717

1818

19+
def test_item_info_v1(foss_v1: Fossology, upload_with_jobs: Upload):
20+
files, _ = foss_v1.search(license="BSD")
21+
info: FileInfo = foss_v1.item_info(upload_with_jobs, files[0].uploadTreeId)
22+
assert info.meta_info
23+
24+
1925
def test_item_info_with_unknown_item_raises_api_error(
2026
foss: Fossology, upload_with_jobs: Upload
2127
):

tests/test_uploads.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import time
88
from datetime import date, timedelta
99
from pathlib import Path
10-
from unittest.mock import Mock
1110

1211
import pytest
1312
import responses
@@ -31,6 +30,19 @@ def test_upload_sha1(upload: Upload):
3130
)
3231

3332

33+
def test_upload_v1(upload_v1: Upload):
34+
assert upload_v1.uploadname == "base-files_11.tar.xz"
35+
assert upload_v1.hash.sha1 == "D4D663FC2877084362FB2297337BE05684869B00"
36+
assert str(upload_v1) == (
37+
f"Upload '{upload_v1.uploadname}' ({upload_v1.id}, {upload_v1.hash.size}B, {upload_v1.hash.sha1}) "
38+
f"in folder {upload_v1.foldername} ({upload_v1.folderid})"
39+
)
40+
assert str(upload_v1.hash) == (
41+
f"File SHA1: {upload_v1.hash.sha1} MD5 {upload_v1.hash.md5} "
42+
f"SH256 {upload_v1.hash.sha256} Size {upload_v1.hash.size}B"
43+
)
44+
45+
3446
def test_get_upload_unauthorized(foss: Fossology, upload: Upload):
3547
with pytest.raises(AuthorizationError) as excinfo:
3648
foss.detail_upload(
@@ -157,13 +169,14 @@ def test_move_upload_to_non_existing_folder(foss: Fossology, upload: Upload):
157169

158170
@responses.activate
159171
def test_move_upload_error(foss: Fossology, foss_server: str, upload: Upload):
172+
folder = Folder(secrets.randbelow(1000), "Folder", "", foss.rootFolder)
160173
responses.add(
161174
responses.PUT,
162175
f"{foss_server}/api/v2/uploads/{upload.id}",
163176
status=500,
164177
)
165178
with pytest.raises(FossologyApiError):
166-
foss.move_upload(upload, Mock(), "move")
179+
foss.move_upload(upload, folder, "move")
167180

168181

169182
def test_update_upload(foss: Fossology, upload: Upload):

0 commit comments

Comments
 (0)