Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ dist
*.egg-info
*.cpython-312.pyc
example-socket-export.py
__pycache__/
__pycache__/
.coverage
.coverage.*
htmlcov/
12 changes: 8 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,19 @@ Supported Functions
-------------------


purl.post(license, components)
""""""""""""""""""""""""""""""
Retrieve the package information for a purl post
purl.post(license, components, org_slug=None)
"""""""""""""""""""""""""""""""""""""""""""""
Retrieve package information for one or more PURLs. Pass ``org_slug`` to use the
current org-scoped endpoint. Omitting ``org_slug`` keeps the legacy deprecated
endpoint for backwards compatibility.

**Usage:**

.. code-block:: python

from socketdev import socketdev
socket = socketdev(token="REPLACE_ME")
org_slug = "your-org-slug"
license = "true"
components = [
{
Expand All @@ -47,12 +50,13 @@ Retrieve the package information for a purl post
"purl": "pkg:pypi/socketsecurity"
}
]
print(socket.purl.post(license, components))
print(socket.purl.post(license, components, org_slug=org_slug))

**PARAMETERS:**

- **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
- **components (array{dict})** - The components list of packages urls
- **org_slug (str, optional)** - Organization slug for the supported org-scoped PURL endpoint. If omitted, the SDK uses the deprecated legacy endpoint for backwards compatibility.

export.cdx_bom(org_slug, id, query_params)
""""""""""""""""""""""""""""""""""""""""""
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "socketdev"
version = "3.0.32"
version = "3.1.0"
requires-python = ">= 3.9"
dependencies = [
'requests',
Expand Down Expand Up @@ -50,7 +50,7 @@ test = [
]

[project.urls]
Homepage = "https://github.com/socketdev/socketdev"
Homepage = "https://github.com/SocketDev/socket-sdk-python"

[tool.ruff]
# Exclude a variety of commonly ignored directories.
Expand Down
18 changes: 16 additions & 2 deletions socketdev/purl/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import urllib.parse
import warnings
from socketdev.log import log
from ..core.dedupe import Dedupe

Expand All @@ -8,8 +9,21 @@ class Purl:
def __init__(self, api):
self.api = api

def post(self, license: str = "false", components: list = None, **kwargs) -> list:
path = "purl?"
def post(
self,
license: str = "false",
components: list = None,
org_slug: str = None,
**kwargs,
) -> list:
if org_slug is None:
warnings.warn(
"Calling purl.post() without org_slug uses the deprecated POST /v0/purl endpoint. "
"Pass org_slug to migrate to POST /v0/orgs/{org_slug}/purl.",
DeprecationWarning,
stacklevel=2,
)
path = f"orgs/{org_slug}/purl?" if org_slug else "purl?"
if components is None:
components = []
purls = {"components": components}
Expand Down
2 changes: 1 addition & 1 deletion socketdev/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "3.0.32"
__version__ = "3.1.0"
24 changes: 14 additions & 10 deletions tests/integration/test_all_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,13 @@ def test_historical_trend_mocked(self):
def test_npm_issues_mocked(self):
"""Test npm issues endpoint."""
self._mock_success_response([{"type": "security", "severity": "high"}])
result = self.sdk.npm.issues("lodash", "4.17.21")
result = self.sdk.npm.issues("lodash", "4.18.1")
self.assertIsInstance(result, list)

def test_npm_score_mocked(self):
"""Test npm score endpoint."""
self._mock_success_response([{"category": "security", "value": 85}])
result = self.sdk.npm.score("lodash", "4.17.21")
result = self.sdk.npm.score("lodash", "4.18.1")
self.assertIsInstance(result, list)

# OpenAPI endpoints
Expand All @@ -206,9 +206,13 @@ def test_org_get_mocked(self):

# PURL endpoints
def test_purl_post_mocked(self):
"""Test purl post endpoint."""
self._mock_success_response([{"purl": "pkg:npm/lodash@4.17.21", "valid": True}])
result = self.sdk.purl.post("false", [{"purl": "pkg:npm/lodash@4.17.21"}])
"""Test org-scoped purl post endpoint."""
mock_response = Mock()
mock_response.status_code = 200
mock_response.headers = {'content-type': 'application/x-ndjson'}
mock_response.text = '{"inputPurl": "pkg:npm/lodash@4.18.1", "purl": "pkg:npm/lodash@4.18.1", "type": "npm", "name": "lodash", "version": "4.18.1", "valid": true, "alerts": []}'
self.mock_requests.request.return_value = mock_response
result = self.sdk.purl.post("false", [{"purl": "pkg:npm/lodash@4.18.1"}], org_slug="test-org")
self.assertIsInstance(result, list)

# Quota endpoints
Expand Down Expand Up @@ -372,7 +376,7 @@ def setUpClass(cls):
test_package = {
"name": "test-integration-package",
"version": "1.0.0",
"dependencies": {"lodash": "4.17.21"}
"dependencies": {"lodash": "4.18.1"}
}
with open(cls.package_json_path, 'w') as f:
json.dump(test_package, f, indent=2)
Expand Down Expand Up @@ -414,20 +418,20 @@ def test_openapi_get_integration(self):
# NPM endpoints (should work for public packages)
def test_npm_issues_integration(self):
"""Test npm issues endpoint."""
result = self._try_endpoint(self.sdk.npm.issues, "lodash", "4.17.21")
result = self._try_endpoint(self.sdk.npm.issues, "lodash", "4.18.1")
if result:
self.assertIsInstance(result, list)

def test_npm_score_integration(self):
"""Test npm score endpoint."""
result = self._try_endpoint(self.sdk.npm.score, "lodash", "4.17.21")
result = self._try_endpoint(self.sdk.npm.score, "lodash", "4.18.1")
if result:
self.assertIsInstance(result, list)

# PURL endpoints
def test_purl_post_integration(self):
"""Test purl post endpoint."""
components = [{"purl": "pkg:npm/lodash@4.17.21"}]
components = [{"purl": "pkg:npm/lodash@4.18.1"}]
result = self._try_endpoint(self.sdk.purl.post, "false", components)
if result:
self.assertIsInstance(result, list)
Expand Down Expand Up @@ -515,7 +519,7 @@ def test_dependencies_get_integration(self):
"""Test dependencies get endpoint."""
result = self._try_endpoint(
self.sdk.dependencies.get,
self.org_slug, "npm", "lodash", "4.17.21"
self.org_slug, "npm", "lodash", "4.18.1"
)
if result:
self.assertIsInstance(result, dict)
Expand Down
14 changes: 9 additions & 5 deletions tests/integration/test_comprehensive_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def setUpClass(cls):
"name": "test-integration-project",
"version": "1.0.0",
"dependencies": {
"lodash": "4.17.21"
"lodash": "4.18.1"
}
}

Expand Down Expand Up @@ -264,7 +264,7 @@ def test_npm_endpoints(self):
"""Test NPM-related endpoints."""
# Test getting package issues - this should work for most packages
try:
issues = self.sdk.npm.issues("lodash", "4.17.21")
issues = self.sdk.npm.issues("lodash", "4.18.1")
self.assertIsInstance(issues, dict)
except Exception as e:
print(f"NPM issues endpoint not available: {e}")
Expand All @@ -281,9 +281,13 @@ def test_purl_endpoint(self):
"""Test PURL (Package URL) functionality."""
try:
# Test with a common npm package
purl = "pkg:npm/lodash@4.17.21"
result = self.sdk.purl.post([purl])
self.assertIsInstance(result, dict)
purl = "pkg:npm/lodash@4.18.1"
result = self.sdk.purl.post(
license="false",
components=[{"purl": purl}],
org_slug=self.org_slug,
)
self.assertIsInstance(result, list)
except Exception as e:
print(f"PURL endpoint not available: {e}")

Expand Down
47 changes: 32 additions & 15 deletions tests/unit/test_all_endpoints_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ def _mock_response(self, data=None, status_code=200):
# Dependencies endpoints
def test_dependencies_post_unit(self):
"""Test dependencies post with proper file handling."""
expected_data = {"packages": [{"name": "lodash", "version": "4.17.21"}]}
expected_data = {"packages": [{"name": "lodash", "version": "4.18.1"}]}
self._mock_response(expected_data)

with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
json.dump({"name": "test-package", "dependencies": {"lodash": "4.17.21"}}, f)
json.dump({"name": "test-package", "dependencies": {"lodash": "4.18.1"}}, f)
f.flush()

try:
Expand All @@ -72,12 +72,12 @@ def test_dependencies_get_unit(self):
expected_data = {"dependencies": [{"name": "sub-dependency", "version": "1.0.0"}]}
self._mock_response(expected_data)

result = self.sdk.dependencies.get("test-org", "npm", "lodash", "4.17.21")
result = self.sdk.dependencies.get("test-org", "npm", "lodash", "4.18.1")

self.assertEqual(result, expected_data)
call_args = self.mock_requests.request.call_args
self.assertEqual(call_args[0][0], "GET")
self.assertIn("/orgs/test-org/dependencies/npm/lodash/4.17.21", call_args[0][1])
self.assertIn("/orgs/test-org/dependencies/npm/lodash/4.18.1", call_args[0][1])

# DiffScans endpoints
def test_diffscans_list_unit(self):
Expand Down Expand Up @@ -305,24 +305,24 @@ def test_npm_issues_unit(self):
expected_data = [{"type": "security", "severity": "high", "title": "Test issue"}]
self._mock_response(expected_data)

result = self.sdk.npm.issues("lodash", "4.17.21")
result = self.sdk.npm.issues("lodash", "4.18.1")

self.assertEqual(result, expected_data)
call_args = self.mock_requests.request.call_args
self.assertEqual(call_args[0][0], "GET")
self.assertIn("/npm/lodash/4.17.21/issues", call_args[0][1])
self.assertIn("/npm/lodash/4.18.1/issues", call_args[0][1])

def test_npm_score_unit(self):
"""Test npm score endpoint."""
expected_data = [{"category": "security", "value": 85}]
self._mock_response(expected_data)

result = self.sdk.npm.score("lodash", "4.17.21")
result = self.sdk.npm.score("lodash", "4.18.1")

self.assertEqual(result, expected_data)
call_args = self.mock_requests.request.call_args
self.assertEqual(call_args[0][0], "GET")
self.assertIn("/npm/lodash/4.17.21/score", call_args[0][1])
self.assertIn("/npm/lodash/4.18.1/score", call_args[0][1])

# OpenAPI endpoints
def test_openapi_get_unit(self):
Expand Down Expand Up @@ -352,22 +352,22 @@ def test_org_get_unit(self):

# PURL endpoints
def test_purl_post_unit(self):
"""Test PURL validation endpoint."""
"""Test org-scoped PURL validation endpoint."""
# Expected final result after deduplication - should match what the dedupe function produces
expected_data = [{
"inputPurl": "pkg:npm/lodash@4.17.21",
"purl": "pkg:npm/lodash@4.17.21",
"inputPurl": "pkg:npm/lodash@4.18.1",
"purl": "pkg:npm/lodash@4.18.1",
"type": "npm",
"name": "lodash",
"version": "4.17.21",
"version": "4.18.1",
"valid": True,
"alerts": [],
"releases": ["npm"]
}]

# Mock the NDJSON response that would come from the actual API
# This simulates what the API returns: newline-delimited JSON with SocketArtifact objects
mock_ndjson_response = '{"inputPurl": "pkg:npm/lodash@4.17.21", "purl": "pkg:npm/lodash@4.17.21", "type": "npm", "name": "lodash", "version": "4.17.21", "valid": true, "alerts": []}'
mock_ndjson_response = '{"inputPurl": "pkg:npm/lodash@4.18.1", "purl": "pkg:npm/lodash@4.18.1", "type": "npm", "name": "lodash", "version": "4.18.1", "valid": true, "alerts": []}'

# Mock the response with NDJSON format
mock_response = Mock()
Expand All @@ -376,13 +376,30 @@ def test_purl_post_unit(self):
mock_response.text = mock_ndjson_response
self.mock_requests.request.return_value = mock_response

components = [{"purl": "pkg:npm/lodash@4.17.21"}]
result = self.sdk.purl.post("false", components)
components = [{"purl": "pkg:npm/lodash@4.18.1"}]
result = self.sdk.purl.post("false", components, org_slug="test-org")

self.assertEqual(result, expected_data)
call_args = self.mock_requests.request.call_args
self.assertEqual(call_args[0][0], "POST")
self.assertIn("/orgs/test-org/purl", call_args[0][1])

def test_purl_post_unit_legacy_path(self):
"""Test legacy PURL validation endpoint remains available for compatibility."""
mock_ndjson_response = '{"inputPurl": "pkg:npm/lodash@4.18.1", "purl": "pkg:npm/lodash@4.18.1", "type": "npm", "name": "lodash", "version": "4.18.1", "valid": true, "alerts": []}'

mock_response = Mock()
mock_response.status_code = 200
mock_response.headers = {'content-type': 'application/x-ndjson'}
mock_response.text = mock_ndjson_response
self.mock_requests.request.return_value = mock_response

self.sdk.purl.post("false", [{"purl": "pkg:npm/lodash@4.18.1"}])

call_args = self.mock_requests.request.call_args
self.assertEqual(call_args[0][0], "POST")
self.assertIn("/purl", call_args[0][1])
self.assertNotIn("/orgs/", call_args[0][1])

# Quota endpoints
def test_quota_get_unit(self):
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/test_socket_sdk_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,13 @@ def test_socket_purl_creation(self):
type=SocketPURL_Type.NPM,
name="lodash",
namespace=None,
release="4.17.21"
release="4.18.1"
)

self.assertEqual(purl.type, SocketPURL_Type.NPM)
self.assertEqual(purl.name, "lodash")
self.assertIsNone(purl.namespace)
self.assertEqual(purl.release, "4.17.21")
self.assertEqual(purl.release, "4.18.1")

def test_integration_types(self):
"""Test that all integration types are available."""
Expand Down Expand Up @@ -223,7 +223,7 @@ def setUp(self):
"name": "test-package",
"version": "1.0.0",
"dependencies": {
"lodash": "4.17.21"
"lodash": "4.18.1"
}
}

Expand Down
8 changes: 4 additions & 4 deletions tests/unit/test_working_endpoints_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,24 @@ def test_npm_issues_unit(self):
expected_data = [{"type": "security", "severity": "high"}]
self._mock_response(expected_data)

result = self.sdk.npm.issues("lodash", "4.17.21")
result = self.sdk.npm.issues("lodash", "4.18.1")

self.assertEqual(result, expected_data)
call_args = self.mock_requests.request.call_args
self.assertEqual(call_args[0][0], "GET")
self.assertIn("/npm/lodash/4.17.21/issues", call_args[0][1])
self.assertIn("/npm/lodash/4.18.1/issues", call_args[0][1])

def test_npm_score_unit(self):
"""Test NPM score endpoint - WORKING."""
expected_data = [{"category": "security", "value": 85}]
self._mock_response(expected_data)

result = self.sdk.npm.score("lodash", "4.17.21")
result = self.sdk.npm.score("lodash", "4.18.1")

self.assertEqual(result, expected_data)
call_args = self.mock_requests.request.call_args
self.assertEqual(call_args[0][0], "GET")
self.assertIn("/npm/lodash/4.17.21/score", call_args[0][1])
self.assertIn("/npm/lodash/4.18.1/score", call_args[0][1])

def test_openapi_get_unit(self):
"""Test OpenAPI specification retrieval - WORKING."""
Expand Down
Loading