From 9e99f8fc3471471bbef113209694819a968a911e Mon Sep 17 00:00:00 2001 From: Martin Torp Date: Mon, 5 Jan 2026 10:55:46 +0100 Subject: [PATCH 01/20] Add ScanType enum with values 'socket', 'socket_tier1', and 'socket_basics' to support different scan types when creating full scans. The scan_type parameter is passed as a query parameter in the fullscans.post and fullscans.archive methods. --- README.rst | 2 ++ pyproject.toml | 2 +- socketdev/fullscans/__init__.py | 9 +++++++++ socketdev/version.py | 2 +- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index fa40940..2e936da 100644 --- a/README.rst +++ b/README.rst @@ -227,6 +227,8 @@ Create a full scan from a set of package manifest files. Returns a full scan inc +------------------------+------------+-------------------------------------------------------------------------------+ | integration_org_slug | False | Organization slug for integration | +------------------------+------------+-------------------------------------------------------------------------------+ +| scan_type | False | ScanType enum value: "socket", "socket_tier1", or "socket_basics" | ++------------------------+------------+-------------------------------------------------------------------------------+ fullscans.delete(org_slug, full_scan_id) """""""""""""""""""""""""""""""""""""""" diff --git a/pyproject.toml b/pyproject.toml index dc74c1f..0501a6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.0.26" +version = "3.0.27" requires-python = ">= 3.9" dependencies = [ 'requests', diff --git a/socketdev/fullscans/__init__.py b/socketdev/fullscans/__init__.py index 0cc7023..9b551b0 100644 --- a/socketdev/fullscans/__init__.py +++ b/socketdev/fullscans/__init__.py @@ -41,6 +41,12 @@ class DiffType(str, Enum): UPDATED = "updated" +class ScanType(str, Enum): + SOCKET = "socket" + SOCKET_TIER1 = "socket_tier1" + SOCKET_BASICS = "socket_basics" + + @dataclass(kw_only=True) class SocketPURL: type: SocketPURL_Type @@ -99,6 +105,7 @@ class FullScanParams: make_default_branch: Optional[bool] = None set_as_pending_head: Optional[bool] = None tmp: Optional[bool] = None + scan_type: Optional[ScanType] = None def __getitem__(self, key): return getattr(self, key) @@ -109,6 +116,7 @@ def to_dict(self): @classmethod def from_dict(cls, data: dict) -> "FullScanParams": integration_type = data.get("integration_type") + scan_type = data.get("scan_type") return cls( repo=data["repo"], org_slug=data.get("org_slug"), @@ -122,6 +130,7 @@ def from_dict(cls, data: dict) -> "FullScanParams": make_default_branch=data.get("make_default_branch"), set_as_pending_head=data.get("set_as_pending_head"), tmp=data.get("tmp"), + scan_type=ScanType(scan_type) if scan_type is not None else None, ) diff --git a/socketdev/version.py b/socketdev/version.py index 5c346b3..9d0e79c 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.0.26" +__version__ = "3.0.27" From f29af4f21c9c046d2cca120129c397f8b120586a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:50:42 -0800 Subject: [PATCH 02/20] Bump urllib3 from 2.6.2 to 2.6.3 (#67) Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.2 to 2.6.3. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.6.2...2.6.3) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/uv.lock b/uv.lock index 3f244df..e9eecd5 100644 --- a/uv.lock +++ b/uv.lock @@ -1343,7 +1343,7 @@ wheels = [ [[package]] name = "socketdev" -version = "3.0.23" +version = "3.0.29" source = { editable = "." } dependencies = [ { name = "requests" }, @@ -1486,11 +1486,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.2" +version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]] From 9f8266cb5cd6b9ced75213218d8800fd5f18f13b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:51:18 -0800 Subject: [PATCH 03/20] Bump virtualenv from 20.35.4 to 20.36.1 (#66) Bumps [virtualenv](https://github.com/pypa/virtualenv) from 20.35.4 to 20.36.1. - [Release notes](https://github.com/pypa/virtualenv/releases) - [Changelog](https://github.com/pypa/virtualenv/blob/main/docs/changelog.rst) - [Commits](https://github.com/pypa/virtualenv/compare/20.35.4...20.36.1) --- updated-dependencies: - dependency-name: virtualenv dependency-version: 20.36.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index e9eecd5..521748c 100644 --- a/uv.lock +++ b/uv.lock @@ -1534,7 +1534,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "20.35.4" +version = "20.36.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -1544,9 +1544,9 @@ dependencies = [ { name = "platformdirs", version = "4.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba", size = 6032239, upload-time = "2026-01-09T18:21:01.296Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" }, + { url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" }, ] [[package]] From 7e0ee5675fa9fe6b61866ce30d1ced65f5a81c1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:53:04 -0800 Subject: [PATCH 04/20] Bump cryptography from 46.0.3 to 46.0.5 (#69) Bumps [cryptography](https://github.com/pyca/cryptography) from 46.0.3 to 46.0.5. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/46.0.3...46.0.5) --- updated-dependencies: - dependency-name: cryptography dependency-version: 46.0.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 80 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/uv.lock b/uv.lock index 521748c..b72135f 100644 --- a/uv.lock +++ b/uv.lock @@ -593,51 +593,51 @@ toml = [ [[package]] name = "cryptography" -version = "46.0.3" +version = "46.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, - { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, - { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, - { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, - { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, - { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, - { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, - { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, - { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, - { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, - { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, - { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, - { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, - { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, - { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, - { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, - { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, - { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, - { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, - { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, - { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" }, - { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" }, - { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" }, - { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" }, + { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" }, + { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" }, + { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" }, + { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, + { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, + { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, + { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, + { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, + { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/e9/6f/6cc6cc9955caa6eaf83660b0da2b077c7fe8ff9950a3c5e45d605038d439/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a", size = 4218321, upload-time = "2026-02-10T19:18:22.349Z" }, + { url = "https://files.pythonhosted.org/packages/3e/5d/c4da701939eeee699566a6c1367427ab91a8b7088cc2328c09dbee940415/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356", size = 4381786, upload-time = "2026-02-10T19:18:24.529Z" }, + { url = "https://files.pythonhosted.org/packages/ac/97/a538654732974a94ff96c1db621fa464f455c02d4bb7d2652f4edc21d600/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da", size = 4217990, upload-time = "2026-02-10T19:18:25.957Z" }, + { url = "https://files.pythonhosted.org/packages/ae/11/7e500d2dd3ba891197b9efd2da5454b74336d64a7cc419aa7327ab74e5f6/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257", size = 4381252, upload-time = "2026-02-10T19:18:27.496Z" }, ] [[package]] From ec8940abb17b2847c4a178f1563f3d0eef4d79b5 Mon Sep 17 00:00:00 2001 From: lelia Date: Thu, 26 Feb 2026 11:38:25 -0500 Subject: [PATCH 05/20] Add `workspace` param support (#68) * Add workspace to FullScanParams for API support Signed-off-by: lelia * Add test to verify that workspace is included in query string on FullScanParams Signed-off-by: lelia * Update README to document workspace parameter Signed-off-by: lelia * Increment version number Signed-off-by: lelia * Update tests to use generic data Signed-off-by: lelia * Bump version again Signed-off-by: lelia * Update pyproject classifiers Signed-off-by: lelia * Pin python and virutalenv versions for workflows Signed-off-by: lelia --------- Signed-off-by: lelia --- .github/workflows/pr-preview.yml | 3 ++- .github/workflows/release.yml | 3 ++- README.rst | 3 +++ pyproject.toml | 3 +-- socketdev/fullscans/__init__.py | 2 ++ socketdev/version.py | 2 +- tests/unit/test_working_endpoints_unit.py | 32 +++++++++++++++++++++++ 7 files changed, 43 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index 12b38da..a8eaa9e 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -16,12 +16,13 @@ jobs: fetch-depth: 0 - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: - python-version: '3.x' + python-version: '3.13' # Install all dependencies from pyproject.toml - name: Install dependencies run: | python -m pip install --upgrade pip + pip install "virtualenv<20.36" pip install hatchling==1.27.0 pip install hatch==1.14.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4fa6c1b..d1a1eab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,12 +15,13 @@ jobs: fetch-depth: 0 - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: - python-version: '3.x' + python-version: '3.13' # Install all dependencies from pyproject.toml - name: Install dependencies run: | python -m pip install --upgrade pip + pip install "virtualenv<20.36" pip install hatchling==1.27.0 pip install hatch==1.14.0 diff --git a/README.rst b/README.rst index 2e936da..2ed1265 100644 --- a/README.rst +++ b/README.rst @@ -184,6 +184,7 @@ Create a full scan from a set of package manifest files. Returns a full scan inc params = FullScanParams( org_slug="org_name", repo="TestRepo", + workspace="my-workspace", branch="main", commit_message="Test Commit Message", commit_hash="abc123def456", @@ -223,6 +224,8 @@ Create a full scan from a set of package manifest files. Returns a full scan inc +------------------------+------------+-------------------------------------------------------------------------------+ | tmp | False | Boolean temporary flag | +------------------------+------------+-------------------------------------------------------------------------------+ +| workspace | False | The workspace of the repository to associate the full-scan with. | ++------------------------+------------+-------------------------------------------------------------------------------+ | integration_type | False | IntegrationType enum value (e.g., "api", "github") | +------------------------+------------+-------------------------------------------------------------------------------+ | integration_org_slug | False | Organization slug for integration | diff --git a/pyproject.toml b/pyproject.toml index eba5e0a..9576dae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.0.29" +version = "3.0.31" requires-python = ">= 3.9" dependencies = [ 'requests', @@ -23,7 +23,6 @@ maintainers = [ classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/socketdev/fullscans/__init__.py b/socketdev/fullscans/__init__.py index 03ea309..2e56e4d 100644 --- a/socketdev/fullscans/__init__.py +++ b/socketdev/fullscans/__init__.py @@ -106,6 +106,7 @@ class FullScanParams: set_as_pending_head: Optional[bool] = None tmp: Optional[bool] = None scan_type: Optional[ScanType] = None + workspace: Optional[str] = None def __getitem__(self, key): return getattr(self, key) @@ -131,6 +132,7 @@ def from_dict(cls, data: dict) -> "FullScanParams": set_as_pending_head=data.get("set_as_pending_head"), tmp=data.get("tmp"), scan_type=ScanType(scan_type) if scan_type is not None else None, + workspace=data.get("workspace"), ) diff --git a/socketdev/version.py b/socketdev/version.py index e0e9ceb..cf67ab3 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.0.29" +__version__ = "3.0.31" diff --git a/tests/unit/test_working_endpoints_unit.py b/tests/unit/test_working_endpoints_unit.py index e9d9d50..33a69bb 100644 --- a/tests/unit/test_working_endpoints_unit.py +++ b/tests/unit/test_working_endpoints_unit.py @@ -203,6 +203,38 @@ def test_fullscans_post_unit(self): finally: os.unlink(f.name) + def test_fullscans_post_with_workspace_unit(self): + """Test that workspace is included in the query string when set on FullScanParams.""" + expected_data = {"id": "new-scan"} + self._mock_response(expected_data, 201) + + params = FullScanParams( + repo="test-repo", + org_slug="test-org", + branch="main", + workspace="test-workspace", + ) + + with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: + json.dump({"name": "test", "version": "1.0.0"}, f) + f.flush() + + try: + with open(f.name, "rb") as file_obj: + files = [("file", ("package.json", file_obj))] + result = self.sdk.fullscans.post(files, params) + + self.assertEqual(result, expected_data) + call_args = self.mock_requests.request.call_args + self.assertEqual(call_args[0][0], "POST") + # Confirm workspace landed in the request URL query string + request_url = call_args[0][1] + self.assertIn("workspace=test-workspace", request_url) + self.assertIn("repo=test-repo", request_url) + + finally: + os.unlink(f.name) + def test_triage_list_alert_triage_unit(self): """Test triage list alerts - WORKING.""" expected_data = {"alerts": []} From 821a777fe177ad39a896342d33c327397d0e6eb8 Mon Sep 17 00:00:00 2001 From: lelia Date: Thu, 26 Feb 2026 11:43:20 -0500 Subject: [PATCH 06/20] Update CODEOWNERS to reflect team ownership (#70) Signed-off-by: lelia --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8d14cf0..edd0e77 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @socketdev/eng +* @SocketDev/customer-engineering \ No newline at end of file From ed38c65fc205874cdfaa70dc63cb8284bca3d7a8 Mon Sep 17 00:00:00 2001 From: Douglas Date: Fri, 27 Feb 2026 09:58:48 -0800 Subject: [PATCH 07/20] Fixing issue where incorrect workspace was being set as None (#71) --- pyproject.toml | 2 +- socketdev/fullscans/__init__.py | 5 +++++ socketdev/version.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9576dae..262ca14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.0.31" +version = "3.0.32" requires-python = ">= 3.9" dependencies = [ 'requests', diff --git a/socketdev/fullscans/__init__.py b/socketdev/fullscans/__init__.py index 2e56e4d..a2eb4f2 100644 --- a/socketdev/fullscans/__init__.py +++ b/socketdev/fullscans/__init__.py @@ -808,6 +808,11 @@ def post( ): print("Removing pull_request param from FullScanParams as it is None, 0, or not an integer") params_dict.pop("pull_request") + + if hasattr(params, 'workspace') and params.workspace is None: + print("Removing workspace param from FullScanParams as it is None") + params_dict.pop("workspace") + params_arg = urllib.parse.urlencode(params_dict) path = "orgs/" + org_slug + "/full-scans?" + str(params_arg) diff --git a/socketdev/version.py b/socketdev/version.py index cf67ab3..b70a418 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.0.31" +__version__ = "3.0.32" From b3a8d0c0ca54d7f526854891a77d6c7c1a0ea4a0 Mon Sep 17 00:00:00 2001 From: Ahmad Nassri Date: Fri, 20 Mar 2026 09:16:22 -0400 Subject: [PATCH 08/20] add context7.json --- context7.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 context7.json diff --git a/context7.json b/context7.json new file mode 100644 index 0000000..39e12bc --- /dev/null +++ b/context7.json @@ -0,0 +1,4 @@ +{ + "url": "https://context7.com/socketdev/socket-sdk-python", + "public_key": "pk_9HRbh6e3q2AL9xuPAiJYT" +} From c8efa8fc57c65ab20f1858a5205e33dee5f2a66b Mon Sep 17 00:00:00 2001 From: Ryan Eberhardt Date: Wed, 25 Mar 2026 08:56:26 -0700 Subject: [PATCH 09/20] fix: harden GitHub Actions workflows (zizmor) (#72) - Fix template injection vulnerabilities in release.yml by using environment variables instead of direct interpolation of github.ref_name - Upgrade actions/checkout to v6.0.2 (pinned to SHA) across all workflows - Add persist-credentials: false to all checkout steps - Add top-level permissions block to version-check.yml Co-authored-by: Claude Opus 4.6 (1M context) --- .github/workflows/pr-preview.yml | 3 ++- .github/workflows/release.yml | 9 ++++++--- .github/workflows/version-check.yml | 8 +++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index a8eaa9e..6701058 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -11,9 +11,10 @@ jobs: contents: read pull-requests: write steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 + persist-credentials: false - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: python-version: '3.13' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d1a1eab..acc8981 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,9 +10,10 @@ jobs: id-token: write contents: read steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 + persist-credentials: false - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: python-version: '3.13' @@ -27,11 +28,13 @@ jobs: - name: Get Version id: version + env: + REF_NAME: ${{ github.ref_name }} run: | RAW_VERSION=$(hatch version) echo "VERSION=$RAW_VERSION" >> $GITHUB_ENV - if [ "v$RAW_VERSION" != "${{ github.ref_name }}" ]; then - echo "Error: Git tag (${{ github.ref_name }}) does not match hatch version (v$RAW_VERSION)" + if [ "v$RAW_VERSION" != "$REF_NAME" ]; then + echo "Error: Git tag ($REF_NAME) does not match hatch version (v$RAW_VERSION)" exit 1 fi diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml index 8fa9203..40b9536 100644 --- a/.github/workflows/version-check.yml +++ b/.github/workflows/version-check.yml @@ -7,13 +7,19 @@ on: - 'setup.py' - 'pyproject.toml' +permissions: + contents: read + pull-requests: write + issues: write + jobs: check_version: runs-on: ubuntu-latest steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # Fetch all history for all branches + persist-credentials: false - name: Check version increment id: version_check From 065407a900dc8bc866b0c8b458e4d201d7f402f0 Mon Sep 17 00:00:00 2001 From: David Larsen Date: Fri, 24 Apr 2026 13:00:34 -0400 Subject: [PATCH 10/20] fix: tolerate unknown SocketCategory values in SocketAlert.from_dict (#79) * fix: tolerate unknown SocketCategory values in SocketAlert.from_dict The Socket API can emit category values the SDK does not yet know about (e.g. "other"). Strict enum construction in SocketAlert.from_dict turned that into a hard failure that propagated up through stream_diff and crashed any consumer that happened to receive such an alert. Fall back to SocketCategory.MISCELLANEOUS and log a warning when the value is unrecognized, so the SDK stays forward-compatible with new server-side categories without needing a coordinated release. Fixes #78. * chore: bump version to 3.0.33 --- pyproject.toml | 2 +- socketdev/fullscans/__init__.py | 11 ++++- socketdev/version.py | 2 +- tests/unit/test_socket_alert_category.py | 63 ++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 tests/unit/test_socket_alert_category.py diff --git a/pyproject.toml b/pyproject.toml index 262ca14..bbcccd8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.0.32" +version = "3.0.33" requires-python = ">= 3.9" dependencies = [ 'requests', diff --git a/socketdev/fullscans/__init__.py b/socketdev/fullscans/__init__.py index a2eb4f2..f295237 100644 --- a/socketdev/fullscans/__init__.py +++ b/socketdev/fullscans/__init__.py @@ -443,11 +443,20 @@ def to_dict(self): @classmethod def from_dict(cls, data: dict) -> "SocketAlert": + try: + category = SocketCategory(data["category"]) + except ValueError: + log.warning( + "Unknown SocketCategory %r; falling back to MISCELLANEOUS. " + "Upgrade socketdev to pick up newer categories.", + data["category"], + ) + category = SocketCategory.MISCELLANEOUS return cls( key=data["key"], type=data["type"], severity=SocketIssueSeverity(data["severity"]), - category=SocketCategory(data["category"]), + category=category, file=data.get("file"), start=data.get("start"), end=data.get("end"), diff --git a/socketdev/version.py b/socketdev/version.py index b70a418..46ae1b5 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.0.32" +__version__ = "3.0.33" diff --git a/tests/unit/test_socket_alert_category.py b/tests/unit/test_socket_alert_category.py new file mode 100644 index 0000000..02ce1c1 --- /dev/null +++ b/tests/unit/test_socket_alert_category.py @@ -0,0 +1,63 @@ +""" +Unit tests for lenient SocketCategory parsing in SocketAlert.from_dict. + +Regression coverage for +https://github.com/SocketDev/socket-sdk-python/issues/78: the Socket API can +emit category values the SDK does not yet know about (e.g. ``"other"``). Strict +enum parsing turned that into a hard failure that took down every consumer +(notably socketsecurity CI runs) whenever a diff included one of those alerts. + +These tests pin the fallback behavior so the SDK stays forward-compatible with +new server-side categories. +""" + +import logging +import unittest + +from socketdev.fullscans import SocketAlert, SocketCategory, SocketIssueSeverity + + +class TestSocketAlertCategoryParsing(unittest.TestCase): + """SocketAlert.from_dict should tolerate unknown category values.""" + + def _base_payload(self, category: str) -> dict: + return { + "key": "alert-key", + "type": "someAlertType", + "severity": "low", + "category": category, + } + + def test_known_category_is_preserved(self): + alert = SocketAlert.from_dict(self._base_payload("supplyChainRisk")) + self.assertEqual(alert.category, SocketCategory.SUPPLY_CHAIN_RISK) + self.assertEqual(alert.severity, SocketIssueSeverity.LOW) + + def test_unknown_category_falls_back_to_miscellaneous(self): + alert = SocketAlert.from_dict(self._base_payload("other")) + self.assertEqual(alert.category, SocketCategory.MISCELLANEOUS) + + def test_unknown_category_does_not_raise(self): + # Explicit regression assertion: no ValueError for brand-new categories. + try: + SocketAlert.from_dict(self._base_payload("somethingCompletelyNew")) + except ValueError as exc: + self.fail(f"SocketAlert.from_dict raised ValueError for unknown category: {exc}") + + def test_unknown_category_emits_warning(self): + with self.assertLogs("socketdev", level=logging.WARNING) as captured: + SocketAlert.from_dict(self._base_payload("other")) + self.assertTrue( + any("Unknown SocketCategory" in message for message in captured.output), + f"expected a warning about the unknown category, got: {captured.output}", + ) + + def test_every_known_category_round_trips(self): + for category in SocketCategory: + with self.subTest(category=category): + alert = SocketAlert.from_dict(self._base_payload(category.value)) + self.assertEqual(alert.category, category) + + +if __name__ == "__main__": + unittest.main() From 41039a8dbab41145ba3765ba11bdd6264ec1ea6b Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Thu, 21 May 2026 13:11:42 -0400 Subject: [PATCH 11/20] Support org-scoped batch package endpoint (#76) * Add org slug param with org-scoped routing, deprecation warning Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> * Add org-scoped + legacy endpoint test coverage, bump lodash placeholders Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> * Bump version for minor release rev Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> * Fix github project homepage on PyPI Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> * Fix RST formatting for title underlines Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --------- Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> Co-authored-by: Eric Hibbs --- .gitignore | 5 +- README.rst | 12 +++-- pyproject.toml | 4 +- socketdev/purl/__init__.py | 18 ++++++- socketdev/version.py | 2 +- tests/integration/test_all_endpoints.py | 24 ++++++---- .../test_comprehensive_integration.py | 14 ++++-- tests/unit/test_all_endpoints_unit.py | 47 +++++++++++++------ tests/unit/test_socket_sdk_unit.py | 6 +-- tests/unit/test_working_endpoints_unit.py | 8 ++-- uv.lock | 2 +- 11 files changed, 94 insertions(+), 48 deletions(-) diff --git a/.gitignore b/.gitignore index 9f566db..77da6d1 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,7 @@ dist *.egg-info *.cpython-312.pyc example-socket-export.py -__pycache__/ \ No newline at end of file +__pycache__/ +.coverage +.coverage.* +htmlcov/ diff --git a/README.rst b/README.rst index 2ed1265..61b64ce 100644 --- a/README.rst +++ b/README.rst @@ -28,9 +28,11 @@ 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:** @@ -38,6 +40,7 @@ Retrieve the package information for a purl post from socketdev import socketdev socket = socketdev(token="REPLACE_ME") + org_slug = "your-org-slug" license = "true" components = [ { @@ -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) """""""""""""""""""""""""""""""""""""""""" diff --git a/pyproject.toml b/pyproject.toml index bbcccd8..5bbc081 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.0.33" +version = "3.1.0" requires-python = ">= 3.9" dependencies = [ 'requests', @@ -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. diff --git a/socketdev/purl/__init__.py b/socketdev/purl/__init__.py index 555f3a5..50118ed 100644 --- a/socketdev/purl/__init__.py +++ b/socketdev/purl/__init__.py @@ -1,5 +1,6 @@ import json import urllib.parse +import warnings from socketdev.log import log from ..core.dedupe import Dedupe @@ -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} diff --git a/socketdev/version.py b/socketdev/version.py index 46ae1b5..f5f41e5 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.0.33" +__version__ = "3.1.0" diff --git a/tests/integration/test_all_endpoints.py b/tests/integration/test_all_endpoints.py index 17d934f..e36f074 100644 --- a/tests/integration/test_all_endpoints.py +++ b/tests/integration/test_all_endpoints.py @@ -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 @@ -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 @@ -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) @@ -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) @@ -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) diff --git a/tests/integration/test_comprehensive_integration.py b/tests/integration/test_comprehensive_integration.py index 137723b..4259b6b 100644 --- a/tests/integration/test_comprehensive_integration.py +++ b/tests/integration/test_comprehensive_integration.py @@ -53,7 +53,7 @@ def setUpClass(cls): "name": "test-integration-project", "version": "1.0.0", "dependencies": { - "lodash": "4.17.21" + "lodash": "4.18.1" } } @@ -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}") @@ -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}") diff --git a/tests/unit/test_all_endpoints_unit.py b/tests/unit/test_all_endpoints_unit.py index 40f90bc..64895ee 100644 --- a/tests/unit/test_all_endpoints_unit.py +++ b/tests/unit/test_all_endpoints_unit.py @@ -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: @@ -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): @@ -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): @@ -352,14 +352,14 @@ 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"] @@ -367,7 +367,7 @@ def test_purl_post_unit(self): # 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() @@ -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): diff --git a/tests/unit/test_socket_sdk_unit.py b/tests/unit/test_socket_sdk_unit.py index b64aaec..973c046 100644 --- a/tests/unit/test_socket_sdk_unit.py +++ b/tests/unit/test_socket_sdk_unit.py @@ -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.""" @@ -223,7 +223,7 @@ def setUp(self): "name": "test-package", "version": "1.0.0", "dependencies": { - "lodash": "4.17.21" + "lodash": "4.18.1" } } diff --git a/tests/unit/test_working_endpoints_unit.py b/tests/unit/test_working_endpoints_unit.py index 33a69bb..3f8112b 100644 --- a/tests/unit/test_working_endpoints_unit.py +++ b/tests/unit/test_working_endpoints_unit.py @@ -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.""" diff --git a/uv.lock b/uv.lock index b72135f..a863a5c 100644 --- a/uv.lock +++ b/uv.lock @@ -1343,7 +1343,7 @@ wheels = [ [[package]] name = "socketdev" -version = "3.0.29" +version = "3.1.0" source = { editable = "." } dependencies = [ { name = "requests" }, From 8b668fd14415f196dde7bf6e4856139c541d9ae1 Mon Sep 17 00:00:00 2001 From: Eric Hibbs Date: Fri, 22 May 2026 14:37:50 -0700 Subject: [PATCH 12/20] ci(version-check): require uv.lock sync alongside pyproject changes (#82) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci(version-check): require uv.lock sync alongside pyproject changes Resolves CE-202. Mirrors the workflow + script changes from socket-python-cli#204 so the SDK catches lockfile drift the same way the CLI now does: - workflow: trigger paths drop unused setup.py, add uv.lock; new step fails CI if pyproject.toml is modified without uv.lock. - sync_version.py: new run_uv_lock() helper runs 'uv lock' and signals whether the lockfile changed. Wired into all three exit paths (--dev auto-bump, normal auto-bump, already-bumped) so the hook either updates uv.lock for you or tells you to commit it. * ci(version-check): also require PR version > latest PyPI stable Mirrors socket-python-cli's fix at 0462b77 (in PR #199). The workflow previously only compared the PR version against main, which missed the case where the same or newer version had already been published to PyPI — that would slip through CI and either collide on publish or leave PyPI ahead of the repo. - workflow: hits pypi.org/pypi/socketdev/json, filters to stable (non-prerelease, non-devrelease), requires PR > max(main, PyPI). - sync_version.py: splits PYPI_PROD_API vs PYPI_TEST_API. Stable auto-bumps now use prod PyPI as the floor via find_next_stable_patch_version(). The .devN flow keeps using TestPyPI. New 'already bumped but ≤ PyPI' path auto-corrects the version when somebody bumps to a stale number. --- .github/workflows/version-check.yml | 59 +++++++++++++++--- .hooks/sync_version.py | 95 ++++++++++++++++++++++++++--- 2 files changed, 135 insertions(+), 19 deletions(-) diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml index 40b9536..6db1426 100644 --- a/.github/workflows/version-check.yml +++ b/.github/workflows/version-check.yml @@ -4,8 +4,8 @@ on: types: [opened, synchronize, ready_for_review] paths: - 'socketdev/**' - - 'setup.py' - 'pyproject.toml' + - 'uv.lock' permissions: contents: read @@ -33,16 +33,55 @@ jobs: MAIN_VERSION=$(grep -o "__version__.*" socketdev/version.py | awk '{print $3}' | tr -d '"' | tr -d "'") echo "MAIN_VERSION=$MAIN_VERSION" >> $GITHUB_ENV - # Compare versions using Python - python3 -c " + export PR_VERSION + export MAIN_VERSION + + # Compare against both main and latest published PyPI release. + python3 <<'PY' + import json + import os + import urllib.request from packaging import version - pr_ver = version.parse('${PR_VERSION}') - main_ver = version.parse('${MAIN_VERSION}') - if pr_ver <= main_ver: - print(f'❌ Version must be incremented! Main: {main_ver}, PR: {pr_ver}') - exit(1) - print(f'✅ Version properly incremented from {main_ver} to {pr_ver}') - " + + pr_ver = version.parse(os.environ["PR_VERSION"]) + main_ver = version.parse(os.environ["MAIN_VERSION"]) + + with urllib.request.urlopen("https://pypi.org/pypi/socketdev/json") as response: + pypi_data = json.load(response) + + published_versions = [] + for raw in pypi_data.get("releases", {}).keys(): + parsed = version.parse(raw) + if not parsed.is_prerelease and not parsed.is_devrelease: + published_versions.append(parsed) + + pypi_ver = max(published_versions) if published_versions else version.parse("0.0.0") + required_floor = max(main_ver, pypi_ver) + + if pr_ver <= required_floor: + print( + f"❌ Version must be greater than main and PyPI! " + f"Main: {main_ver}, PyPI: {pypi_ver}, PR: {pr_ver}" + ) + raise SystemExit(1) + + print( + f"✅ Version properly incremented. " + f"Main: {main_ver}, PyPI: {pypi_ver}, PR: {pr_ver}" + ) + PY + + - name: Require uv.lock update when pyproject changes + run: | + CHANGED_FILES="$(git diff --name-only origin/main...HEAD)" + + if echo "$CHANGED_FILES" | grep -qx 'pyproject.toml'; then + if ! echo "$CHANGED_FILES" | grep -qx 'uv.lock'; then + echo "❌ pyproject.toml changed, but uv.lock was not updated." + echo "Run 'uv lock' and commit uv.lock with the version bump." + exit 1 + fi + fi - name: Manage PR Comment uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea diff --git a/.hooks/sync_version.py b/.hooks/sync_version.py index 59b0427..7a8ab24 100755 --- a/.hooks/sync_version.py +++ b/.hooks/sync_version.py @@ -8,10 +8,13 @@ VERSION_FILE = pathlib.Path("socketdev/version.py") PYPROJECT_FILE = pathlib.Path("pyproject.toml") +UV_LOCK_FILE = pathlib.Path("uv.lock") VERSION_PATTERN = re.compile(r"__version__\s*=\s*['\"]([^'\"]+)['\"]") PYPROJECT_PATTERN = re.compile(r'^version\s*=\s*".*"$', re.MULTILINE) -PYPI_API = "https://test.pypi.org/pypi/socketdev/json" +STABLE_VERSION_PATTERN = re.compile(r"^\d+\.\d+\.\d+$") +PYPI_PROD_API = "https://pypi.org/pypi/socketdev/json" +PYPI_TEST_API = "https://test.pypi.org/pypi/socketdev/json" def read_version_from_version_file(path: pathlib.Path) -> str: content = path.read_text() @@ -38,17 +41,40 @@ def bump_patch_version(version: str) -> str: parts[-1] = str(int(parts[-1]) + 1) return ".".join(parts) -def fetch_existing_versions() -> set: +def parse_stable_version(version: str): + if not STABLE_VERSION_PATTERN.fullmatch(version): + return None + return tuple(int(part) for part in version.split(".")) + + +def format_stable_version(version_parts) -> str: + return ".".join(str(part) for part in version_parts) + + +def fetch_existing_versions(api_url: str) -> set: try: - with urllib.request.urlopen(PYPI_API) as response: + with urllib.request.urlopen(api_url) as response: data = json.load(response) return set(data.get("releases", {}).keys()) except Exception as e: - print(f"⚠️ Warning: Failed to fetch existing versions from Test PyPI: {e}") + print(f"⚠️ Warning: Failed to fetch versions from {api_url}: {e}") return set() + +def fetch_latest_stable_pypi_version(): + versions = fetch_existing_versions(PYPI_PROD_API) + stable_versions = [] + for ver in versions: + parsed = parse_stable_version(ver) + if parsed is not None: + stable_versions.append(parsed) + if not stable_versions: + return None + return max(stable_versions) + + def find_next_available_dev_version(base_version: str) -> str: - existing_versions = fetch_existing_versions() + existing_versions = fetch_existing_versions(PYPI_TEST_API) for i in range(1, 100): candidate = f"{base_version}.dev{i}" if candidate not in existing_versions: @@ -56,6 +82,20 @@ def find_next_available_dev_version(base_version: str) -> str: print("❌ Could not find available .devN slot after 100 attempts.") sys.exit(1) + +def find_next_stable_patch_version(current_version: str) -> str: + current_stable = current_version.split(".dev")[0] if ".dev" in current_version else current_version + current_parts = parse_stable_version(current_stable) + if current_parts is None: + print(f"❌ Unsupported version format for stable bump: {current_version}") + sys.exit(1) + + latest_pypi_parts = fetch_latest_stable_pypi_version() + base_parts = max([current_parts, latest_pypi_parts] if latest_pypi_parts else [current_parts]) + next_parts = (base_parts[0], base_parts[1], base_parts[2] + 1) + return format_stable_version(next_parts) + + def inject_version(version: str): print(f"🔁 Updating version to: {version}") @@ -68,6 +108,22 @@ def inject_version(version: str): new_pyproject = PYPROJECT_PATTERN.sub(f'version = "{version}"', pyproject) PYPROJECT_FILE.write_text(new_pyproject) + +def run_uv_lock() -> bool: + before = UV_LOCK_FILE.read_bytes() if UV_LOCK_FILE.exists() else b"" + try: + subprocess.run(["uv", "lock"], check=True, text=True) + except FileNotFoundError: + print("❌ `uv` is required but was not found in PATH.") + sys.exit(1) + except subprocess.CalledProcessError: + print("❌ `uv lock` failed. Please run it manually and fix any errors.") + sys.exit(1) + + after = UV_LOCK_FILE.read_bytes() if UV_LOCK_FILE.exists() else b"" + return before != after + + def main(): dev_mode = "--dev" in sys.argv current_version = read_version_from_version_file(VERSION_FILE) @@ -80,15 +136,36 @@ def main(): base_version = current_version.split(".dev")[0] if ".dev" in current_version else current_version new_version = find_next_available_dev_version(base_version) inject_version(new_version) - print("⚠️ Version was unchanged — auto-bumped. Please git add + commit again.") + uv_lock_changed = run_uv_lock() + lock_hint = " and uv.lock" if uv_lock_changed else "" + print(f"⚠️ Version was unchanged — auto-bumped. Please git add{lock_hint} + commit again.") sys.exit(0) else: - new_version = bump_patch_version(current_version) + new_version = find_next_stable_patch_version(current_version) inject_version(new_version) - print("⚠️ Version was unchanged — auto-bumped. Please git add + commit again.") + uv_lock_changed = run_uv_lock() + lock_hint = " and uv.lock" if uv_lock_changed else "" + print(f"⚠️ Version was unchanged — auto-bumped to {new_version}. Please git add{lock_hint} + commit again.") sys.exit(1) else: - print("✅ Version already bumped — proceeding.") + if not dev_mode: + current_parts = parse_stable_version(current_version) + latest_pypi_parts = fetch_latest_stable_pypi_version() + if current_parts is not None and latest_pypi_parts is not None and current_parts <= latest_pypi_parts: + next_parts = (latest_pypi_parts[0], latest_pypi_parts[1], latest_pypi_parts[2] + 1) + new_version = format_stable_version(next_parts) + inject_version(new_version) + uv_lock_changed = run_uv_lock() + lock_hint = " and uv.lock" if uv_lock_changed else "" + print(f"⚠️ Version {current_version} is already published on PyPI — auto-bumped to {new_version}. Please git add{lock_hint} + commit again.") + sys.exit(1) + + uv_lock_changed = run_uv_lock() + if uv_lock_changed: + print("⚠️ Version already bumped, but uv.lock was out of date and has been updated. Please git add uv.lock + commit again.") + sys.exit(1) + + print("✅ Version already bumped and uv.lock is up to date — proceeding.") sys.exit(0) if __name__ == "__main__": From be87c349119d03dc55d5da955d26fd3afa9870ff Mon Sep 17 00:00:00 2001 From: Eric Hibbs Date: Fri, 22 May 2026 15:02:47 -0700 Subject: [PATCH 13/20] Fix stale `didYouMean` props (#81) * test: failing regression for stale didYouMean props * fix(issues): drop stale didYouMean props, add detectedAt Resolves CUS2-5. The didYouMean class declared four props (alternatePackage, downloads, downloadsRatio, editDistance) but the current OpenAPI schema (socket-sdk-js/openapi.json:9298) only emits { alternatePackage, detectedAt }. The three stale keys were dead at runtime and detectedAt was missing a human-readable label entirely. Updated to match the schema. * chore(release): bump to 3.1.1 and sync pyproject.toml Run via .hooks/sync_version.py after merging origin/main (now at 3.1.0 from lelia's purl PR). Keeps pyproject.toml and socketdev/version.py in lockstep, as flagged in code review. * chore: sync uv.lock to 3.1.1 --- pyproject.toml | 2 +- socketdev/core/issues.py | 2 +- socketdev/version.py | 2 +- tests/unit/test_issues_did_you_mean_props.py | 28 ++++++++++++++++++++ uv.lock | 2 +- 5 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 tests/unit/test_issues_did_you_mean_props.py diff --git a/pyproject.toml b/pyproject.toml index 5bbc081..0fc8167 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.1.0" +version = "3.1.1" requires-python = ">= 3.9" dependencies = [ 'requests', diff --git a/socketdev/core/issues.py b/socketdev/core/issues.py index d712056..027ad98 100644 --- a/socketdev/core/issues.py +++ b/socketdev/core/issues.py @@ -463,7 +463,7 @@ class didYouMean: def __init__(self): self.description = "Package name is similar to other popular packages and may not be the package you want." - self.props = {"alternatePackage": "Alternate package", "downloads": "Downloads", "downloadsRatio": "Download ratio", "editDistance": "Edit distance"} + self.props = {"alternatePackage": "Alternate package", "detectedAt": "Detected at"} self.suggestion = "Use care when consuming similarly named packages and ensure that you did not intend to consume a different package. Malicious packages often publish using similar names as existing popular packages." self.title = "Possible typosquat attack" self.emoji = "\ud83e\uddd0" diff --git a/socketdev/version.py b/socketdev/version.py index f5f41e5..d539d50 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.1.0" +__version__ = "3.1.1" diff --git a/tests/unit/test_issues_did_you_mean_props.py b/tests/unit/test_issues_did_you_mean_props.py new file mode 100644 index 0000000..8657872 --- /dev/null +++ b/tests/unit/test_issues_did_you_mean_props.py @@ -0,0 +1,28 @@ +"""Contract test for the didYouMean alert-type class's props. + +The OpenAPI schema (`socket-sdk-js/openapi.json` around line 9298) declares +that the API emits `didYouMean` alerts with ``props: { alternatePackage, +detectedAt }``. The Python SDK previously declared four props +(``alternatePackage``, ``downloads``, ``downloadsRatio``, ``editDistance``); +the latter three are no longer in the API schema and were dead keys at +runtime — and ``detectedAt`` was missing. + +Tracks CUS2-5. Sibling of CUS2-4. +""" + +import unittest + +from socketdev.core.issues import didYouMean + + +class TestDidYouMeanProps(unittest.TestCase): + def test_props_match_openapi_schema(self): + """API emits props { alternatePackage, detectedAt } (openapi.json:9298).""" + issue = didYouMean() + self.assertEqual(set(issue.props.keys()), {"alternatePackage", "detectedAt"}) + + def test_props_label_strings_are_non_empty(self): + """Every props key must have a non-empty human-readable label.""" + issue = didYouMean() + for key, label in issue.props.items(): + self.assertTrue(label, f"props[{key!r}] label should not be empty") diff --git a/uv.lock b/uv.lock index a863a5c..9c96b4d 100644 --- a/uv.lock +++ b/uv.lock @@ -1343,7 +1343,7 @@ wheels = [ [[package]] name = "socketdev" -version = "3.1.0" +version = "3.1.1" source = { editable = "." } dependencies = [ { name = "requests" }, From 8dfb7a1ba115eb8020ee383666c7386e2e01c9f5 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:30:29 -0400 Subject: [PATCH 14/20] Dependabot hardening + dependency update bundle (#84) * Harden Dependabot reviews and bundle dependency updates Mirrors the Dependabot hardening done in socket-python-cli (#207/#217/#218), adapted to this SDK (no Dockerfile, no e2e fixtures, hatch/pip build path). Bundle dependency updates (supersedes 4 open Dependabot PRs): - idna 3.11 -> 3.17 (security: CVE-2026-45409 quadratic-time DoS fix) - cryptography 46.0.5 -> 46.0.7 - pygments 2.19.2 -> 2.20.0 - uv 0.9.21 -> 0.11.17 Verified via uv sync --locked, import smoke, and pytest tests/unit (102 passed). Adds grouped/cooldowned dependabot.yml (uv + github-actions), a dependabot-review workflow running anonymous Socket Firewall smoke jobs, Version Check / PR Preview skips for Dependabot PRs, and setup-sfw / setup-hatch composite actions. Co-Authored-By: Claude Opus 4.8 * chore(release): bump to 3.1.2 Version Check requires a package-version increment on maintainer PRs, and this PR bundles dependency bumps + Dependabot hardening. Bump version.py, pyproject.toml, and the uv.lock project version in sync. Co-Authored-By: Claude Opus 4.8 * Extend dependency review to maintainers (free + enterprise SFW) Broaden dependabot-review into dependency-review so the Socket Firewall guardrail covers maintainer PRs too, not just Dependabot: - inspect now runs on every PR and computes the SFW edition per-PR: enterprise for a trusted SocketDev member (author_association OWNER/ MEMBER/COLLABORATOR) on an in-repo (non-fork) PR when SOCKET_API_TOKEN is present; free (anonymous) for Dependabot, forks, external contributors, or when the token is absent. - The mode degrades to free whenever the token is missing, so this is safe to ship before the secret exists and auto-upgrades to enterprise once SOCKET_API_TOKEN is added (repo or org level). The SDK has no Socket token today (cf. socket-python-cli's SOCKET_CLI_API_TOKEN). - setup-sfw composite action gains `mode` + `socket-token` inputs, forwarded to socketdev/action (same action, firewall-free vs firewall-enterprise). - Rename workflow dependabot-review.yml -> dependency-review.yml to match the broadened scope (not a required status check). Co-Authored-By: Claude Opus 4.8 * fix(dependency-review): use runner Python, forbid uv interpreter download .python-version pins 3.12.7; setup-python provides 3.12.13, so `uv sync` tried to download the exact managed CPython from GitHub, which Socket Firewall's TLS interception blocked (UnknownIssuer). Set UV_PYTHON=3.12 + UV_PYTHON_DOWNLOADS=never so uv uses the runner interpreter and only PyPI package fetches route through sfw. Co-Authored-By: Claude Opus 4.8 * fix(dependency-review): require strict org membership for enterprise SFW Tighten the enterprise-mode gate to author_association OWNER/MEMBER only. Outside collaborators (COLLABORATOR) now fall through to the free edition, same as Dependabot / forks / external contributors. Co-Authored-By: Claude Opus 4.8 * chore(dependency-review): rename enterprise secret to SOCKET_SFW_API_TOKEN Co-Authored-By: Claude Opus 4.8 * fix(dependency-review): scope SFW token to a dedicated environment Resolve zizmor secrets-outside-env (medium) without suppressing it. Split the single mode-switching smoke job into two: - python-sfw-smoke-free: untrusted PRs (Dependabot, forks, outside collaborators, externals). Anonymous free edition, never references the token. - python-sfw-smoke-enterprise: SocketDev org members (OWNER/MEMBER) on an in-repo PR. Authenticated enterprise edition; SOCKET_SFW_API_TOKEN is scoped to the `socket-firewall` GitHub environment, so only this job can read it. inspect now classifies PR trust (author_association OWNER/MEMBER, non-fork, non-Dependabot) and references no secret. No required-reviewer protection on the environment, so trusted dep PRs still run automatically. Co-Authored-By: Claude Opus 4.8 * fix(dependency-review): gate enterprise on write-access (non-fork), not author_association author_association only reflects PUBLIC org membership, so private members (the common case here) show as CONTRIBUTOR and were misclassified -> the enterprise job always skipped. Switch the trust gate to "non-fork PR and not Dependabot": only accounts with write access can push an in-repo branch, the same boundary GitHub uses for secret exposure. No read:org token needed. Co-Authored-By: Claude Opus 4.8 * ci(dependency-review): upload SFW smoke artifacts * ci(dependency-review): include SFW JSON report artifact * ci(dependency-review): read SFW report path from env var, drop stdout scrape Match socket-python-cli: discover the firewall report via the $SFW_JSON_REPORT_PATH env var that socketdev/action exports, instead of parsing the 'sfw report written to:' line out of stdout. The two sync steps return to plain 'set -o pipefail' + tee. A new 'Collect SFW JSON report' step (if: always(), before each upload) copies $SFW_JSON_REPORT_PATH into sfw-artifacts/sfw-report.json -- copy, not move, since socketdev/action's post step reads that temp path for its job summary -- and drops a sfw-report-missing.txt breadcrumb when absent. More robust than scraping an undocumented log string, and keeps the report-capture pattern uniform across both repos. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --------- Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 --- .github/actions/setup-hatch/action.yml | 13 ++ .github/actions/setup-sfw/action.yml | 41 ++++ .github/dependabot.yml | 63 ++++++ .github/workflows/dependency-review.yml | 281 ++++++++++++++++++++++++ .github/workflows/pr-preview.yml | 22 +- .github/workflows/release.yml | 11 +- .github/workflows/version-check.yml | 4 + pyproject.toml | 2 +- socketdev/version.py | 2 +- uv.lock | 171 +++++++------- 10 files changed, 513 insertions(+), 97 deletions(-) create mode 100644 .github/actions/setup-hatch/action.yml create mode 100644 .github/actions/setup-sfw/action.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/dependency-review.yml diff --git a/.github/actions/setup-hatch/action.yml b/.github/actions/setup-hatch/action.yml new file mode 100644 index 0000000..0da5160 --- /dev/null +++ b/.github/actions/setup-hatch/action.yml @@ -0,0 +1,13 @@ +name: "Set up Hatch build tooling" +description: >- + Install the pinned hatch / hatchling / virtualenv toolchain used to build + and publish the package. Assumes Python is already set up by the caller. + +runs: + using: "composite" + steps: + - shell: bash + run: | + python -m pip install --upgrade pip + pip install "virtualenv<20.36" + pip install hatchling==1.27.0 hatch==1.14.0 diff --git a/.github/actions/setup-sfw/action.yml b/.github/actions/setup-sfw/action.yml new file mode 100644 index 0000000..ffe006d --- /dev/null +++ b/.github/actions/setup-sfw/action.yml @@ -0,0 +1,41 @@ +name: "Set up Socket Firewall" +description: >- + Set up the requested Python/uv toolchain and install Socket Firewall so + subsequent steps can run package-manager commands wrapped with `sfw`. + Defaults to free/anonymous mode (no API token -- safe on untrusted / + Dependabot / fork PRs). Pass mode: firewall-enterprise + socket-token for + full org-policy enforcement on trusted maintainer PRs. + +inputs: + python: + description: "Set up Python 3.12" + default: "false" + uv: + description: "Install uv (implies Python)" + default: "false" + mode: + description: "socketdev/action mode: firewall-free or firewall-enterprise" + default: "firewall-free" + socket-token: + description: "Socket API token (only used/required for firewall-enterprise)" + default: "" + +runs: + using: "composite" + steps: + - if: ${{ inputs.python == 'true' || inputs.uv == 'true' }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.12" + + # Official Socket setup action. Wires up sfw routing correctly. + # socket-token is ignored in firewall-free mode and empty when absent. + - uses: socketdev/action@ba6de6cc0565af1f42295590380973573297e31f # v1.3.2 + with: + mode: ${{ inputs.mode }} + socket-token: ${{ inputs.socket-token }} + + - if: ${{ inputs.uv == 'true' }} + name: Install uv + shell: bash + run: python -m pip install --upgrade pip uv diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c02d0dd --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,63 @@ +# Dependabot configuration for socket-sdk-python. +# +# Design notes: +# - Python deps are grouped into a weekly PR (minor/patch), with a +# separate group for majors so breaking bumps stay reviewable. +# - GitHub Actions are grouped similarly into one weekly PR, and Dependabot +# scans both the workflows and the local composite actions. +# - 7-day cooldown enforced across all ecosystems. +# - This repo ships no Dockerfile, so there is no docker ecosystem entry. + +version: 2 +updates: + + # Python deps (uv-tracked via uv.lock) + - package-ecosystem: "uv" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 2 + groups: + python-minor-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" + python-major: + patterns: + - "*" + update-types: + - "major" + labels: + - "dependencies" + - "python:uv" + commit-message: + prefix: "chore" + include: "scope" + cooldown: + default-days: 7 + + # GitHub Actions used in workflows and local composite actions. + - package-ecosystem: "github-actions" + directories: + - "/" + - "/.github/actions/*" + schedule: + interval: "weekly" + open-pull-requests-limit: 2 + groups: + github-actions-minor-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" + labels: + - "dependencies" + - "github-actions" + commit-message: + prefix: "ci" + include: "scope" + cooldown: + default-days: 7 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..ea0acee --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,281 @@ +name: dependency-review + +# Supply-chain guardrails for dependency-update PRs -- for BOTH Dependabot +# and maintainers. `inspect` classifies the PR, then exactly one Socket +# Firewall (sfw) install smoke job runs when Python deps change: +# +# - python-sfw-smoke-enterprise -- trusted authors: any in-repo (non-fork) +# PR other than Dependabot's (i.e. someone with write access). Runs the +# authenticated enterprise edition for full org-policy enforcement. The +# SOCKET_SFW_API_TOKEN is scoped to the `socket-firewall` environment, so +# it is the ONLY job that can read the token. +# - python-sfw-smoke-free -- everyone else (Dependabot + all fork PRs from +# external contributors). Anonymous free edition, no token. This job never +# references the secret. +# +# Splitting the jobs (rather than picking a mode in one job) keeps the token +# out of scope on every untrusted run and satisfies zizmor's +# `secrets-outside-env` audit without suppressing it. The free path runs in +# the unprivileged `pull_request` context with no secret-leak surface. +# +# Pattern adapted from SocketDev/socket-python-cli. + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + +concurrency: + group: dependency-review-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + inspect: + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + python_deps_changed: ${{ steps.diff.outputs.python_deps_changed }} + workflow_or_action_changed: ${{ steps.diff.outputs.workflow_or_action_changed }} + is_trusted: ${{ steps.trust.outputs.is_trusted }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Inspect changed files + id: diff + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + CHANGED_FILES="$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")" + + { + echo "## Changed files" + echo '```' + printf '%s\n' "$CHANGED_FILES" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + has_file() { + local pattern="$1" + if printf '%s\n' "$CHANGED_FILES" | grep -Eq "$pattern"; then + echo "true" + else + echo "false" + fi + } + + { + echo "python_deps_changed=$(has_file '^(pyproject\.toml|uv\.lock)$')" + echo "workflow_or_action_changed=$(has_file '^\.github/workflows/|^\.github/actions/|^\.github/dependabot\.yml$')" + } >> "$GITHUB_OUTPUT" + + - name: Classify PR trust + id: trust + # Trusted == any in-repo (non-fork) PR that isn't Dependabot's. Only + # accounts with write access can push a branch to this repo, so a + # non-fork PR already implies a trusted author -- the same boundary + # GitHub uses to decide whether secrets are exposed at all. + # + # NB: author_association is deliberately NOT used to require strict org + # membership. It only reflects PUBLIC org membership, so private members + # (the common case) show up as CONTRIBUTOR and would be misclassified. + # Reliable strict-membership detection would need a read:org token or + # public membership. This step references NO secret regardless -- it + # only decides which smoke job runs. + env: + IS_DEPENDABOT: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }} + IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + AUTHOR_ASSOC: ${{ github.event.pull_request.author_association }} + run: | + is_trusted=false + if [ "$IS_DEPENDABOT" != "true" ] && [ "$IS_FORK" != "true" ]; then + is_trusted=true + fi + + echo "is_trusted=$is_trusted" >> "$GITHUB_OUTPUT" + { + echo "## Socket Firewall edition: \`$([ "$is_trusted" = true ] && echo enterprise || echo free)\`" + echo "- author_association: \`$AUTHOR_ASSOC\`" + echo "- dependabot: \`$IS_DEPENDABOT\` | fork: \`$IS_FORK\`" + } >> "$GITHUB_STEP_SUMMARY" + + - name: Summarize review expectations + env: + PR_URL: ${{ github.event.pull_request.html_url }} + run: | + { + echo "## Dependency Review Checklist" + echo "- PR: $PR_URL" + echo "- Confirm upstream release notes before merge" + echo "- Do not treat a dependency PR as trusted solely because of the actor" + echo "- This workflow runs in pull_request context only; no publish secrets are exposed" + } >> "$GITHUB_STEP_SUMMARY" + + # Untrusted PRs (Dependabot, forks, outside collaborators, externals): + # anonymous free edition. Never references the token. + python-sfw-smoke-free: + needs: inspect + if: needs.inspect.outputs.python_deps_changed == 'true' && needs.inspect.outputs.is_trusted != 'true' + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + persist-credentials: false + + - name: Prepare SFW artifact directory + run: | + mkdir -p sfw-artifacts + { + echo "mode=firewall-free" + echo "pr=${{ github.event.pull_request.number }}" + echo "sha=${{ github.event.pull_request.head.sha }}" + } > sfw-artifacts/context.txt + + - uses: ./.github/actions/setup-sfw + with: + uv: "true" + mode: firewall-free + + - name: Sync project through Socket Firewall (free) + # pipefail keeps sfw's exit code through the tee so a firewall block + # still fails the job; tee captures the report for the artifact upload. + env: + UV_PYTHON: "3.12" + UV_PYTHON_DOWNLOADS: never + run: | + set -o pipefail + sfw uv sync --locked --extra test --extra dev 2>&1 | tee sfw-artifacts/sfw-uv-sync.log + + - name: Import smoke test + run: | + set -o pipefail + uv run python -c " + import socketdev + from socketdev import socketdev as SocketDevClient + from socketdev.core.api import API + from socketdev.version import __version__ + print('import smoke OK', __version__) + " 2>&1 | tee sfw-artifacts/import-smoke.log + + - name: Collect SFW JSON report + # socketdev/action points sfw at SFW_JSON_REPORT_PATH (a $RUNNER_TEMP + # file) and reads it back in its post step to render the job summary, so + # COPY (don't move) the report into the bundle. sfw writes it even when + # it blocks an install -- always() keeps it on failures too. + if: always() + run: | + if [ -n "${SFW_JSON_REPORT_PATH:-}" ] && [ -f "$SFW_JSON_REPORT_PATH" ]; then + cp "$SFW_JSON_REPORT_PATH" "$GITHUB_WORKSPACE/sfw-artifacts/sfw-report.json" + echo "Collected SFW report -> sfw-artifacts/sfw-report.json" + else + echo "No SFW JSON report found at '${SFW_JSON_REPORT_PATH:-}'." \ + > "$GITHUB_WORKSPACE/sfw-artifacts/sfw-report-missing.txt" + fi + + - name: Upload SFW report artifact + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: socket-firewall-free-${{ github.event.pull_request.number }} + path: sfw-artifacts/ + if-no-files-found: warn + retention-days: 14 + + # Trusted SocketDev members: authenticated enterprise edition. The token is + # scoped to the `socket-firewall` environment, so only this job can read it. + python-sfw-smoke-enterprise: + needs: inspect + if: needs.inspect.outputs.python_deps_changed == 'true' && needs.inspect.outputs.is_trusted == 'true' + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: socket-firewall + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + persist-credentials: false + + - name: Prepare SFW artifact directory + run: | + mkdir -p sfw-artifacts + { + echo "mode=firewall-enterprise" + echo "pr=${{ github.event.pull_request.number }}" + echo "sha=${{ github.event.pull_request.head.sha }}" + } > sfw-artifacts/context.txt + + - uses: ./.github/actions/setup-sfw + with: + uv: "true" + mode: firewall-enterprise + socket-token: ${{ secrets.SOCKET_SFW_API_TOKEN }} + + - name: Sync project through Socket Firewall (enterprise) + # See free job for the UV_PYTHON rationale: .python-version pins an + # exact patch that uv would otherwise fetch from GitHub through the + # firewall (blocked by its TLS interception); use the runner's Python. + # + # pipefail keeps sfw's exit code through the tee so a firewall block + # still fails the job; tee captures the report for the artifact upload. + env: + UV_PYTHON: "3.12" + UV_PYTHON_DOWNLOADS: never + run: | + set -o pipefail + sfw uv sync --locked --extra test --extra dev 2>&1 | tee sfw-artifacts/sfw-uv-sync.log + + - name: Import smoke test + run: | + set -o pipefail + uv run python -c " + import socketdev + from socketdev import socketdev as SocketDevClient + from socketdev.core.api import API + from socketdev.version import __version__ + print('import smoke OK', __version__) + " 2>&1 | tee sfw-artifacts/import-smoke.log + + - name: Collect SFW JSON report + # socketdev/action points sfw at SFW_JSON_REPORT_PATH (a $RUNNER_TEMP + # file) and reads it back in its post step to render the job summary, so + # COPY (don't move) the report into the bundle. sfw writes it even when + # it blocks an install -- always() keeps it on failures too. + if: always() + run: | + if [ -n "${SFW_JSON_REPORT_PATH:-}" ] && [ -f "$SFW_JSON_REPORT_PATH" ]; then + cp "$SFW_JSON_REPORT_PATH" "$GITHUB_WORKSPACE/sfw-artifacts/sfw-report.json" + echo "Collected SFW report -> sfw-artifacts/sfw-report.json" + else + echo "No SFW JSON report found at '${SFW_JSON_REPORT_PATH:-}'." \ + > "$GITHUB_WORKSPACE/sfw-artifacts/sfw-report-missing.txt" + fi + + - name: Upload SFW report artifact + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: socket-firewall-enterprise-${{ github.event.pull_request.number }} + path: sfw-artifacts/ + if-no-files-found: warn + retention-days: 14 + + workflow-notice: + needs: inspect + if: needs.inspect.outputs.workflow_or_action_changed == 'true' + runs-on: ubuntu-latest + timeout-minutes: 2 + steps: + - name: Flag workflow-sensitive updates + run: | + { + echo "## Sensitive File Notice" + echo "This PR changes workflow, composite-action, or dependabot config files." + echo "Require explicit human review before merge." + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index 6701058..f45ff55 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -3,8 +3,21 @@ on: pull_request: types: [opened, synchronize, ready_for_review] +# Cancel an in-flight preview when the PR is pushed again -- previews publish +# to Test PyPI, so superseded runs shouldn't keep churning. +concurrency: + group: pr-preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: preview: + # Skip on: + # - PRs from forks (no access to publish secrets / OIDC) + # - Dependabot PRs: preview-publishing a dependency bump to Test PyPI is + # pointless (no package version bump) and would fail the version check. + if: >- + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.user.login != 'dependabot[bot]' runs-on: ubuntu-latest permissions: id-token: write @@ -19,13 +32,8 @@ jobs: with: python-version: '3.13' - # Install all dependencies from pyproject.toml - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install "virtualenv<20.36" - pip install hatchling==1.27.0 - pip install hatch==1.14.0 + - name: Install build tooling + uses: ./.github/actions/setup-hatch - name: Inject full dynamic version run: python .hooks/sync_version.py --dev diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index acc8981..a2a940e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,14 +18,9 @@ jobs: with: python-version: '3.13' - # Install all dependencies from pyproject.toml - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install "virtualenv<20.36" - pip install hatchling==1.27.0 - pip install hatch==1.14.0 - + - name: Install build tooling + uses: ./.github/actions/setup-hatch + - name: Get Version id: version env: diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml index 6db1426..e2c9292 100644 --- a/.github/workflows/version-check.yml +++ b/.github/workflows/version-check.yml @@ -14,6 +14,10 @@ permissions: jobs: check_version: + # Skip on Dependabot PRs: they bump dependencies (touching uv.lock / + # pyproject.toml) without bumping the package version, so the increment + # check would always fail. Package-version bumps come from maintainer PRs. + if: github.event.pull_request.user.login != 'dependabot[bot]' runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/pyproject.toml b/pyproject.toml index 0fc8167..b97b217 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.1.1" +version = "3.1.2" requires-python = ">= 3.9" dependencies = [ 'requests', diff --git a/socketdev/version.py b/socketdev/version.py index d539d50..911557b 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.1.1" +__version__ = "3.1.2" diff --git a/uv.lock b/uv.lock index 9c96b4d..29be8fa 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,8 @@ revision = 3 requires-python = ">=3.9" resolution-markers = [ "python_full_version >= '3.10'", - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] [[package]] @@ -331,7 +332,8 @@ name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, @@ -370,7 +372,8 @@ name = "coverage" version = "7.10.7" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } wheels = [ @@ -593,51 +596,51 @@ toml = [ [[package]] name = "cryptography" -version = "46.0.5" +version = "46.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, - { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, - { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, - { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, - { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, - { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, - { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, - { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" }, - { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" }, - { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" }, - { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" }, - { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" }, - { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" }, - { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" }, - { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, - { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, - { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, - { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, - { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, - { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, - { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, - { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, - { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, - { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, - { url = "https://files.pythonhosted.org/packages/e9/6f/6cc6cc9955caa6eaf83660b0da2b077c7fe8ff9950a3c5e45d605038d439/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a", size = 4218321, upload-time = "2026-02-10T19:18:22.349Z" }, - { url = "https://files.pythonhosted.org/packages/3e/5d/c4da701939eeee699566a6c1367427ab91a8b7088cc2328c09dbee940415/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356", size = 4381786, upload-time = "2026-02-10T19:18:24.529Z" }, - { url = "https://files.pythonhosted.org/packages/ac/97/a538654732974a94ff96c1db621fa464f455c02d4bb7d2652f4edc21d600/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da", size = 4217990, upload-time = "2026-02-10T19:18:25.957Z" }, - { url = "https://files.pythonhosted.org/packages/ae/11/7e500d2dd3ba891197b9efd2da5454b74336d64a7cc419aa7327ab74e5f6/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257", size = 4381252, upload-time = "2026-02-10T19:18:27.496Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, + { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, + { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, + { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, + { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, + { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" }, + { url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" }, + { url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" }, + { url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" }, + { url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" }, + { url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" }, + { url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, + { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, + { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, + { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ea/075aac6a84b7c271578d81a2f9968acb6e273002408729f2ddff517fed4a/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15", size = 4219700, upload-time = "2026-04-08T01:57:40.625Z" }, + { url = "https://files.pythonhosted.org/packages/6c/7b/1c55db7242b5e5612b29fc7a630e91ee7a6e3c8e7bf5406d22e206875fbd/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455", size = 4385982, upload-time = "2026-04-08T01:57:42.725Z" }, + { url = "https://files.pythonhosted.org/packages/cb/da/9870eec4b69c63ef5925bf7d8342b7e13bc2ee3d47791461c4e49ca212f4/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65", size = 4219115, upload-time = "2026-04-08T01:57:44.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/72/05aa5832b82dd341969e9a734d1812a6aadb088d9eb6f0430fc337cc5a8f/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968", size = 4385479, upload-time = "2026-04-08T01:57:46.86Z" }, ] [[package]] @@ -675,7 +678,8 @@ name = "filelock" version = "3.19.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } wheels = [ @@ -708,7 +712,8 @@ name = "hatch" version = "1.15.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -769,7 +774,8 @@ name = "hatchling" version = "1.27.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "packaging", marker = "python_full_version < '3.10'" }, @@ -856,11 +862,11 @@ wheels = [ [[package]] name = "idna" -version = "3.11" +version = "3.17" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/28/99c51f664567218d824af024c0251650fb27e4ca066df188dab0769c5b91/idna-3.17.tar.gz", hash = "sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f", size = 196048, upload-time = "2026-05-28T14:32:38.55Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/de/a7/f76514cc40ad6234098ecdebda08732d75964776c51a42845b7da10649e2/idna-3.17-py3-none-any.whl", hash = "sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c", size = 65316, upload-time = "2026-05-28T14:32:37.035Z" }, ] [[package]] @@ -880,7 +886,8 @@ name = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ @@ -968,7 +975,8 @@ name = "markdown-it-py" version = "3.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "mdurl", marker = "python_full_version < '3.10'" }, @@ -1079,7 +1087,8 @@ name = "platformdirs" version = "4.4.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } wheels = [ @@ -1127,11 +1136,11 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] @@ -1148,7 +1157,8 @@ name = "pytest" version = "8.4.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, @@ -1305,7 +1315,8 @@ name = "secretstorage" version = "3.3.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.10'", + "python_full_version > '3.9' and python_full_version < '3.10'", + "python_full_version <= '3.9'", ] dependencies = [ { name = "cryptography", marker = "python_full_version < '3.10'" }, @@ -1343,7 +1354,7 @@ wheels = [ [[package]] name = "socketdev" -version = "3.1.1" +version = "3.1.2" source = { editable = "." } dependencies = [ { name = "requests" }, @@ -1508,28 +1519,28 @@ wheels = [ [[package]] name = "uv" -version = "0.9.21" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e2/2b/4e2090bc3a6265b445b3d31ca6fff20c6458d11145069f7e48ade3e2d75b/uv-0.9.21.tar.gz", hash = "sha256:aa4ca6ccd68e81b5ebaa3684d3c4df2b51a982ac16211eadf0707741d36e6488", size = 3834762, upload-time = "2025-12-30T16:12:51.927Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/26/0750c5bb1637ebefe1db0936dc76ead8ce97f17368cda950642bfd90fa3f/uv-0.9.21-py3-none-linux_armv6l.whl", hash = "sha256:0b330eaced2fd9d94e2a70f3bb6c8fd7beadc9d9bf9f1227eb14da44039c413a", size = 21266556, upload-time = "2025-12-30T16:12:47.311Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ef/f019466c1e367ea68003cf35f4d44cc328694ed4a59b6004aa7dcacb2b35/uv-0.9.21-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1d8e0940bddd37a55f4479d61adaa6b302b780d473f037fc084e48b09a1678e7", size = 20485648, upload-time = "2025-12-30T16:12:15.746Z" }, - { url = "https://files.pythonhosted.org/packages/2a/41/f735bd9a5b4848b6f4f1028e6d768f581559d68eddb6403eb0f19ca4c843/uv-0.9.21-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cb420ddab7bcdd12c2352d4b551ced428d104311c0b98ce205675ab5c97072db", size = 18986976, upload-time = "2025-12-30T16:12:25.034Z" }, - { url = "https://files.pythonhosted.org/packages/9a/5f/01d537e05927594dc379ff8bc04f8cde26384d25108a9f63758eae2a7936/uv-0.9.21-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a36d164438a6310c9fceebd041d80f7cffcc63ba80a7c83ee98394fadf2b8545", size = 20819312, upload-time = "2025-12-30T16:12:41.802Z" }, - { url = "https://files.pythonhosted.org/packages/18/89/9497395f57e007a2daed8172042ecccade3ff5569fd367d093f49bd6a4a8/uv-0.9.21-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c0ad83ce874cbbf9eda569ba793a9fb70870db426e9862300db8cf2950a7fe3b", size = 20900227, upload-time = "2025-12-30T16:12:19.242Z" }, - { url = "https://files.pythonhosted.org/packages/04/61/a3f6dfc75d278cce96b370e00b6f03d73ec260e5304f622504848bad219d/uv-0.9.21-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9076191c934b813147060e4cd97e33a58999de0f9c46f8ac67f614e154dae5c8", size = 21965424, upload-time = "2025-12-30T16:12:01.589Z" }, - { url = "https://files.pythonhosted.org/packages/18/3e/344e8c1078cfea82159c6608b8694f24fdfe850ce329a4708c026cb8b0ff/uv-0.9.21-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2ce0f6aca91f7fbf1192e43c063f4de3666fd43126aacc71ff7d5a79f831af59", size = 23540343, upload-time = "2025-12-30T16:12:13.139Z" }, - { url = "https://files.pythonhosted.org/packages/7f/20/5826659a81526687c6e5b5507f3f79f4f4b7e3022f3efae2ba36b19864c3/uv-0.9.21-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b4817642d5ef248b74ca7be3505e5e012a06be050669b80d1f7ced5ad50d188", size = 23171564, upload-time = "2025-12-30T16:12:22.219Z" }, - { url = "https://files.pythonhosted.org/packages/a6/8d/404c54e019bb99ce474dc21e6b96c8a1351ba3c06e5e19fd8dcae0ba1899/uv-0.9.21-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb42237fa309d79905fb73f653f63c1fe45a51193411c614b13512cf5506df3", size = 22202400, upload-time = "2025-12-30T16:12:04.612Z" }, - { url = "https://files.pythonhosted.org/packages/1a/f0/aa3d0081a2004050564364a1ef3277ddf889c9989a7278c0a9cce8284926/uv-0.9.21-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1d22f0ac03635d661e811c69d7c0b292751f90699acc6a1fb1509e17c936474", size = 22206448, upload-time = "2025-12-30T16:12:30.626Z" }, - { url = "https://files.pythonhosted.org/packages/fc/a9/7a375e723a588f31f305ddf9ae2097af0b9dc7f7813641788b5b9764a237/uv-0.9.21-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:cdd805909d360ad67640201376c8eb02de08dcf1680a1a81aebd9519daed6023", size = 20940568, upload-time = "2025-12-30T16:12:27.533Z" }, - { url = "https://files.pythonhosted.org/packages/18/d5/6187ffb7e1d24df34defe2718db8c4c3c08f153d3e7da22c250134b79cd1/uv-0.9.21-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:82e438595a609cbe4e45c413a54bd5756d37c8c39108ce7b2799aff15f7d3337", size = 22085077, upload-time = "2025-12-30T16:12:10.153Z" }, - { url = "https://files.pythonhosted.org/packages/ee/fa/8e211167d0690d9f15a08da610a0383d2f43a6c838890878e14948472284/uv-0.9.21-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:fc1c06e1e5df423e1517e350ea2c9d85ecefd0919188a0a9f19bd239bbbdeeaf", size = 20862893, upload-time = "2025-12-30T16:12:49.87Z" }, - { url = "https://files.pythonhosted.org/packages/33/b2/9d24d84cb9a1a6a5ea98d03a29abf800d87e5710d25e53896dc73aeb63a5/uv-0.9.21-py3-none-musllinux_1_1_i686.whl", hash = "sha256:9ef3d2a213c7720f4dae336e5123fe88427200d7523c78091c4ab7f849c3f13f", size = 21428397, upload-time = "2025-12-30T16:12:07.483Z" }, - { url = "https://files.pythonhosted.org/packages/4f/40/1e8e4c2e1308432c708eaa66dccdb83d2ee6120ea2b7d65e04fc06f48ff8/uv-0.9.21-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:8da20914d92ba4cc35f071414d3da7365294fc0b7114da8ac2ab3a86c695096f", size = 22450537, upload-time = "2025-12-30T16:12:33.36Z" }, - { url = "https://files.pythonhosted.org/packages/18/b8/99c4731d001f512e844dfdc740db2bf2fea56d538749b639d21f5117a74a/uv-0.9.21-py3-none-win32.whl", hash = "sha256:e716e23bc0ec8cbb0811f99e653745e0cf15223e7ba5d8857d46be5b40b3045b", size = 20032654, upload-time = "2025-12-30T16:12:36.007Z" }, - { url = "https://files.pythonhosted.org/packages/29/6b/da441bf335f5e1c0c100b7dfb9702b6fed367ba703e543037bf1e70bf8c3/uv-0.9.21-py3-none-win_amd64.whl", hash = "sha256:64a7bb0e4e6a4c2d98c2d55f42aead7c2df0ceb17d5911d1a42b76228cab4525", size = 22206744, upload-time = "2025-12-30T16:12:38.953Z" }, - { url = "https://files.pythonhosted.org/packages/98/02/afbed8309fe07aaa9fa58a98941cebffbcd300fe70499a02a6806d93517b/uv-0.9.21-py3-none-win_arm64.whl", hash = "sha256:6c13c40966812f6bd6ecb6546e5d3e27e7fe9cefa07018f074f51d703cb29e1c", size = 20591604, upload-time = "2025-12-30T16:12:44.634Z" }, +version = "0.11.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/8e/ec34c19d0f254fcbcc5c1ce8c7f06e47e0f69a7e1a0269c1d59cb0b0f279/uv-0.11.17.tar.gz", hash = "sha256:1d1be74deec997db1dda05a7e67541c904d65cbfd72e455d3c0a2a1e4bf2cddf", size = 4203607, upload-time = "2026-05-28T20:39:47.707Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/2e/e6d42f9d39009eee976f1e5dfd31d3d1943e6e593ad7b191cf11e9744a36/uv-0.11.17-py3-none-linux_armv6l.whl", hash = "sha256:8426bfe315564d414cbc5ba5467595dc6348965e19acec742914f47da3ff269f", size = 23551216, upload-time = "2026-05-28T20:39:05.395Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ee/d72bcc60f3585653a4b768425854d737d98d65c1765547d25c2999547ea9/uv-0.11.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6d1a033cc68cabb4141d6c1e3b66ffc6e970b98ba42e210f33270251e0bd8697", size = 22997377, upload-time = "2026-05-28T20:39:25.21Z" }, + { url = "https://files.pythonhosted.org/packages/58/34/1bc69798d9ae998fbc42c61b02883f2ba00d04bdd858e589604d01846287/uv-0.11.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:58c07ffc272c847d29cd98ca5082fa4304a645f87c718ec900e3cca9026bd096", size = 21630197, upload-time = "2026-05-28T20:39:28.935Z" }, + { url = "https://files.pythonhosted.org/packages/6b/93/1be48ec6a8933d9a77d0ce5240ed63f68869f68517ccf5d62268ed03f3e8/uv-0.11.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:036d6e2940afe8b79637530b01b9241d8cfd174b07f1179a1ebbd42409c38ca3", size = 23414940, upload-time = "2026-05-28T20:39:55.015Z" }, + { url = "https://files.pythonhosted.org/packages/00/31/b7488ff49d80090ea9d05d67a4d381a1b4479502e9853e654caa1c1c678e/uv-0.11.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:283186700c3e65a4644a73a917232da7d3e4a94d25ea0377a44f5b263fa49577", size = 23096330, upload-time = "2026-05-28T20:39:01.284Z" }, + { url = "https://files.pythonhosted.org/packages/fe/95/42b6137c5de06278d229c7eef2f314df2a738cd799795bbb44dace21bd6e/uv-0.11.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f2e44dfbfc7778d0d90edc6738f237c91e5e37e4e3cfe94c8a312cec56a41485", size = 23101906, upload-time = "2026-05-28T20:39:17.149Z" }, + { url = "https://files.pythonhosted.org/packages/17/7c/0ca03b2d19965db6d5dfe0c8cf96a3d0b424503c8cbc3cd2ffdc5869a15d/uv-0.11.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a817eeb3026f27a53d3f4b7855a5105f6787dd192140e201eda4d2b9a11b72e", size = 24444409, upload-time = "2026-05-28T20:39:59.218Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fb/179f55a3b19d47c30ec1f41b9b964da74dfa7053ff310a70a9c4d8cb998d/uv-0.11.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf8f5ad959583dcd2c4ae445c754a97c05700246ff89259f3fd285c9c20f4c00", size = 25540153, upload-time = "2026-05-28T20:39:09.535Z" }, + { url = "https://files.pythonhosted.org/packages/f7/29/592f42012765c43ae45c112110e214bca7b0cfc08c4c1b52e1dfa47dedd5/uv-0.11.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce16892a45134d20165c1ceababe06f3e9ce6a58902db1eff812c8c93626823f", size = 24665906, upload-time = "2026-05-28T20:39:41.254Z" }, + { url = "https://files.pythonhosted.org/packages/0e/51/b75808766f895248553c6370968509cd4f726e6943e310a8f7a171036ad0/uv-0.11.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da839e5a491c9a701d7d327a199cafc76ac27a03ac84fd2a8d4bf32c3af2448", size = 24863325, upload-time = "2026-05-28T20:39:51.006Z" }, + { url = "https://files.pythonhosted.org/packages/ee/6a/6f27ee69e97f480104bb8ec335f04c2a12add98edfcc4844a68e9538b6e2/uv-0.11.17-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:ec004b3c9bf9cb7756067ad1bd0bf64eb843e6fa2edbfbb3135ee152c14cea91", size = 23521674, upload-time = "2026-05-28T20:38:55.869Z" }, + { url = "https://files.pythonhosted.org/packages/df/11/1344aca7c710f794750f74de0e552a54ab24193ecc01fa3b3ae22ff822a1/uv-0.11.17-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:659227cac719b618cc91e02be9e274ad5bd72d74fa278123e6373537e9f28216", size = 24224725, upload-time = "2026-05-28T20:39:32.945Z" }, + { url = "https://files.pythonhosted.org/packages/ad/44/7b11550c1453ea13b81e549c83523e6ab6ed3231d09b2fd6b9eb19acceaf/uv-0.11.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e301d844eed9401f0f0351de12c55f1306ca05372acb0f28d35717c8ba663a22", size = 24301643, upload-time = "2026-05-28T20:39:45.183Z" }, + { url = "https://files.pythonhosted.org/packages/1a/36/8f683bc60547b8f93d0e752a8574d13fad776999cb978482b360c053ca22/uv-0.11.17-py3-none-musllinux_1_1_i686.whl", hash = "sha256:f0bf483c0d9fa14283992d56061b498b9d3d4adebd285af8744dc33f64dadfba", size = 23786049, upload-time = "2026-05-28T20:39:20.999Z" }, + { url = "https://files.pythonhosted.org/packages/10/dc/7a495db39c2970de4fa375c337dbd617b16780911f88f0511f8fe7f6747c/uv-0.11.17-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:2ccd5487a4a192bc832ea04c867a26883757db8fdfe88bed85d8129c82f9e505", size = 25049786, upload-time = "2026-05-28T20:40:03.292Z" }, + { url = "https://files.pythonhosted.org/packages/37/dd/74eff72d749eaf7e19f489878e21a368a7fef58d26ea0c63ec044ecd78b1/uv-0.11.17-py3-none-win32.whl", hash = "sha256:12b701fa32c5be3691759a73956e4462f30fa7b0dfa52ec66cb305bbb6ea4129", size = 22479213, upload-time = "2026-05-28T20:39:13.316Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/8af4a92b99a8a4823297c26df727fe957267e03e1196e3caa803c3f6ccb2/uv-0.11.17-py3-none-win_amd64.whl", hash = "sha256:44ec1fe3af839f87370dcf0400c0cab917cc1ce697d563e860fc7d9ed72655e7", size = 25083161, upload-time = "2026-05-28T20:40:07.931Z" }, + { url = "https://files.pythonhosted.org/packages/00/76/a689077832d585d29d87f9cd0d65eca1af58abd29a4eab004d0a8a858b9c/uv-0.11.17-py3-none-win_arm64.whl", hash = "sha256:37c915bfcf86f99c1c5be7c9ed21e0d80624067ba47bc8916a3cb0530bc94d27", size = 23544936, upload-time = "2026-05-28T20:39:37.137Z" }, ] [[package]] From 64d5b0604766ab1a55bb7e4cb388de2821bc7453 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:43:33 -0400 Subject: [PATCH 15/20] chore(deps): bump the python-minor-patch group with 2 updates (#88) Bumps the python-minor-patch group with 2 updates: [ruff](https://github.com/astral-sh/ruff) and [pytest-cov](https://github.com/pytest-dev/pytest-cov). Updates `ruff` from 0.14.10 to 0.15.14 - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.14.10...0.15.14) Updates `pytest-cov` from 7.0.0 to 7.1.0 - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v7.0.0...v7.1.0) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.14 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-patch - dependency-name: pytest-cov dependency-version: 7.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-minor-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 49 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/uv.lock b/uv.lock index 29be8fa..18c95ab 100644 --- a/uv.lock +++ b/uv.lock @@ -1197,7 +1197,7 @@ wheels = [ [[package]] name = "pytest-cov" -version = "7.0.0" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.10'" }, @@ -1206,9 +1206,9 @@ dependencies = [ { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, ] [[package]] @@ -1286,28 +1286,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" }, - { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" }, - { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" }, - { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" }, - { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" }, - { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" }, - { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" }, - { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" }, - { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" }, - { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" }, - { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" }, - { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" }, - { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" }, - { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" }, - { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" }, - { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" }, - { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" }, - { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, +version = "0.15.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/8a/8bce2894573e9dae6ff4d77fe34ad727d79b9e6238ad288c5638990d90f6/ruff-0.15.14.tar.gz", hash = "sha256:48e866b165be4a9bdbf310f7d3c9a07edef2fe8cd63ffeb4e00bb590506ebf9f", size = 4700910, upload-time = "2026-05-21T14:34:55.177Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/c8/74a92c6ff9fcfb4f1f947126d3ebee8389276e161ecc85de5bda7cda51bd/ruff-0.15.14-py3-none-linux_armv6l.whl", hash = "sha256:8dd2db9416e487c8d4b01fa7056bb02c4d05969d4f8d17a08c229c2f4ff3c108", size = 10739177, upload-time = "2026-05-21T14:34:37.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/254a35c20acc38a7223c9d2d594af12e794432464f2cdeb52af1dc4a892d/ruff-0.15.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:be4ff55af755bd71a00ab3dc6bd7ffc467bd76e0df6881e286c2e3d23e8fb43b", size = 11144969, upload-time = "2026-05-21T14:34:43.978Z" }, + { url = "https://files.pythonhosted.org/packages/56/9e/d13e40f83b8d0a94430e6778ce1d94a43b38cf2efe63278bdd2b4c65abbf/ruff-0.15.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:48d5909d7d06276ce7dde6d32bfa4b0d4cb2651145cd8ee4b440722cbc77832f", size = 10478207, upload-time = "2026-05-21T14:34:48.378Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f1/b15a7839fa4f332f8acec78e20564f26bb2d866e3d21710b877fd0263000/ruff-0.15.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca8cbfa94c4f90984a67561978602746d4cd27103568f745fa90eee3f0d4107d", size = 10818459, upload-time = "2026-05-21T14:34:22.318Z" }, + { url = "https://files.pythonhosted.org/packages/45/33/53d651177f84f94b400a0e27f8824eeada3dddc9d5ee8aeb048f4352a520/ruff-0.15.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a6bbc0333f1ab053423bcbf6226477d266ca7cec7738c4c8e3f55647803f3c4", size = 10541800, upload-time = "2026-05-21T14:34:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/868f87e0bf9786ed24b5d0d0ad8676b8a94fd1912f42cddf9cfc7857818a/ruff-0.15.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a24a4f7605d7003a6674d4387651effd939dead3fddd0f36561eb77a9a2e542", size = 11342149, upload-time = "2026-05-21T14:34:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/a7/8b/38cd5c19faffdcc05a408d2b78edccc69492ab9720eadb49ea15ef80d768/ruff-0.15.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:049b5326e53ed80978f2fc041a280603f69dd6b0c95464342a2bb4572d9d9e2f", size = 12212563, upload-time = "2026-05-21T14:34:28.579Z" }, + { url = "https://files.pythonhosted.org/packages/3e/4d/a3c5b874a556d5731e3e657aaf04311bb76f0a5c3ec220ed43051be6b64b/ruff-0.15.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4ed42e6696c8dfa5f06728e6441993901f548eb92d73bc472cb5a38d1395fbf", size = 11493299, upload-time = "2026-05-21T14:34:41.836Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c0/56472c251d09858a53e51efbd485b09e1995d8731668b76d52e5dd6ee0f1/ruff-0.15.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:715c543cf450c4888251f91c52f1942a800541d9bddd7ac060aa4e6b77ae7cba", size = 11455931, upload-time = "2026-05-21T14:34:57.276Z" }, + { url = "https://files.pythonhosted.org/packages/2c/4a/e2e7b4d8dbf233d4eace59c75bc3435fa6d8bd3bae82d351d4e4300c0fd1/ruff-0.15.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ebab6013ec887d439d8b7593737a0a4ffb06d45d209d4e4bf2e92813082d3f", size = 11400794, upload-time = "2026-05-21T14:34:39.773Z" }, + { url = "https://files.pythonhosted.org/packages/97/c7/83c0539fe34c3e09136204d1e75d6052492364e0b3cb05e9465423f567d7/ruff-0.15.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:49072d36abdbe97a8dd7f480afe9c675699c0c495d4c84076e2c1203c4550581", size = 10804759, upload-time = "2026-05-21T14:34:31.045Z" }, + { url = "https://files.pythonhosted.org/packages/86/a6/18f2bfc095a2ab4a78745644e428205532ce6653a5d0fa8501572891534d/ruff-0.15.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:958522aee105068640c2c2ceae08f413ae44d922f52a1374ac13d6a96032fc93", size = 10539517, upload-time = "2026-05-21T14:34:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/54/3a/5a8b3b69c654d4e4bf1d246ac5b49cbcdac6eaab6905925f8915f31e3b80/ruff-0.15.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f3707da619a143a2e8830e2abab8224478d69ace2d28cb6c20543ae97c36bf61", size = 11065169, upload-time = "2026-05-21T14:34:24.484Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c5/8864e4e7925b836ea354b31d57641ec03830564e281a8b6f061f8c3e0ec1/ruff-0.15.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bb01d645694e3ec0102105d07ef2d53703970407d59c04e59d3ba0b7a1d53553", size = 11560214, upload-time = "2026-05-21T14:34:50.975Z" }, + { url = "https://files.pythonhosted.org/packages/36/38/012bf76752e1f89ed50b77b99532d90f3a3e287bc7918e1fc0948ac866ac/ruff-0.15.14-py3-none-win32.whl", hash = "sha256:6d0c1ad2a0ab718d39b6d8fd2217981ce4d625cd96a720095f798fb47d8b13e6", size = 10805548, upload-time = "2026-05-21T14:34:33.453Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/4ea2c170f10ad760fff2a5250beb18897719dc8b52b53a24cddbb9dd3f19/ruff-0.15.14-py3-none-win_amd64.whl", hash = "sha256:802342981e056db3851a7836e5b070f8f15f67d4a685ae2a6160939d364b2902", size = 11939523, upload-time = "2026-05-21T14:34:18.077Z" }, + { url = "https://files.pythonhosted.org/packages/62/d5/bc97ff895ec35cf3925d4bd60f3b39d822f377a446906ec9bcc87405e59b/ruff-0.15.14-py3-none-win_arm64.whl", hash = "sha256:ff47b90a9ef6a40c9e2f3b479c1fb78531adf055b94c1eba0a7ba04b31951826", size = 11208607, upload-time = "2026-05-21T14:34:26.525Z" }, ] [[package]] From 8ffef98328c446acc06362da83237624690f57e8 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Tue, 2 Jun 2026 22:26:08 -0400 Subject: [PATCH 16/20] feat: add OTHER category to SocketCategory enum (CE-225) (#85) The Socket backend returns "other" as an alert category. Since v3.0.33 (commit 065407a, #79) the SDK tolerates unknown categories via a try/except fallback in SocketAlert.from_dict, but that path logs a warning that confused customers (Anthropic/Buildkite, FINRA/GitLab CI) into reporting it as a crash. Add OTHER = "other" so the value is recognized as a first-class category and the warning no longer fires. The defensive fallback is retained for any future unknown categories. Bump to 3.2.0 and sync uv.lock. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- pyproject.toml | 2 +- socketdev/fullscans/__init__.py | 1 + socketdev/version.py | 2 +- tests/unit/test_socket_alert_category.py | 10 ++++++++-- uv.lock | 2 +- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b97b217..d6e5f17 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.1.2" +version = "3.2.0" requires-python = ">= 3.9" dependencies = [ 'requests', diff --git a/socketdev/fullscans/__init__.py b/socketdev/fullscans/__init__.py index f295237..dd4c6b7 100644 --- a/socketdev/fullscans/__init__.py +++ b/socketdev/fullscans/__init__.py @@ -31,6 +31,7 @@ class SocketCategory(str, Enum): VULNERABILITY = "vulnerability" LICENSE = "license" MISCELLANEOUS = "miscellaneous" + OTHER = "other" # Added to match backend API responses class DiffType(str, Enum): diff --git a/socketdev/version.py b/socketdev/version.py index 911557b..1173108 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.1.2" +__version__ = "3.2.0" diff --git a/tests/unit/test_socket_alert_category.py b/tests/unit/test_socket_alert_category.py index 02ce1c1..c55113b 100644 --- a/tests/unit/test_socket_alert_category.py +++ b/tests/unit/test_socket_alert_category.py @@ -33,8 +33,14 @@ def test_known_category_is_preserved(self): self.assertEqual(alert.category, SocketCategory.SUPPLY_CHAIN_RISK) self.assertEqual(alert.severity, SocketIssueSeverity.LOW) - def test_unknown_category_falls_back_to_miscellaneous(self): + def test_other_category_is_recognized(self): + # "other" is a known backend category as of CE-225; it should resolve to + # SocketCategory.OTHER rather than falling back to MISCELLANEOUS. alert = SocketAlert.from_dict(self._base_payload("other")) + self.assertEqual(alert.category, SocketCategory.OTHER) + + def test_unknown_category_falls_back_to_miscellaneous(self): + alert = SocketAlert.from_dict(self._base_payload("somethingCompletelyNew")) self.assertEqual(alert.category, SocketCategory.MISCELLANEOUS) def test_unknown_category_does_not_raise(self): @@ -46,7 +52,7 @@ def test_unknown_category_does_not_raise(self): def test_unknown_category_emits_warning(self): with self.assertLogs("socketdev", level=logging.WARNING) as captured: - SocketAlert.from_dict(self._base_payload("other")) + SocketAlert.from_dict(self._base_payload("somethingCompletelyNew")) self.assertTrue( any("Unknown SocketCategory" in message for message in captured.output), f"expected a warning about the unknown category, got: {captured.output}", diff --git a/uv.lock b/uv.lock index 18c95ab..98f3daf 100644 --- a/uv.lock +++ b/uv.lock @@ -1353,7 +1353,7 @@ wheels = [ [[package]] name = "socketdev" -version = "3.1.2" +version = "3.2.0" source = { editable = "." } dependencies = [ { name = "requests" }, From 7fdb3ac55083af267a25077b6024c47368478d2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 22:43:57 -0400 Subject: [PATCH 17/20] ci(deps): bump actions/setup-python from 5.2.0 to 6.2.0 (#86) * ci(deps): bump actions/setup-python from 5.2.0 to 6.2.0 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.2.0 to 6.2.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/f677139bbe7f9c59b41e40162b753c062f5d49a3...a309ff8b426b58ec0e2a45f0f869d46889d02405) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Add version number as comment * Add version number as comment --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: lelia <2418071+lelia@users.noreply.github.com> --- .github/workflows/pr-preview.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index f45ff55..c697092 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -28,7 +28,7 @@ jobs: with: fetch-depth: 0 persist-credentials: false - - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.13' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a2a940e..322ae27 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: with: fetch-depth: 0 persist-credentials: false - - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.13' From 6beea882e3283ad54a4a90f9f94a6ad767f0b39d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 22:45:31 -0400 Subject: [PATCH 18/20] ci(deps): bump actions/github-script from 7.0.1 to 9.0.0 (#87) * ci(deps): bump actions/github-script from 7.0.1 to 9.0.0 Bumps [actions/github-script](https://github.com/actions/github-script) from 7.0.1 to 9.0.0. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/60a0d83039c74a4aee543508d2ffcb1c3799cdea...3a2844b7e9c422d3c10d287c895573f7108da1b3) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: 9.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Add version number as comment * Add version number as comment --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: lelia <2418071+lelia@users.noreply.github.com> --- .github/workflows/pr-preview.yml | 2 +- .github/workflows/version-check.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index c697092..73849d8 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -74,7 +74,7 @@ jobs: - name: Comment on PR if: steps.version_check.outputs.exists != 'true' - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: VERSION: ${{ env.VERSION }} with: diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml index e2c9292..b7f2871 100644 --- a/.github/workflows/version-check.yml +++ b/.github/workflows/version-check.yml @@ -88,7 +88,7 @@ jobs: fi - name: Manage PR Comment - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 if: always() env: MAIN_VERSION: ${{ env.MAIN_VERSION }} From 836936cad0455bf28c1e6515d61ddad585f881a1 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Wed, 3 Jun 2026 14:06:50 -0400 Subject: [PATCH 19/20] Add `sfw` aggregator gate to enforce required CI checks (#89) * ci: add Socket Firewall aggregator gate; bump 3.2.0 -> 3.2.1 Add a single sfw-gate job (if: always(), needs the conditional inspect + free/enterprise smoke + workflow-notice jobs) that fails only when an upstream job failed or was cancelled -- success and skipped both pass. This is the check intended to become the required status check on main: the smoke jobs are conditional (deps-changed gates them, and exactly one of free/enterprise runs per PR), so none can be required directly -- a required check whose job is if-skipped is never created and blocks merge forever. The gate is green when no deps change and is satisfied by whichever smoke path actually ran. NOT yet wired into branch protection -- added during a soak period so the check is visible before it becomes blocking, and so requiring it doesn't strand other open PRs. Pattern adapted from SocketDev/socket-python-cli #224. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> * ci: spell out 'iff' as 'if and only if' in gate comment Review feedback: 'iff' read as a typo. It is the logic shorthand for 'if and only if', but the comment exists to communicate, so spell it out. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --------- Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- .github/workflows/dependency-review.yml | 49 +++++++++++++++++++++++++ pyproject.toml | 2 +- socketdev/version.py | 2 +- uv.lock | 2 +- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index ea0acee..fd1d8fc 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -279,3 +279,52 @@ jobs: echo "This PR changes workflow, composite-action, or dependabot config files." echo "Require explicit human review before merge." } >> "$GITHUB_STEP_SUMMARY" + + # Aggregator gate -- the single check intended to become the required status + # check on main. The Socket Firewall smoke jobs are conditional (deps-changed + # gates them, and exactly one of free/enterprise runs per PR), so neither can + # be required directly: a required check whose job is `if:`-skipped is never + # created and sits at "Expected -- Waiting for status to be reported" + # forever, permanently blocking merge (this hits every Dependabot/fork PR and + # every PR that doesn't touch deps). + # + # This job runs unconditionally (`if: always()`), depends on all the + # conditional jobs, and fails ONLY when one of them actually failed or was + # cancelled. A `skipped` dependency passes -- so the gate is green when no + # deps changed, and otherwise satisfied by whichever smoke path ran (free for + # Dependabot/forks, enterprise for trusted maintainers). A real Socket + # Firewall block surfaces as a smoke-job failure and thus a gate failure. + # + # NOT YET wired into branch protection -- added during a soak period so the + # check is visible before it becomes blocking. Requiring it before it lands + # on main would strand every other open PR on the trap above. + sfw-gate: + name: Socket Firewall Gate + needs: [inspect, python-sfw-smoke-free, python-sfw-smoke-enterprise, workflow-notice] + if: always() + runs-on: ubuntu-latest + timeout-minutes: 2 + steps: + - name: Evaluate dependency-review results + env: + NEEDS_JSON: ${{ toJSON(needs) }} + run: | + echo "$NEEDS_JSON" + # Fail if and only if a needed job reported failure or cancelled; + # success and skipped both pass. jq returns the count of offending + # results. + bad="$(printf '%s' "$NEEDS_JSON" \ + | jq '[to_entries[] | select(.value.result == "failure" or .value.result == "cancelled")] | length')" + + { + echo "## Socket Firewall Gate" + printf '%s\n' "$NEEDS_JSON" | jq -r 'to_entries[] | "- \(.key): \(.value.result)"' + } >> "$GITHUB_STEP_SUMMARY" + + if [ "$bad" -ne 0 ]; then + echo "Gate failed: $bad upstream job(s) failed or were cancelled." >> "$GITHUB_STEP_SUMMARY" + echo "::error::Socket Firewall Gate failed -- $bad upstream job(s) failed or were cancelled." + exit 1 + fi + + echo "Gate passed." >> "$GITHUB_STEP_SUMMARY" diff --git a/pyproject.toml b/pyproject.toml index d6e5f17..7c30914 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.2.0" +version = "3.2.1" requires-python = ">= 3.9" dependencies = [ 'requests', diff --git a/socketdev/version.py b/socketdev/version.py index 1173108..1da6a55 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.2.0" +__version__ = "3.2.1" diff --git a/uv.lock b/uv.lock index 98f3daf..6331649 100644 --- a/uv.lock +++ b/uv.lock @@ -1353,7 +1353,7 @@ wheels = [ [[package]] name = "socketdev" -version = "3.2.0" +version = "3.2.1" source = { editable = "." } dependencies = [ { name = "requests" }, From 273ee88c8880321762e299cb4e0e790f285fcb0e Mon Sep 17 00:00:00 2001 From: Martin Torp Date: Wed, 10 Jun 2026 13:37:25 +0200 Subject: [PATCH 20/20] Add transient-error classification to APIFailure (#93) * Add transient-error classification to APIFailure API.do_request now records the HTTP status code on every exception it raises (status_code attribute), and APIFailure gains is_transient_error(): True for gateway/connection-level failures (HTTP 408/502/503/504, dropped or reset connections, client-side timeouts) where retrying the same request may succeed, False for deterministic errors (400/401/403/404/429, wrapped unexpected errors). Classification is based on the recorded status code rather than exception class identity or message text, so it stays correct if a status code gains a dedicated subclass later. Motivated by SocketDev/socket-python-cli#232: the CLI retries transient full-scan upload failures and previously had to parse the status code out of catch-all APIFailure message text. * Hardcode 502 in APIBadGateway instead of accepting a status_code The class definitionally represents a 502, so there is no reason for construction sites to pass (or be able to override) the status. --- pyproject.toml | 2 +- socketdev/core/api.py | 18 ++-- socketdev/exceptions.py | 127 +++++++++++++++--------- socketdev/version.py | 2 +- tests/unit/test_exceptions.py | 176 ++++++++++++++++++++++++++++++++++ uv.lock | 2 +- 6 files changed, 268 insertions(+), 59 deletions(-) create mode 100644 tests/unit/test_exceptions.py diff --git a/pyproject.toml b/pyproject.toml index 7c30914..609daa2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.2.1" +version = "3.3.0" requires-python = ">= 3.9" dependencies = [ 'requests', diff --git a/socketdev/core/api.py b/socketdev/core/api.py index 575e086..8c35231 100644 --- a/socketdev/core/api.py +++ b/socketdev/core/api.py @@ -76,26 +76,26 @@ def format_headers(headers_dict): path_str = f"\nPath: {url}" if response.status_code == 401: - raise APIAccessDenied(f"Unauthorized{path_str}{headers_str}") + raise APIAccessDenied(f"Unauthorized{path_str}{headers_str}", status_code=401) if response.status_code == 403: try: error_message = response.json().get("error", {}).get("message", "") if "Insufficient permissions for API method" in error_message: log.error(f"{error_message}{path_str}{headers_str}") - raise APIInsufficientPermissions() + raise APIInsufficientPermissions(status_code=403) elif "Organization not allowed" in error_message: log.error(f"{error_message}{path_str}{headers_str}") - raise APIOrganizationNotAllowed() + raise APIOrganizationNotAllowed(status_code=403) elif "Insufficient max quota" in error_message: log.error(f"{error_message}{path_str}{headers_str}") - raise APIInsufficientQuota() + raise APIInsufficientQuota(status_code=403) else: - raise APIAccessDenied(f"{error_message or 'Access denied'}{path_str}{headers_str}") + raise APIAccessDenied(f"{error_message or 'Access denied'}{path_str}{headers_str}", status_code=403) except ValueError: - raise APIAccessDenied(f"Access denied{path_str}{headers_str}") + raise APIAccessDenied(f"Access denied{path_str}{headers_str}", status_code=403) if response.status_code == 404: log.error(f"Path not found {path}{path_str}{headers_str}") - raise APIResourceNotFound() + raise APIResourceNotFound(status_code=404) if response.status_code == 429: retry_after = response.headers.get("retry-after") if retry_after: @@ -109,7 +109,7 @@ def format_headers(headers_dict): else: time_msg = "" log.error(f"Insufficient quota for API route.{time_msg}{path_str}{headers_str}") - raise APIInsufficientQuota() + raise APIInsufficientQuota(status_code=429) if response.status_code == 502: log.error(f"Upstream server error{path_str}{headers_str}") raise APIBadGateway() @@ -124,7 +124,7 @@ def format_headers(headers_dict): f"Error message: {error_message}" ) log.error(error) - raise APIFailure(error) + raise APIFailure(error, status_code=response.status_code) return response except Timeout: diff --git a/socketdev/exceptions.py b/socketdev/exceptions.py index d61b827..980aaf9 100644 --- a/socketdev/exceptions.py +++ b/socketdev/exceptions.py @@ -1,47 +1,80 @@ -class APIFailure(Exception): - """Base exception for all Socket API errors""" - pass - - -class APIKeyMissing(APIFailure): - """Raised when the api key is not passed and the headers are empty""" - - -class APIAccessDenied(APIFailure): - """Raised when access is denied to the API""" - pass - - -class APIInsufficientPermissions(APIFailure): - """Raised when the API token doesn't have required permissions""" - pass - - -class APIOrganizationNotAllowed(APIFailure): - """Raised when organization doesn't have access to the feature""" - pass - - -class APIInsufficientQuota(APIFailure): - """Raised when access is denied to the API due to quota limits""" - pass - - -class APIResourceNotFound(APIFailure): - """Raised when the requested resource is not found""" - pass - - -class APITimeout(APIFailure): - """Raised when a request times out""" - pass - - -class APIConnectionError(APIFailure): - """Raised when there's a connection error""" - pass - - -class APIBadGateway(APIFailure): - """Raised when the upstream server returns a 502 Bad Gateway error""" - pass \ No newline at end of file +from typing import Optional + +# HTTP statuses classified as transient by APIFailure.is_transient_error(): gateway / +# availability failures where the request was dropped before the application produced a +# definitive response, so retrying the same request may succeed (408 Request Timeout, +# 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout). +TRANSIENT_HTTP_STATUS_CODES = frozenset({408, 502, 503, 504}) + + +class APIFailure(Exception): + """Base exception for all Socket API errors""" + + def __init__(self, *args, status_code: Optional[int] = None): + super().__init__(*args) + self.status_code = status_code + + def is_transient_error(self) -> bool: + """Whether this failure is transient, i.e. retrying the same request may succeed. + + Transient failures happen at the gateway/connection level - HTTP 408/502/503/504, + dropped or reset connections, and client-side timeouts - before the server produced + a definitive response. Deterministic errors (e.g. 400/401/403/404/429) are not + transient: retrying the same request fails the same way. Classification is based on + the HTTP status code recorded when the exception was raised (or overridden by + subclasses without an HTTP status, like timeouts), so it stays correct even if a + status code gains a dedicated exception subclass later. + """ + return self.status_code in TRANSIENT_HTTP_STATUS_CODES + + +class APIKeyMissing(APIFailure): + """Raised when the api key is not passed and the headers are empty""" + + +class APIAccessDenied(APIFailure): + """Raised when access is denied to the API""" + pass + + +class APIInsufficientPermissions(APIFailure): + """Raised when the API token doesn't have required permissions""" + pass + + +class APIOrganizationNotAllowed(APIFailure): + """Raised when organization doesn't have access to the feature""" + pass + + +class APIInsufficientQuota(APIFailure): + """Raised when access is denied to the API due to quota limits""" + pass + + +class APIResourceNotFound(APIFailure): + """Raised when the requested resource is not found""" + pass + + +class APITimeout(APIFailure): + """Raised when a request times out""" + + def is_transient_error(self) -> bool: + # No HTTP status: the request timed out client-side, so a retry may succeed. + return True + + +class APIConnectionError(APIFailure): + """Raised when there's a connection error""" + + def is_transient_error(self) -> bool: + # No HTTP status: the connection was dropped/reset mid-request, so a retry may succeed. + return True + + +class APIBadGateway(APIFailure): + """Raised when the upstream server returns a 502 Bad Gateway error""" + + def __init__(self, *args): + super().__init__(*args, status_code=502) diff --git a/socketdev/version.py b/socketdev/version.py index 1da6a55..88c513e 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.2.1" +__version__ = "3.3.0" diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py new file mode 100644 index 0000000..df621ad --- /dev/null +++ b/tests/unit/test_exceptions.py @@ -0,0 +1,176 @@ +""" +Unit tests for the SDK exception hierarchy and transient-error classification. + +`APIFailure.is_transient_error()` tells consumers whether retrying the same request may +succeed (gateway/connection-level failures: HTTP 408/502/503/504, dropped or reset +connections, client-side timeouts) or whether the failure is deterministic (400/401/403/ +404/429 and similar). Classification is based on the `status_code` recorded at raise time +inside `API.do_request`, so these tests cover both the exception classes themselves and +the status codes `do_request` attaches when raising them. + +Run with: python -m pytest tests/unit/ -v +""" + +import unittest +from unittest.mock import MagicMock, patch + +import requests + +from socketdev.core.api import API +from socketdev.exceptions import ( + APIAccessDenied, + APIBadGateway, + APIConnectionError, + APIFailure, + APIInsufficientPermissions, + APIInsufficientQuota, + APIOrganizationNotAllowed, + APIResourceNotFound, + APITimeout, +) + + +class TestIsTransientError(unittest.TestCase): + """Classification of exceptions constructed directly.""" + + def test_transient_statuses_on_catch_all_failure(self): + for status in (408, 502, 503, 504): + self.assertTrue(APIFailure("boom", status_code=status).is_transient_error()) + + def test_deterministic_statuses_on_catch_all_failure(self): + for status in (400, 401, 403, 404, 422, 429, 500): + self.assertFalse(APIFailure("boom", status_code=status).is_transient_error()) + + def test_no_status_code_is_not_transient(self): + # The wrapped-unexpected-error case: do_request raises a bare APIFailure(). + self.assertFalse(APIFailure().is_transient_error()) + self.assertFalse(APIFailure("boom").is_transient_error()) + + def test_connection_level_classes_are_transient(self): + self.assertTrue(APITimeout().is_transient_error()) + self.assertTrue(APIConnectionError().is_transient_error()) + self.assertTrue(APIBadGateway().is_transient_error()) + + def test_bad_gateway_carries_502_by_default(self): + self.assertEqual(APIBadGateway().status_code, 502) + + def test_dedicated_4xx_classes_are_not_transient(self): + self.assertFalse(APIAccessDenied("denied", status_code=401).is_transient_error()) + self.assertFalse(APIInsufficientPermissions(status_code=403).is_transient_error()) + self.assertFalse(APIOrganizationNotAllowed(status_code=403).is_transient_error()) + self.assertFalse(APIInsufficientQuota(status_code=429).is_transient_error()) + self.assertFalse(APIResourceNotFound(status_code=404).is_transient_error()) + + def test_subclass_with_transient_status_follows_the_status(self): + # Classification is by recorded status, not class identity: if a transient status + # ever gains a dedicated subclass, is_transient_error() keeps working unchanged. + class APIServiceUnavailable(APIFailure): + pass + + self.assertTrue(APIServiceUnavailable(status_code=503).is_transient_error()) + + def test_message_text_does_not_affect_classification(self): + self.assertFalse( + APIFailure("original_status_code:503 lookalike").is_transient_error() + ) + + def test_single_message_arg_is_preserved(self): + error = APIFailure("something broke", status_code=503) + self.assertEqual(str(error), "something broke") + + +def _mock_response(status_code, json_data=None, headers=None, text=""): + response = MagicMock() + response.status_code = status_code + response.headers = headers if headers is not None else {} + response.text = text + if json_data is None: + response.json.side_effect = ValueError("no json") + else: + response.json.return_value = json_data + return response + + +class TestDoRequestStatusCodes(unittest.TestCase): + """do_request attaches the HTTP status to the exceptions it raises.""" + + def setUp(self): + self.api = API() + self.api.encode_key("test-token") + + def _do_request_raising(self, expected_class, response=None, side_effect=None): + with patch("socketdev.core.api.requests.request") as mock_request: + if side_effect is not None: + mock_request.side_effect = side_effect + else: + mock_request.return_value = response + with self.assertRaises(expected_class) as ctx: + self.api.do_request("orgs/test/full-scans", method="POST") + return ctx.exception + + def test_401_access_denied_is_not_transient(self): + error = self._do_request_raising(APIAccessDenied, _mock_response(401)) + self.assertEqual(error.status_code, 401) + self.assertFalse(error.is_transient_error()) + + def test_403_insufficient_permissions_is_not_transient(self): + response = _mock_response( + 403, + json_data={"error": {"message": "Insufficient permissions for API method"}}, + ) + error = self._do_request_raising(APIInsufficientPermissions, response) + self.assertEqual(error.status_code, 403) + self.assertFalse(error.is_transient_error()) + + def test_404_not_found_is_not_transient(self): + error = self._do_request_raising(APIResourceNotFound, _mock_response(404)) + self.assertEqual(error.status_code, 404) + self.assertFalse(error.is_transient_error()) + + def test_429_quota_is_not_transient(self): + error = self._do_request_raising(APIInsufficientQuota, _mock_response(429)) + self.assertEqual(error.status_code, 429) + self.assertFalse(error.is_transient_error()) + + def test_502_bad_gateway_is_transient(self): + error = self._do_request_raising(APIBadGateway, _mock_response(502)) + self.assertEqual(error.status_code, 502) + self.assertTrue(error.is_transient_error()) + + def test_catch_all_transient_statuses(self): + for status in (408, 503, 504): + error = self._do_request_raising(APIFailure, _mock_response(status)) + self.assertIs(type(error), APIFailure) + self.assertEqual(error.status_code, status) + self.assertTrue(error.is_transient_error()) + + def test_catch_all_deterministic_statuses(self): + for status in (400, 500): + error = self._do_request_raising(APIFailure, _mock_response(status)) + self.assertIs(type(error), APIFailure) + self.assertEqual(error.status_code, status) + self.assertFalse(error.is_transient_error()) + + def test_timeout_is_transient(self): + error = self._do_request_raising( + APITimeout, side_effect=requests.exceptions.Timeout("timed out") + ) + self.assertIsNone(error.status_code) + self.assertTrue(error.is_transient_error()) + + def test_connection_error_is_transient(self): + error = self._do_request_raising( + APIConnectionError, + side_effect=requests.exceptions.ConnectionError("reset"), + ) + self.assertIsNone(error.status_code) + self.assertTrue(error.is_transient_error()) + + def test_unexpected_error_wrapped_without_status_is_not_transient(self): + error = self._do_request_raising(APIFailure, side_effect=RuntimeError("boom")) + self.assertIsNone(error.status_code) + self.assertFalse(error.is_transient_error()) + + +if __name__ == "__main__": + unittest.main() diff --git a/uv.lock b/uv.lock index 6331649..7397d8f 100644 --- a/uv.lock +++ b/uv.lock @@ -1353,7 +1353,7 @@ wheels = [ [[package]] name = "socketdev" -version = "3.2.1" +version = "3.3.0" source = { editable = "." } dependencies = [ { name = "requests" },