Skip to content

Commit 799e95b

Browse files
authored
added endpoints to python sdk (#11)
* Added stream diff and export endpoints to sdk
1 parent 5a9e0a7 commit 799e95b

File tree

11 files changed

+312
-160
lines changed

11 files changed

+312
-160
lines changed

README.rst

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,64 @@ Retrieve the package information for a purl post
5252
- **license (str)** - The license parameter if enabled will show alerts and license information. If disabled will only show the basic package metadata and scores. Default is true
5353
- **components (array{dict})** - The components list of packages urls
5454

55+
export.cdx_bom(org_slug, id, query_params)
56+
"""""""""""""""""""""""""""""""""""""""""
57+
Export a Socket SBOM as a CycloneDX SBOM
58+
59+
**Usage:**
60+
61+
.. code-block::
62+
63+
from socketdev import socketdev
64+
from socketdev.export import ExportQueryParams
65+
66+
socket = socketdev(token="REPLACE_ME")
67+
query_params = ExportQueryParams(
68+
author="john_doe",
69+
project_name="my-project"
70+
)
71+
print(socket.export.cdx_bom("org_slug", "sbom_id", query_params))
72+
73+
**PARAMETERS:**
74+
75+
- **org_slug (str)** - The organization name
76+
- **id (str)** - The ID of either a full scan or an SBOM report
77+
- **query_params (ExportQueryParams)** - Optional query parameters for filtering:
78+
- **author (str)** - Filter by author
79+
- **project_group (str)** - Filter by project group
80+
- **project_name (str)** - Filter by project name
81+
- **project_version (str)** - Filter by project version
82+
- **project_id (str)** - Filter by project ID
83+
84+
export.spdx_bom(org_slug, id, query_params)
85+
""""""""""""""""""""""""""""""""""""""""""
86+
Export a Socket SBOM as an SPDX SBOM
87+
88+
**Usage:**
89+
90+
.. code-block::
91+
92+
from socketdev import socketdev
93+
from socketdev.export import ExportQueryParams
94+
95+
socket = socketdev(token="REPLACE_ME")
96+
query_params = ExportQueryParams(
97+
project_name="my-project",
98+
project_version="1.0.0"
99+
)
100+
print(socket.export.spdx_bom("org_slug", "sbom_id", query_params))
101+
102+
**PARAMETERS:**
103+
104+
- **org_slug (str)** - The organization name
105+
- **id (str)** - The ID of either a full scan or an SBOM report
106+
- **query_params (ExportQueryParams)** - Optional query parameters for filtering:
107+
- **author (str)** - Filter by author
108+
- **project_group (str)** - Filter by project group
109+
- **project_name (str)** - Filter by project name
110+
- **project_version (str)** - Filter by project version
111+
- **project_id (str)** - Filter by project ID
112+
55113
fullscans.get(org_slug)
56114
"""""""""""""""""""""""
57115
Retrieve the Fullscans information for around Organization
@@ -143,6 +201,25 @@ Delete an existing full scan.
143201
- **org_slug (str)** - The organization name
144202
- **full_scan_id (str)** - The ID of the full scan
145203

204+
fullscans.stream_diff(org_slug, before, after, preview)
205+
"""""""""""""""""""""""""""""""""""""""""""""""""""""""
206+
Stream a diff between two full scans. Returns a scan diff.
207+
208+
**Usage:**
209+
210+
.. code-block::
211+
212+
from socketdev import socketdev
213+
socket = socketdev(token="REPLACE_ME")
214+
print(socket.fullscans.stream_diff("org_slug", "before_scan_id", "after_scan_id"))
215+
216+
**PARAMETERS:**
217+
218+
- **org_slug (str)** - The organization name
219+
- **before (str)** - The base full scan ID
220+
- **after (str)** - The comparison full scan ID
221+
- **preview (bool)** - Create a diff-scan that is not persisted. Defaults to False
222+
146223
fullscans.stream(org_slug, full_scan_id)
147224
""""""""""""""""""""""""""""""""""""""""
148225
Stream all SBOM artifacts for a full scan.

pyproject.toml

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ classifiers = [
3030
"Programming Language :: Python :: 3.14"
3131
]
3232

33+
# modern, faster linter and language server. install with `pip install -e ".[dev]"`
34+
[project.optional-dependencies]
35+
dev = [
36+
"ruff>=0.3.0",
37+
]
38+
3339
[project.urls]
3440
Homepage = "https://github.com/socketdev/socket-sdk-python"
3541

@@ -53,4 +59,76 @@ include = [
5359
]
5460

5561
[tool.setuptools.dynamic]
56-
version = {attr = "socketdev.__version__"}
62+
version = {attr = "socketdev.__version__"}
63+
64+
[tool.ruff]
65+
# Exclude a variety of commonly ignored directories.
66+
exclude = [
67+
".bzr",
68+
".direnv",
69+
".eggs",
70+
".git",
71+
".git-rewrite",
72+
".hg",
73+
".ipynb_checkpoints",
74+
".mypy_cache",
75+
".nox",
76+
".pants.d",
77+
".pyenv",
78+
".pytest_cache",
79+
".pytype",
80+
".ruff_cache",
81+
".svn",
82+
".tox",
83+
".venv",
84+
".vscode",
85+
"__pypackages__",
86+
"_build",
87+
"buck-out",
88+
"build",
89+
"dist",
90+
"node_modules",
91+
"site-packages",
92+
"venv",
93+
]
94+
95+
[tool.ruff.lint]
96+
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
97+
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
98+
# McCabe complexity (`C901`) by default.
99+
select = ["E4", "E7", "E9", "F"]
100+
ignore = []
101+
102+
# Allow fix for all enabled rules (when `--fix`) is provided.
103+
fixable = ["ALL"]
104+
unfixable = []
105+
106+
# Allow unused variables when underscore-prefixed.
107+
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
108+
109+
[tool.ruff.format]
110+
# Like Black, use double quotes for strings.
111+
quote-style = "double"
112+
113+
# Like Black, indent with spaces, rather than tabs.
114+
indent-style = "space"
115+
116+
# Like Black, respect magic trailing commas.
117+
skip-magic-trailing-comma = false
118+
119+
# Like Black, automatically detect the appropriate line ending.
120+
line-ending = "auto"
121+
122+
# Enable auto-formatting of code examples in docstrings. Markdown,
123+
# reStructuredText code/literal blocks and doctests are all supported.
124+
#
125+
# This is currently disabled by default, but it is planned for this
126+
# to be opt-out in the future.
127+
docstring-code-format = false
128+
129+
# Set the line length limit used when formatting code snippets in
130+
# docstrings.
131+
#
132+
# This only has an effect when the `docstring-code-format` setting is
133+
# enabled.
134+
docstring-code-line-length = "dynamic"

socketdev/__init__.py

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,26 @@
22
import requests
33
import base64
44

5+
from socketdev.core.classes import Response
56
from socketdev.dependencies import Dependencies
7+
from socketdev.exceptions import APIKeyMissing, APIFailure, APIAccessDenied, APIInsufficientQuota, APIResourceNotFound
8+
from socketdev.export import Export
9+
from socketdev.fullscans import FullScans
610
from socketdev.npm import NPM
711
from socketdev.openapi import OpenAPI
812
from socketdev.org import Orgs
13+
from socketdev.purl import Purl
914
from socketdev.quota import Quota
1015
from socketdev.report import Report
11-
from socketdev.sbom import Sbom
12-
from socketdev.purl import Purl
13-
from socketdev.fullscans import FullScans
1416
from socketdev.repos import Repos
1517
from socketdev.repositories import Repositories
18+
from socketdev.sbom import Sbom
1619
from socketdev.settings import Settings
17-
from socketdev.core.classes import Response
18-
from socketdev.exceptions import APIKeyMissing, APIFailure, APIAccessDenied, APIInsufficientQuota, APIResourceNotFound
1920

2021

21-
__author__ = 'socket.dev'
22-
__version__ = '1.0.12'
23-
__all__ = [
24-
"socketdev"
25-
]
22+
__author__ = "socket.dev"
23+
__version__ = "1.0.13"
24+
__all__ = ["socketdev"]
2625

2726

2827
global encoded_key
@@ -36,15 +35,11 @@
3635

3736
def encode_key(token: str):
3837
global encoded_key
39-
encoded_key = base64.b64encode(token.encode()).decode('ascii')
38+
encoded_key = base64.b64encode(token.encode()).decode("ascii")
4039

4140

4241
def do_request(
43-
path: str,
44-
headers: dict = None,
45-
payload: [dict, str] = None,
46-
files: list = None,
47-
method: str = "GET"
42+
path: str, headers: dict = None, payload: [dict, str] = None, files: list = None, method: str = "GET"
4843
) -> Response:
4944
"""
5045
Shared function for performing the requests against the API.
@@ -60,19 +55,14 @@ def do_request(
6055

6156
if headers is None:
6257
headers = {
63-
'Authorization': f"Basic {encoded_key}",
64-
'User-Agent': f'SocketPythonScript/{__version__}',
65-
"accept": "application/json"
58+
"Authorization": f"Basic {encoded_key}",
59+
"User-Agent": f"SocketPythonScript/{__version__}",
60+
"accept": "application/json",
6661
}
6762
url = f"{api_url}/{path}"
6863
try:
6964
response = requests.request(
70-
method.upper(),
71-
url,
72-
headers=headers,
73-
data=payload,
74-
files=files,
75-
timeout=request_timeout
65+
method.upper(), url, headers=headers, data=payload, files=files, timeout=request_timeout
7666
)
7767
if response.status_code >= 400:
7868
raise APIFailure("Bad Request")
@@ -85,11 +75,7 @@ def do_request(
8575
elif response.status_code == 429:
8676
raise APIInsufficientQuota("Insufficient quota for API route")
8777
except Exception as error:
88-
response = Response(
89-
text=f"{error}",
90-
error=True,
91-
status_code=500
92-
)
78+
response = Response(text=f"{error}", error=True, status_code=500)
9379
raise APIFailure(response)
9480
return response
9581

@@ -104,8 +90,9 @@ class socketdev:
10490
quota: Quota
10591
report: Report
10692
sbom: Sbom
107-
purl: purl
93+
purl: Purl
10894
fullscans: FullScans
95+
export: Export
10996
repositories: Repositories
11097
settings: Settings
11198
repos: Repos

socketdev/export/__init__.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from urllib.parse import urlencode
2+
from dataclasses import dataclass, asdict
3+
from typing import Optional
4+
import socketdev
5+
6+
7+
@dataclass
8+
class ExportQueryParams:
9+
author: Optional[str] = None
10+
project_group: Optional[str] = None
11+
project_name: Optional[str] = None
12+
project_version: Optional[str] = None
13+
project_id: Optional[str] = None
14+
15+
def to_query_params(self) -> str:
16+
# Filter out None values and convert to query string
17+
params = {k: v for k, v in asdict(self).items() if v is not None}
18+
if not params:
19+
return ""
20+
return "?" + urlencode(params)
21+
22+
23+
class Export:
24+
@staticmethod
25+
def cdx_bom(org_slug: str, id: str, query_params: Optional[ExportQueryParams] = None) -> dict:
26+
"""
27+
Export a Socket SBOM as a CycloneDX SBOM
28+
:param org_slug: String - The slug of the organization
29+
:param id: String - The id of either a full scan or an sbom report
30+
:param query_params: Optional[ExportQueryParams] - Query parameters for filtering
31+
:return:
32+
"""
33+
path = f"orgs/{org_slug}/export/cdx/{id}"
34+
if query_params:
35+
path += query_params.to_query_params()
36+
result = socketdev.do_request(path=path)
37+
try:
38+
sbom = result.json()
39+
sbom["success"] = True
40+
except Exception as error:
41+
sbom = {"success": False, "message": str(error)}
42+
return sbom
43+
44+
@staticmethod
45+
def spdx_bom(org_slug: str, id: str, query_params: Optional[ExportQueryParams] = None) -> dict:
46+
"""
47+
Export a Socket SBOM as an SPDX SBOM
48+
:param org_slug: String - The slug of the organization
49+
:param id: String - The id of either a full scan or an sbom report
50+
:param query_params: Optional[ExportQueryParams] - Query parameters for filtering
51+
:return:
52+
"""
53+
path = f"orgs/{org_slug}/export/spdx/{id}"
54+
if query_params:
55+
path += query_params.to_query_params()
56+
result = socketdev.do_request(path=path)
57+
try:
58+
sbom = result.json()
59+
sbom["success"] = True
60+
except Exception as error:
61+
sbom = {"success": False, "message": str(error)}
62+
return sbom

0 commit comments

Comments
 (0)