From dd62171f31aae83ff252cba6a26e7d11ef5d7842 Mon Sep 17 00:00:00 2001 From: Henry Su Date: Fri, 11 Sep 2026 13:16:12 -0500 Subject: [PATCH] fix(storage): send camelCase sort params in analytics list StorageAnalyticsClient.list sent sort_column and sort_order as query parameters. GET /iceberg/bucket only reads sortColumn and sortOrder and ignores unknown keys, so both options were silently dropped and buckets always came back in the default order. Use the wire names, matching the server schema and storage-js. Applies to the async client and its generated sync twin. Fixes #1635 --- src/storage/src/storage3/_async/analytics.py | 4 +- src/storage/src/storage3/_sync/analytics.py | 4 +- src/storage/tests/test_client.py | 63 ++++++++++++++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/storage/src/storage3/_async/analytics.py b/src/storage/src/storage3/_async/analytics.py index 5cce3799..dbd56408 100644 --- a/src/storage/src/storage3/_async/analytics.py +++ b/src/storage/src/storage3/_async/analytics.py @@ -35,8 +35,8 @@ async def list( params = dict( limit=limit, offset=offset, - sort_column=sort_column, - sort_order=sort_order, + sortColumn=sort_column, + sortOrder=sort_order, search=search, ) filtered_params = QueryParams( diff --git a/src/storage/src/storage3/_sync/analytics.py b/src/storage/src/storage3/_sync/analytics.py index 2bcd5c3c..e221d369 100644 --- a/src/storage/src/storage3/_sync/analytics.py +++ b/src/storage/src/storage3/_sync/analytics.py @@ -35,8 +35,8 @@ def list( params = dict( limit=limit, offset=offset, - sort_column=sort_column, - sort_order=sort_order, + sortColumn=sort_column, + sortOrder=sort_order, search=search, ) filtered_params = QueryParams( diff --git a/src/storage/tests/test_client.py b/src/storage/tests/test_client.py index 92f0ea01..13fb171d 100644 --- a/src/storage/tests/test_client.py +++ b/src/storage/tests/test_client.py @@ -409,3 +409,66 @@ async def test_async_bucket_proxy_exists_false_on_headless_error() -> None: proxy._client, "request", new_callable=AsyncMock, return_value=mock_response ): assert await proxy.exists("missing.txt") is False + + +def _analytics_list_handler(captured: Dict[str, Any]) -> Any: + def handler(request: Request) -> Response: + captured["url"] = request.url + return Response(200, json=[]) + + return handler + + +@pytest.mark.asyncio +async def test_async_analytics_list_sends_camel_case_sort_params( + valid_url, valid_headers +) -> None: + from httpx import MockTransport + + captured: Dict[str, Any] = {} + client = AsyncStorageClient( + url=valid_url + "/", + headers=valid_headers, + http_client=AsyncClient( + transport=MockTransport(_analytics_list_handler(captured)) + ), + ) + + await client.analytics().list( + limit=5, offset=10, sort_column="created_at", sort_order="desc", search="logs" + ) + + assert captured["url"].path.endswith("/iceberg/bucket") + assert dict(captured["url"].params) == { + "limit": "5", + "offset": "10", + "sortColumn": "created_at", + "sortOrder": "desc", + "search": "logs", + } + + +def test_sync_analytics_list_sends_camel_case_sort_params( + valid_url, valid_headers +) -> None: + from httpx import MockTransport + + captured: Dict[str, Any] = {} + client = SyncStorageClient( + url=valid_url + "/", + headers=valid_headers, + http_client=Client(transport=MockTransport(_analytics_list_handler(captured))), + ) + + client.analytics().list( + limit=5, offset=10, sort_column="created_at", sort_order="desc", search="logs" + ) + + assert captured["url"].path.endswith("/iceberg/bucket") + assert dict(captured["url"].params) == { + "limit": "5", + "offset": "10", + "sortColumn": "created_at", + "sortOrder": "desc", + "search": "logs", + }