Skip to content

Commit d66adb9

Browse files
authored
Merge pull request #7 from zkarpinski/create_project_feature
Added create project api support
2 parents a449deb + fb0b9db commit d66adb9

File tree

6 files changed

+174
-93
lines changed

6 files changed

+174
-93
lines changed

codeinsight_sdk/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from .client import CodeInsightClient
1+
from .client import CodeInsightClient

codeinsight_sdk/client.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ class CodeInsightClient:
1111
def __init__(self,
1212
base_url: str,
1313
api_token: str,
14+
timeout: int = 60,
15+
verify_ssl: bool = True
1416
):
1517
self.base_url = base_url
1618
self.api_url = f"{base_url}/codeinsight/api"
@@ -20,6 +22,8 @@ def __init__(self,
2022
"Authorization": "Bearer %s" % self.__api_token,
2123
"User-Agent": "codeinsight_sdk_python",
2224
}
25+
self.__timeout = timeout
26+
self.__verify_ssl = verify_ssl
2327

2428
def request(self, method, url_part: str, params: dict = None, body: any = None ):
2529
url = f"{self.api_url}/{url_part}"
@@ -31,10 +35,11 @@ def request(self, method, url_part: str, params: dict = None, body: any = None )
3135
del params[k]
3236

3337
response = requests.request(method, url,
34-
headers=self.__api_headers, params=params, json=body)
38+
headers=self.__api_headers, params=params, json=body,
39+
timeout=self.__timeout, verify=self.__verify_ssl)
3540

3641
if not response.ok:
37-
logger.error(f"Error: {response.status_code} - {response.reason}")
42+
logger.error(f"Error: {response.status_code} - {response.reason}", exc_info=True)
3843
logger.error(response.text)
3944
raise CodeInsightError(response)
4045

codeinsight_sdk/handlers.py

Lines changed: 155 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -27,100 +27,135 @@ def get(self):
2727
class ProjectHandler(Handler):
2828
def __init__(self, client):
2929
super().__init__(client)
30-
self.cls = Project
31-
30+
self.cls = Project
31+
32+
def create(self, name:str, description:str = None, folder:str = None,
33+
scanProfileName:str = None,
34+
owner:str = None,
35+
risk:str = None,
36+
folderId:int = None,
37+
customFields:List[dict] = None,
38+
) -> int:
39+
"""
40+
Creates a project.
41+
42+
Args:
43+
name (str): The name of the project.
44+
description (str, optional): The description of the project. Defaults to None.
45+
folder (str, optional): The folder of the project. Defaults to None.
46+
47+
Returns:
48+
Project: The created project id.
49+
"""
50+
path = "projects"
51+
data = {"name": name,
52+
"description": description,
53+
"folderName": folder,
54+
"scanProfileName": scanProfileName,
55+
"owner": owner,
56+
"risk": risk,
57+
"folderId": folderId,
58+
"customFields": customFields}
59+
resp = self.client.request("POST", url_part=path, body=data)
60+
try:
61+
project_id = resp.json()['data']['id']
62+
except KeyError:
63+
raise CodeInsightError(resp)
64+
return project_id
65+
66+
3267
#Note API endpoints switch between projects and project...
3368
def all(self) -> List[Project]:
34-
"""
35-
Retrieves all projects from the server.
69+
"""
70+
Retrieves all projects from the server.
3671
37-
Returns:
38-
A list of Project objects representing all the projects.
39-
"""
40-
41-
path = "projects"
42-
resp = self.client.request("GET", url_part=path)
43-
projects = []
44-
for project_data in resp.json()['data']:
45-
projects.append(self.cls.from_dict(project_data))
46-
return projects
72+
Returns:
73+
A list of Project objects representing all the projects.
74+
"""
75+
76+
path = "projects"
77+
resp = self.client.request("GET", url_part=path)
78+
projects = []
79+
for project_data in resp.json()['data']:
80+
projects.append(self.cls.from_dict(project_data))
81+
return projects
4782

4883
def get(self, id:int) -> Project:
49-
"""
50-
Retrieves a project by its ID.
51-
52-
Args:
53-
id (int): The ID of the project requested.
54-
55-
Returns:
56-
Project: The retrieved project.
57-
"""
58-
path = f"projects/{id}"
59-
resp = self.client.request("GET", url_part=path)
60-
project_data = resp.json()['data']
61-
return self.cls.from_dict(project_data)
84+
"""
85+
Retrieves a project by its ID.
86+
87+
Args:
88+
id (int): The ID of the project requested.
89+
90+
Returns:
91+
Project: The retrieved project.
92+
"""
93+
path = f"projects/{id}"
94+
resp = self.client.request("GET", url_part=path)
95+
project_data = resp.json()['data']
96+
return self.cls.from_dict(project_data)
6297

6398
def get_id(self, project_name:str) -> int:
64-
"""
65-
Retrieves the ID of a project based on its name.
66-
67-
Args:
68-
projectName (str): The name of the project.
69-
70-
Returns:
71-
int: The ID of the project.
72-
"""
73-
path = "project/id"
74-
params = {"projectName": project_name}
75-
resp = self.client.request("GET", url_part=path, params=params)
76-
try:
77-
project_id = resp.json()['Content: '] # Yes, the key is called 'Content: ' ...
78-
except KeyError:
79-
raise CodeInsightError(resp)
80-
return project_id
81-
99+
"""
100+
Retrieves the ID of a project based on its name.
101+
102+
Args:
103+
projectName (str): The name of the project.
104+
105+
Returns:
106+
int: The ID of the project.
107+
"""
108+
path = "project/id"
109+
params = {"projectName": project_name}
110+
resp = self.client.request("GET", url_part=path, params=params)
111+
try:
112+
project_id = resp.json()['Content: '] # Yes, the key is called 'Content: ' ...
113+
except KeyError:
114+
raise CodeInsightError(resp)
115+
return project_id
116+
82117
def get_inventory_summary(self, project_id:int,
83-
vulnerabilitySummary : bool = False,
84-
cvssVersion: str = 'ANY',
85-
published: str = 'ALL',
86-
offset:int = 1,
87-
limit:int = 25) -> List[ProjectInventoryItem]:
88-
"""
89-
Retrieves the inventory summary for a specific project.
90-
91-
Args:
92-
project_id (int): The ID of the project.
93-
vulnerabilitySummary (bool, optional): Flag to include vulnerability summary. Defaults to False.
94-
cvssVersion (str, optional): The CVSS version to filter vulnerabilities. Defaults to 'ANY'.
95-
published (str, optional): The publication status. Defaults to 'ALL'.
96-
offset (int, optional): The offset for pagination. Defaults to 1.
97-
limit (int, optional): The maximum number of items to return. Defaults to 25.
98-
99-
Returns:
100-
List[ProjectInventoryItem]: A list of ProjectInventoryItem objects representing the inventory summary.
101-
"""
102-
path = f"projects/{project_id}/inventorySummary"
103-
params = {"vulnerabilitySummary": vulnerabilitySummary,
104-
"cvssVersion": cvssVersion,
105-
"published": published,
106-
"offset": offset,
107-
"limit": limit
108-
}
109-
resp = self.client.request("GET", url_part=path, params=params)
110-
current_page = int(resp.headers['current-page'])
111-
number_of_pages = int(resp.headers['number-of-pages'])
112-
total_records = int(resp.headers['total-records'])
113-
inventory = []
114-
for inv_item in resp.json()['data']:
115-
inventory.append(ProjectInventoryItem.from_dict(inv_item))
116-
117-
# Iterate through all the pages
118-
if number_of_pages > offset:
119-
params.update({"offset": offset+1})
120-
chunk = self.get_inventory_summary(project_id, **params)
121-
# Only append the inventory records
122-
inventory.extend(chunk)
123-
return inventory
118+
vulnerabilitySummary : bool = False,
119+
cvssVersion: str = 'ANY',
120+
published: str = 'ALL',
121+
offset:int = 1,
122+
limit:int = 25) -> List[ProjectInventoryItem]:
123+
"""
124+
Retrieves the inventory summary for a specific project.
125+
126+
Args:
127+
project_id (int): The ID of the project.
128+
vulnerabilitySummary (bool, optional): Flag to include vulnerability summary. Defaults to False.
129+
cvssVersion (str, optional): The CVSS version to filter vulnerabilities. Defaults to 'ANY'.
130+
published (str, optional): The publication status. Defaults to 'ALL'.
131+
offset (int, optional): The offset for pagination. Defaults to 1.
132+
limit (int, optional): The maximum number of items to return. Defaults to 25.
133+
134+
Returns:
135+
List[ProjectInventoryItem]: A list of ProjectInventoryItem objects representing the inventory summary.
136+
"""
137+
path = f"projects/{project_id}/inventorySummary"
138+
params = {"vulnerabilitySummary": vulnerabilitySummary,
139+
"cvssVersion": cvssVersion,
140+
"published": published,
141+
"offset": offset,
142+
"limit": limit
143+
}
144+
resp = self.client.request("GET", url_part=path, params=params)
145+
current_page = int(resp.headers['current-page'])
146+
number_of_pages = int(resp.headers['number-of-pages'])
147+
total_records = int(resp.headers['total-records'])
148+
inventory = []
149+
for inv_item in resp.json()['data']:
150+
inventory.append(ProjectInventoryItem.from_dict(inv_item))
151+
152+
# Iterate through all the pages
153+
if number_of_pages > offset:
154+
params.update({"offset": offset+1})
155+
chunk = self.get_inventory_summary(project_id, **params)
156+
# Only append the inventory records
157+
inventory.extend(chunk)
158+
return inventory
124159

125160
def get_inventory(self,project_id:int,
126161
skip_vulnerabilities: bool = False,
@@ -158,22 +193,53 @@ def get_inventory(self,project_id:int,
158193

159194

160195
class ReportHandler(Handler):
196+
"""
197+
A class that handles operations related to reports.
198+
199+
Args:
200+
client (Client): The client object used for making API requests.
201+
202+
Attributes:
203+
cls (Report): The class representing a report.
204+
205+
Methods:
206+
get(id): Retrieves a report by its ID.
207+
all(): Retrieves all reports.
208+
209+
"""
210+
161211
def __init__(self, client):
162212
super().__init__(client)
163213
self.cls = Report
164-
214+
165215
def get(self, id:int):
216+
"""
217+
Retrieves a report by its ID.
218+
219+
Args:
220+
id (int): The ID of the report to retrieve.
221+
222+
Returns:
223+
Report: The report object.
224+
225+
"""
166226
path = f"reports/{id}"
167227
resp = self.client.request("GET", url_part=path)
168228
report_data = resp.json()['data']
169229
report = self.cls.from_dict(report_data)
170230
return report
171-
231+
172232
def all(self):
233+
"""
234+
Retrieves all reports.
235+
236+
Returns:
237+
list: A list of report objects.
238+
239+
"""
173240
path = "reports"
174241
resp = self.client.request("GET", url_part=path)
175242
reports = []
176243
for report_data in resp.json()['data']:
177244
reports.append(self.cls.from_dict(report_data))
178245
return reports
179-

pylintrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[master]
2+
ignore-patterns=test_.*?py

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "codeinsight_sdk"
3-
version = "0.0.4"
3+
version = "0.0.5"
44
description = "A Python client for the Revenera Code Insight"
55
authors = ["Zachary Karpinski <1206496+zkarpinski@users.noreply.github.com>"]
66
readme = "README.md"

tests/test_client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ class TestProjectEndpoints:
3131
@pytest.fixture
3232
def client(self):
3333
return CodeInsightClient(TEST_URL, TEST_API_TOKEN)
34+
35+
def test_create_project(self, client):
36+
project_name = "Test"
37+
with requests_mock.Mocker() as m:
38+
m.post(f"{TEST_URL}/codeinsight/api/projects", text='{"data": {"id":1}}')
39+
project_id = client.projects.create(project_name)
40+
assert project_id == 1
3441

3542
def test_get_all_projects(self, client):
3643
with requests_mock.Mocker() as m:
@@ -163,6 +170,7 @@ def test_get_project_inventory_summary(self,client):
163170
assert len(project_inventory_summary) == 8
164171
assert project_inventory_summary[1].id == 12346
165172

173+
166174
class TestReportsEndpoints:
167175
@pytest.fixture
168176
def client(self):

0 commit comments

Comments
 (0)