Skip to content

Commit 5211c29

Browse files
authored
Merge pull request #11 from zkarpinski/experimental
Added inventory api support and experimental features
2 parents 6417dda + 69d4c35 commit 5211c29

File tree

13 files changed

+295
-100
lines changed

13 files changed

+295
-100
lines changed

codeinsight_sdk/client.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,22 @@
33

44
from .handlers import ProjectHandler, ReportHandler
55
from .exceptions import CodeInsightError
6+
from .experimental import ExperimentalHandler
7+
from .handlers.inventory import InventoryHandler
68

79
logger = logging.getLogger(__name__)
810

911
class CodeInsightClient:
12+
"""Client for the code insight API."""
13+
1014
def __init__(self,
1115
base_url: str,
1216
api_token: str,
1317
timeout: int = 60,
14-
verify_ssl: bool = True
18+
verify_ssl: bool = True,
19+
experimental: bool = False
1520
):
21+
1622
self.base_url = base_url
1723
self.api_url = f"{base_url}/codeinsight/api"
1824
self.__api_token = api_token
@@ -23,6 +29,7 @@ def __init__(self,
2329
}
2430
self.__timeout = timeout
2531
self.__verify_ssl = verify_ssl
32+
self.experimental_enabled = experimental
2633

2734
def request(self, method, url_part: str, params: dict = None, body: any = None, data: any = None, content_type: str = None):
2835
url = f"{self.api_url}/{url_part}"
@@ -57,10 +64,20 @@ def projects(self) -> ProjectHandler:
5764
def reports(self) -> ReportHandler:
5865
return ReportHandler(self)
5966

67+
@property
68+
def inventories(self):
69+
return InventoryHandler(self)
70+
71+
@property
72+
def experimental(self) -> ExperimentalHandler:
73+
if self.experimental_enabled == False:
74+
raise CodeInsightError("Experimental API is not enabled for this instance")
75+
else:
76+
return ExperimentalHandler(self)
77+
6078

6179
# Coming soon...?
62-
def inventories(self):
63-
raise NotImplementedError("Inventories are not yet implemented")
80+
6481

6582
def vulnerabilites(self):
6683
raise NotImplementedError

codeinsight_sdk/experimental.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from .handler import Handler
2+
from .models import ProjectInventoryItem
3+
4+
class ExperimentalHandler(Handler):
5+
def __init__(self, client):
6+
super().__init__(client)
7+
8+
def get(self):
9+
# Do nothing, there is no get for this handler
10+
pass
11+
12+
def get_project_vulnerabilities(self, project_id:int) -> list[ProjectInventoryItem]:
13+
"""
14+
Get all vulnerabilities for a project.
15+
16+
Args:
17+
project_id (int): The project id.
18+
19+
Returns:
20+
dict: The vulnerabilities.
21+
"""
22+
# First we get the inventory summary for the project with vulnerability summary
23+
# Then we iterate over the inventory items and calling the inventory vulnerability endpoint for each item with a vulnerability
24+
inventory = self.client.projects.get_inventory_summary(project_id, vulnerabilitySummary=True)
25+
26+
# Iterate over the inventory items, find which have vulnerabilities.
27+
item: ProjectInventoryItem
28+
vuln_items: list(ProjectInventoryItem) = []
29+
for item in inventory:
30+
if item.vulnerabilitySummary is None:
31+
continue
32+
33+
# If the item has a vulnerability, get the vulnerability details for this item and append it
34+
if sum(item.vulnerabilitySummary[0]['CvssV3'].values()) > 0:
35+
36+
vul_detail = self.client.inventories.get_inventory_vulnerabilities(item.id)
37+
item.vulnerabilities = vul_detail
38+
vuln_items.append(item)
39+
else:
40+
# If the item has no vulnerabilities, skip it
41+
continue
42+
43+
return vuln_items

codeinsight_sdk/handler.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import abc
2+
from typing import List
3+
4+
from .models import Project, ProjectInventory, ProjectInventoryItem, Report
5+
from .exceptions import CodeInsightError
6+
7+
class Handler(abc.ABC):
8+
def __init__(self, client):
9+
self.client = client
10+
self.cls = None
11+
12+
@abc.abstractmethod
13+
def get(self):
14+
pass
15+

codeinsight_sdk/handlers/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .inventory import InventoryHandler
2+
from .project import ProjectHandler
3+
from .report import ReportHandler

codeinsight_sdk/handlers/inventory.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from ..models import ProjectInventoryItem, Vulnerability
2+
from ..handler import Handler
3+
4+
class InventoryHandler(Handler):
5+
""" Handles operations related to inventories."""
6+
7+
def __init__(self, client):
8+
super().__init__(client)
9+
self.cls = ProjectInventoryItem
10+
11+
def get(self, inventoryId: int) -> list[ProjectInventoryItem]:
12+
"""
13+
Get an inventory item by id.
14+
15+
Args:
16+
inventoryId (int): The inventory item id.
17+
18+
Returns:
19+
ProjectInventoryItem: The inventory item.
20+
"""
21+
path = f"inventories/{inventoryId}"
22+
resp = self.client.request("GET", url_part=path)
23+
inventory = []
24+
for inv_item in resp.json()['data']:
25+
inventory.append(ProjectInventoryItem.from_dict(inv_item))
26+
return inventory
27+
28+
def get_inventory_vulnerabilities(self, inventoryId: int,
29+
limit: int = 25,
30+
offset: int = 1) -> list[Vulnerability]:
31+
"""
32+
Get all vulnerabilities for an inventory item.
33+
34+
Args:
35+
inventoryId (int): The inventory item id.
36+
37+
Returns:
38+
dict: The vulnerabilities.
39+
"""
40+
path = f"inventories/{inventoryId}/vulnerabilities"
41+
params = {"limit": limit, "offset": offset}
42+
resp = self.client.request("GET", url_part=path, params=params)
43+
44+
# TODO - Iterate pages
45+
46+
inventory_vuls: list(Vulnerability) = []
47+
for v in resp.json()['data']:
48+
inventory_vuls.append(Vulnerability.from_dict(v))
49+
50+
return inventory_vuls

codeinsight_sdk/handlers.py renamed to codeinsight_sdk/handlers/project.py

Lines changed: 3 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,9 @@
1-
import abc
21
from typing import List
32

4-
from codeinsight_sdk.models import Project, ProjectInventory, ProjectInventoryItem, Report
5-
from codeinsight_sdk.exceptions import CodeInsightError
3+
from ..models import Project, ProjectInventory, ProjectInventoryItem
4+
from ..handler import Handler
65

7-
class Handler(abc.ABC):
8-
def __init__(self, client):
9-
self.client = client
10-
self.cls = None
11-
12-
@staticmethod
13-
def create(client, cls):
14-
k = cls.__name__
15-
handlers = {"Project": ProjectHandler,
16-
"Report": ReportHandler
17-
}
18-
handler = handlers.get(k)
19-
if handler is None:
20-
raise ValueError(f"Handler not found for class '{k}'")
21-
return handler(client)
22-
23-
@abc.abstractmethod
24-
def get(self):
25-
pass
6+
from ..exceptions import CodeInsightError
267

278
class ProjectHandler(Handler):
289
def __init__(self, client):
@@ -208,55 +189,3 @@ def upload_codebase(self, project_id:int,
208189
content_type = "application/octet-stream"
209190
resp = self.client.request("POST", url_part=path, params=params, data=code_file,content_type=content_type)
210191
return resp.status_code
211-
212-
class ReportHandler(Handler):
213-
"""
214-
A class that handles operations related to reports.
215-
216-
Args:
217-
client (Client): The client object used for making API requests.
218-
219-
Attributes:
220-
cls (Report): The class representing a report.
221-
222-
Methods:
223-
get(id): Retrieves a report by its ID.
224-
all(): Retrieves all reports.
225-
226-
"""
227-
228-
def __init__(self, client):
229-
super().__init__(client)
230-
self.cls = Report
231-
232-
def get(self, id:int):
233-
"""
234-
Retrieves a report by its ID.
235-
236-
Args:
237-
id (int): The ID of the report to retrieve.
238-
239-
Returns:
240-
Report: The report object.
241-
242-
"""
243-
path = f"reports/{id}"
244-
resp = self.client.request("GET", url_part=path)
245-
report_data = resp.json()['data']
246-
report = self.cls.from_dict(report_data)
247-
return report
248-
249-
def all(self):
250-
"""
251-
Retrieves all reports.
252-
253-
Returns:
254-
list: A list of report objects.
255-
256-
"""
257-
path = "reports"
258-
resp = self.client.request("GET", url_part=path)
259-
reports = []
260-
for report_data in resp.json()['data']:
261-
reports.append(self.cls.from_dict(report_data))
262-
return reports

codeinsight_sdk/handlers/report.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from ..models import Report
2+
from ..handler import Handler
3+
4+
5+
class ReportHandler(Handler):
6+
"""
7+
A class that handles operations related to reports.
8+
9+
Args:
10+
client (Client): The client object used for making API requests.
11+
12+
Attributes:
13+
cls (Report): The class representing a report.
14+
15+
Methods:
16+
get(id): Retrieves a report by its ID.
17+
all(): Retrieves all reports.
18+
19+
"""
20+
21+
def __init__(self, client):
22+
super().__init__(client)
23+
self.cls = Report
24+
25+
def get(self, id:int):
26+
"""
27+
Retrieves a report by its ID.
28+
29+
Args:
30+
id (int): The ID of the report to retrieve.
31+
32+
Returns:
33+
Report: The report object.
34+
35+
"""
36+
path = f"reports/{id}"
37+
resp = self.client.request("GET", url_part=path)
38+
report_data = resp.json()['data']
39+
report = self.cls.from_dict(report_data)
40+
return report
41+
42+
def all(self):
43+
"""
44+
Retrieves all reports.
45+
46+
Returns:
47+
list: A list of report objects.
48+
49+
"""
50+
path = "reports"
51+
resp = self.client.request("GET", url_part=path)
52+
reports = []
53+
for report_data in resp.json()['data']:
54+
reports.append(self.cls.from_dict(report_data))
55+
return reports

codeinsight_sdk/models.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# pylint: disable=invalid-name
2+
# Disable invalid name because the API uses camelCase
3+
14
from dataclasses import dataclass
25
from dataclasses_json import DataClassJsonMixin, dataclass_json
36
from typing import Any, Optional, List, Dict
@@ -39,7 +42,7 @@ class ProjectInventoryItem(DataClassJsonMixin):
3942
componentUrl: Optional[str] = None
4043
componentDescription: Optional[str] = None
4144
vulnerabilities: Optional[List[Vulnerability]] = None
42-
vulnerabilitySummary: Optional[Dict[str, Dict]] = None
45+
vulnerabilitySummary: Optional[List[Dict[str, Dict]]] = None
4346
filePaths: Optional[List[str]] = None
4447

4548
@dataclass_json #Trying this style instead of DataClassJsonMixin

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.7"
3+
version = "0.0.8"
44
description = "A Python client for the Revenera Code Insight"
55
authors = ["Zachary Karpinski <[email protected]>"]
66
readme = "README.md"

tests/test_client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ def client(self):
2020

2121
def test_client(self, client):
2222
assert client.base_url == TEST_URL
23+
24+
def test_client_expertimental_disabled(self, client):
25+
assert client.experimental_enabled == False
2326

2427
def test_endpoint_not_found(self, client):
2528
with requests_mock.Mocker() as m:
@@ -168,7 +171,6 @@ def test_get_project_inventory_summary(self,client):
168171
"componentVersionName":"2.0"
169172
}
170173
]
171-
172174
}
173175
"""
174176
with requests_mock.Mocker() as m:

0 commit comments

Comments
 (0)