Skip to content

Commit ccc8cd5

Browse files
blackmad-cradleclaudegjtorikian
authored
fix: list_client_secrets returns raw list, not paginated response (#586)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Garen J. Torikian <gjtorikian@users.noreply.github.com>
1 parent eef9564 commit ccc8cd5

File tree

4 files changed

+29
-112
lines changed

4 files changed

+29
-112
lines changed

src/workos/connect.py

Lines changed: 14 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
from functools import partial
21
from typing import Optional, Protocol, Sequence
32

43
from workos.types.connect import ClientSecret, ConnectApplication
54
from workos.types.connect.connect_application import ApplicationType
6-
from workos.types.connect.list_filters import (
7-
ClientSecretListFilters,
8-
ConnectApplicationListFilters,
9-
)
5+
from workos.types.connect.list_filters import ConnectApplicationListFilters
6+
from workos.types.connect.redirect_uri_input import RedirectUriInput
107
from workos.types.list_resource import ListMetadata, ListPage, WorkOSListResource
118
from workos.typing.sync_or_async import SyncOrAsync
129
from workos.utils.http_client import AsyncHTTPClient, SyncHTTPClient
@@ -26,10 +23,6 @@
2623
ConnectApplication, ConnectApplicationListFilters, ListMetadata
2724
]
2825

29-
ClientSecretsListResource = WorkOSListResource[
30-
ClientSecret, ClientSecretListFilters, ListMetadata
31-
]
32-
3326

3427
class ConnectModule(Protocol):
3528
"""Offers methods through the WorkOS Connect service."""
@@ -76,7 +69,7 @@ def create_application(
7669
is_first_party: bool,
7770
description: Optional[str] = None,
7871
scopes: Optional[Sequence[str]] = None,
79-
redirect_uris: Optional[Sequence[str]] = None,
72+
redirect_uris: Optional[Sequence[RedirectUriInput]] = None,
8073
uses_pkce: Optional[bool] = None,
8174
organization_id: Optional[str] = None,
8275
) -> SyncOrAsync[ConnectApplication]:
@@ -104,7 +97,7 @@ def update_application(
10497
name: Optional[str] = None,
10598
description: Optional[str] = None,
10699
scopes: Optional[Sequence[str]] = None,
107-
redirect_uris: Optional[Sequence[str]] = None,
100+
redirect_uris: Optional[Sequence[RedirectUriInput]] = None,
108101
) -> SyncOrAsync[ConnectApplication]:
109102
"""Update a connect application.
110103
@@ -145,25 +138,14 @@ def create_client_secret(self, application_id: str) -> SyncOrAsync[ClientSecret]
145138
def list_client_secrets(
146139
self,
147140
application_id: str,
148-
*,
149-
limit: int = DEFAULT_LIST_RESPONSE_LIMIT,
150-
before: Optional[str] = None,
151-
after: Optional[str] = None,
152-
order: PaginationOrder = "desc",
153-
) -> SyncOrAsync[ClientSecretsListResource]:
141+
) -> SyncOrAsync[Sequence[ClientSecret]]:
154142
"""List client secrets for a connect application.
155143
156144
Args:
157145
application_id (str): Application ID or client ID.
158146
159-
Kwargs:
160-
limit (int): Maximum number of records to return. (Optional)
161-
before (str): Pagination cursor to receive records before a provided ID. (Optional)
162-
after (str): Pagination cursor to receive records after a provided ID. (Optional)
163-
order (Literal["asc","desc"]): Sort records in either ascending or descending order. (Optional)
164-
165147
Returns:
166-
ClientSecretsListResource: Client secrets list response from WorkOS.
148+
Sequence[ClientSecret]: Client secrets for the application.
167149
"""
168150
...
169151

@@ -232,7 +214,7 @@ def create_application(
232214
is_first_party: bool,
233215
description: Optional[str] = None,
234216
scopes: Optional[Sequence[str]] = None,
235-
redirect_uris: Optional[Sequence[str]] = None,
217+
redirect_uris: Optional[Sequence[RedirectUriInput]] = None,
236218
uses_pkce: Optional[bool] = None,
237219
organization_id: Optional[str] = None,
238220
) -> ConnectApplication:
@@ -262,7 +244,7 @@ def update_application(
262244
name: Optional[str] = None,
263245
description: Optional[str] = None,
264246
scopes: Optional[Sequence[str]] = None,
265-
redirect_uris: Optional[Sequence[str]] = None,
247+
redirect_uris: Optional[Sequence[RedirectUriInput]] = None,
266248
) -> ConnectApplication:
267249
json = {
268250
"name": name,
@@ -297,30 +279,13 @@ def create_client_secret(self, application_id: str) -> ClientSecret:
297279
def list_client_secrets(
298280
self,
299281
application_id: str,
300-
*,
301-
limit: int = DEFAULT_LIST_RESPONSE_LIMIT,
302-
before: Optional[str] = None,
303-
after: Optional[str] = None,
304-
order: PaginationOrder = "desc",
305-
) -> ClientSecretsListResource:
306-
list_params: ClientSecretListFilters = {
307-
"limit": limit,
308-
"before": before,
309-
"after": after,
310-
"order": order,
311-
}
312-
282+
) -> Sequence[ClientSecret]:
313283
response = self._http_client.request(
314284
f"{CONNECT_APPLICATIONS_PATH}/{application_id}/client_secrets",
315285
method=REQUEST_METHOD_GET,
316-
params=list_params,
317286
)
318287

319-
return WorkOSListResource[ClientSecret, ClientSecretListFilters, ListMetadata](
320-
list_method=partial(self.list_client_secrets, application_id),
321-
list_args=list_params,
322-
**ListPage[ClientSecret](**response).model_dump(),
323-
)
288+
return [ClientSecret.model_validate(secret) for secret in response]
324289

325290
def delete_client_secret(self, client_secret_id: str) -> None:
326291
self._http_client.request(
@@ -382,7 +347,7 @@ async def create_application(
382347
is_first_party: bool,
383348
description: Optional[str] = None,
384349
scopes: Optional[Sequence[str]] = None,
385-
redirect_uris: Optional[Sequence[str]] = None,
350+
redirect_uris: Optional[Sequence[RedirectUriInput]] = None,
386351
uses_pkce: Optional[bool] = None,
387352
organization_id: Optional[str] = None,
388353
) -> ConnectApplication:
@@ -412,7 +377,7 @@ async def update_application(
412377
name: Optional[str] = None,
413378
description: Optional[str] = None,
414379
scopes: Optional[Sequence[str]] = None,
415-
redirect_uris: Optional[Sequence[str]] = None,
380+
redirect_uris: Optional[Sequence[RedirectUriInput]] = None,
416381
) -> ConnectApplication:
417382
json = {
418383
"name": name,
@@ -447,30 +412,13 @@ async def create_client_secret(self, application_id: str) -> ClientSecret:
447412
async def list_client_secrets(
448413
self,
449414
application_id: str,
450-
*,
451-
limit: int = DEFAULT_LIST_RESPONSE_LIMIT,
452-
before: Optional[str] = None,
453-
after: Optional[str] = None,
454-
order: PaginationOrder = "desc",
455-
) -> ClientSecretsListResource:
456-
list_params: ClientSecretListFilters = {
457-
"limit": limit,
458-
"before": before,
459-
"after": after,
460-
"order": order,
461-
}
462-
415+
) -> Sequence[ClientSecret]:
463416
response = await self._http_client.request(
464417
f"{CONNECT_APPLICATIONS_PATH}/{application_id}/client_secrets",
465418
method=REQUEST_METHOD_GET,
466-
params=list_params,
467419
)
468420

469-
return WorkOSListResource[ClientSecret, ClientSecretListFilters, ListMetadata](
470-
list_method=partial(self.list_client_secrets, application_id),
471-
list_args=list_params,
472-
**ListPage[ClientSecret](**response).model_dump(),
473-
)
421+
return [ClientSecret.model_validate(secret) for secret in response]
474422

475423
async def delete_client_secret(self, client_secret_id: str) -> None:
476424
await self._http_client.request(

src/workos/types/connect/list_filters.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,3 @@
55

66
class ConnectApplicationListFilters(ListArgs, total=False):
77
organization_id: Optional[str]
8-
9-
10-
class ClientSecretListFilters(ListArgs, total=False):
11-
pass
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from typing_extensions import TypedDict
2+
3+
4+
class _RedirectUriInputRequired(TypedDict):
5+
uri: str
6+
7+
8+
class RedirectUriInput(_RedirectUriInputRequired, total=False):
9+
default: bool

tests/test_connect.py

Lines changed: 6 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,7 @@ def mock_client_secret(self):
4545

4646
@pytest.fixture
4747
def mock_client_secrets(self):
48-
secret_list = [MockClientSecret(id=f"cs_{i}").dict() for i in range(10)]
49-
return {
50-
"data": secret_list,
51-
"list_metadata": {"before": None, "after": None},
52-
"object": "list",
53-
}
54-
55-
@pytest.fixture
56-
def mock_client_secrets_multiple_data_pages(self):
57-
secrets_list = [MockClientSecret(id=f"cs_{i + 1}").dict() for i in range(40)]
58-
return list_response_of(data=secrets_list)
48+
return [MockClientSecret(id=f"cs_{i}").dict() for i in range(10)]
5949

6050
# --- Application Tests ---
6151

@@ -149,7 +139,9 @@ def test_create_oauth_application(
149139
name="Test Application",
150140
application_type="oauth",
151141
is_first_party=True,
152-
redirect_uris=["https://example.com/callback"],
142+
redirect_uris=[
143+
{"uri": "https://example.com/callback", "default": True}
144+
],
153145
uses_pkce=True,
154146
)
155147
)
@@ -158,7 +150,7 @@ def test_create_oauth_application(
158150
assert request_kwargs["method"] == "post"
159151
assert request_kwargs["json"]["application_type"] == "oauth"
160152
assert request_kwargs["json"]["redirect_uris"] == [
161-
"https://example.com/callback"
153+
{"uri": "https://example.com/callback", "default": True}
162154
]
163155

164156
def test_update_application(
@@ -250,9 +242,7 @@ def test_list_client_secrets(
250242
assert request_kwargs["url"].endswith(
251243
"/connect/applications/app_01ABC/client_secrets"
252244
)
253-
assert (
254-
list(map(lambda x: x.dict(), response.data)) == mock_client_secrets["data"]
255-
)
245+
assert [secret.dict() for secret in response] == mock_client_secrets
256246

257247
def test_delete_client_secret(self, capture_and_mock_http_client_request):
258248
request_kwargs = capture_and_mock_http_client_request(
@@ -269,29 +259,3 @@ def test_delete_client_secret(self, capture_and_mock_http_client_request):
269259
assert request_kwargs["url"].endswith("/connect/client_secrets/cs_01ABC")
270260
assert request_kwargs["method"] == "delete"
271261
assert response is None
272-
273-
def test_list_client_secrets_auto_pagination_for_single_page(
274-
self,
275-
mock_client_secrets,
276-
test_auto_pagination: TestAutoPaginationFunction,
277-
):
278-
test_auto_pagination(
279-
http_client=self.http_client,
280-
list_function=self.connect.list_client_secrets,
281-
expected_all_page_data=mock_client_secrets["data"],
282-
list_function_params={"application_id": "app_01ABC"},
283-
url_path_keys=["application_id"],
284-
)
285-
286-
def test_list_client_secrets_auto_pagination_for_multiple_pages(
287-
self,
288-
mock_client_secrets_multiple_data_pages,
289-
test_auto_pagination: TestAutoPaginationFunction,
290-
):
291-
test_auto_pagination(
292-
http_client=self.http_client,
293-
list_function=self.connect.list_client_secrets,
294-
expected_all_page_data=mock_client_secrets_multiple_data_pages["data"],
295-
list_function_params={"application_id": "app_01ABC"},
296-
url_path_keys=["application_id"],
297-
)

0 commit comments

Comments
 (0)