From 975c8d55db6e8a2d50b8e0d60cc57e2fc77bd62a Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Fri, 30 Jan 2026 10:33:12 -0500 Subject: [PATCH 1/2] feat(api/list(): allow filtering the list of snaps This allows filtering the list of install snaps server side using the `snaps` request parameter to the API: https://snapcraft.io/docs/snapd-api#heading--snaps It is backwards compatible, as the snaps parameter is optional. Signed-off-by: Alex Lowe --- snap_http/api/snaps.py | 11 ++++++++--- tests/integration/test_snaps.py | 21 +++++++++++++++++++++ tests/unit/api/test_snaps.py | 17 +++++++++++++---- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/snap_http/api/snaps.py b/snap_http/api/snaps.py index 687300c..b6ef228 100644 --- a/snap_http/api/snaps.py +++ b/snap_http/api/snaps.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Literal, Optional, Union +from typing import Dict, List, Literal, Optional, Union, Iterable from .. import http from ..types import FileUpload, FormData, SnapdResponse @@ -244,12 +244,17 @@ def unhold_all(names: List[str]) -> SnapdResponse: return http.post("/snaps", {"action": "unhold", "snaps": names}) -def list() -> SnapdResponse: +def list(*, snaps: Optional[Iterable[str]] = None) -> SnapdResponse: """GETs a list of installed snaps. This stomps on builtins.list, so please import it namespaced. + + :param snaps: An optional iterable of snap names by which to filter. """ - return http.get("/snaps") + query_params = {} + if snaps is not None: + query_params["snaps"] = ",".join(snaps) + return http.get("/snaps", query_params=query_params) def list_all() -> SnapdResponse: diff --git a/tests/integration/test_snaps.py b/tests/integration/test_snaps.py index 228424a..d96edfe 100644 --- a/tests/integration/test_snaps.py +++ b/tests/integration/test_snaps.py @@ -1,3 +1,5 @@ +import pytest + import snap_http from tests.utils import get_snap_details, is_snap_installed, remove_assertion, wait_for @@ -9,6 +11,25 @@ def test_list_snaps(): assert "snapd" in installed_snaps +@pytest.mark.parametrize( + "snaps", + [ + {"snapd"}, + {"snapd", "core24"}, + ] +) +def test_list_snaps_with_names(snaps): + """Test listing snaps, filtering by name.""" + listed_snaps = {snap["name"] for snap in snap_http.list(snaps=snaps).result} + assert listed_snaps == snaps + + +def test_list_snaps_with_names_no_match(): + """Test listing snaps, filtering by name.""" + listed_snaps = {snap["name"] for snap in snap_http.list(snaps=["othsnaushoeatnlxbuehceaoemtapgi0ceuobhtkerobhgcio"]).result} + assert listed_snaps == set() + + def test_list_all_snaps(): """Test listing snaps.""" installed_snaps = {snap["name"] for snap in snap_http.list_all().result} diff --git a/tests/unit/api/test_snaps.py b/tests/unit/api/test_snaps.py index e009c1a..bcfb98a 100644 --- a/tests/unit/api/test_snaps.py +++ b/tests/unit/api/test_snaps.py @@ -876,8 +876,15 @@ def mock_post(path, body): with pytest.raises(http.SnapdHttpException): _ = api.unhold_all(["placeholder1", "placeholder2"]) - -def test_list(monkeypatch): +\ +@pytest.mark.parametrize( + ("snaps", "expected_params"), + [ + (None, {}), + (["snapd", "core24"], {"snaps": "snapd,core24"}), + ] +) +def test_list(monkeypatch, snaps, expected_params): """`api.list` returns a `types.SnapdResponse`.""" mock_response = types.SnapdResponse( type="sync", @@ -886,14 +893,16 @@ def test_list(monkeypatch): result=[{"title": "placeholder1"}, {"title": "placeholder2"}], ) - def mock_get(path): + def mock_get(path, query_params): assert path == "/snaps" + assert query_params == expected_params + return mock_response monkeypatch.setattr(http, "get", mock_get) - result = api.list() + result = api.list(snaps=snaps) assert result == mock_response From 9c8b08fedb949269cafe51a1e9c5ee936e2a1dba Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Fri, 30 Jan 2026 13:54:48 -0500 Subject: [PATCH 2/2] fix: typo --- tests/unit/api/test_snaps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/api/test_snaps.py b/tests/unit/api/test_snaps.py index bcfb98a..032aa3c 100644 --- a/tests/unit/api/test_snaps.py +++ b/tests/unit/api/test_snaps.py @@ -876,7 +876,7 @@ def mock_post(path, body): with pytest.raises(http.SnapdHttpException): _ = api.unhold_all(["placeholder1", "placeholder2"]) -\ + @pytest.mark.parametrize( ("snaps", "expected_params"), [