Skip to content

Commit 022259c

Browse files
authored
fix: use query params for resource get/delete/deactivate methods (#24)
Match API migration from path-based to query-param-based resource endpoints. Adds missing delete_resource tests for both sync and async.
1 parent 8a27e35 commit 022259c

2 files changed

Lines changed: 90 additions & 29 deletions

File tree

src/pragma_sdk/client.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
TrustTier,
2626
UpgradePolicy,
2727
UserInfo,
28-
format_resource_id,
2928
)
3029

3130

@@ -271,13 +270,16 @@ def get_resource[ResourceT: Resource](
271270
Raises:
272271
httpx.HTTPStatusError: If resource not found or request fails.
273272
""" # noqa: DOC502
274-
resource_id = format_resource_id(provider, resource, name)
275-
params: dict[str, Any] = {}
273+
params: dict[str, Any] = {"provider": provider, "resource": resource, "name": name}
274+
276275
if reveal:
277276
params["reveal"] = "true"
278-
response = self._request("GET", f"/resources/{resource_id}", params=params)
277+
278+
response = self._request("GET", "/resources/by-name", params=params)
279+
279280
if model is not None:
280281
return model.model_validate(response)
282+
281283
return response
282284

283285
def apply_resource[ResourceT: Resource](
@@ -334,8 +336,8 @@ def deactivate_resource[ResourceT: Resource](
334336
Raises:
335337
httpx.HTTPStatusError: If resource not found or deactivation fails.
336338
""" # noqa: DOC502
337-
resource_id = format_resource_id(provider, resource, name)
338-
response = self._request("POST", f"/resources/{resource_id}/deactivate")
339+
params = {"provider": provider, "resource": resource, "name": name}
340+
response = self._request("POST", "/resources/deactivate", params=params)
339341

340342
if model is not None:
341343
return model.model_validate(response)
@@ -348,8 +350,8 @@ def delete_resource(self, provider: str, resource: str, name: str) -> None:
348350
Raises:
349351
httpx.HTTPStatusError: If resource not found or deletion fails.
350352
""" # noqa: DOC502
351-
resource_id = format_resource_id(provider, resource, name)
352-
self._request("DELETE", f"/resources/{resource_id}")
353+
params = {"provider": provider, "resource": resource, "name": name}
354+
self._request("DELETE", "/resources/by-name", params=params)
353355

354356
def list_dead_letter_events(self, provider: str | None = None) -> list[dict[str, Any]]:
355357
"""List dead letter events with optional provider filter.
@@ -1018,13 +1020,16 @@ async def get_resource[ResourceT: Resource](
10181020
Raises:
10191021
httpx.HTTPStatusError: If resource not found or request fails.
10201022
""" # noqa: DOC502
1021-
resource_id = format_resource_id(provider, resource, name)
1022-
params: dict[str, Any] = {}
1023+
params: dict[str, Any] = {"provider": provider, "resource": resource, "name": name}
1024+
10231025
if reveal:
10241026
params["reveal"] = "true"
1025-
response = await self._request("GET", f"/resources/{resource_id}", params=params)
1027+
1028+
response = await self._request("GET", "/resources/by-name", params=params)
1029+
10261030
if model is not None:
10271031
return model.model_validate(response)
1032+
10281033
return response
10291034

10301035
async def apply_resource[ResourceT: Resource](
@@ -1081,8 +1086,8 @@ async def deactivate_resource[ResourceT: Resource](
10811086
Raises:
10821087
httpx.HTTPStatusError: If resource not found or deactivation fails.
10831088
""" # noqa: DOC502
1084-
resource_id = format_resource_id(provider, resource, name)
1085-
response = await self._request("POST", f"/resources/{resource_id}/deactivate")
1089+
params = {"provider": provider, "resource": resource, "name": name}
1090+
response = await self._request("POST", "/resources/deactivate", params=params)
10861091

10871092
if model is not None:
10881093
return model.model_validate(response)
@@ -1095,8 +1100,8 @@ async def delete_resource(self, provider: str, resource: str, name: str) -> None
10951100
Raises:
10961101
httpx.HTTPStatusError: If resource not found or deletion fails.
10971102
""" # noqa: DOC502
1098-
resource_id = format_resource_id(provider, resource, name)
1099-
await self._request("DELETE", f"/resources/{resource_id}")
1103+
params = {"provider": provider, "resource": resource, "name": name}
1104+
await self._request("DELETE", "/resources/by-name", params=params)
11001105

11011106
async def list_dead_letter_events(self, provider: str | None = None) -> list[dict[str, Any]]:
11021107
"""List dead letter events with optional provider filter.

tests/test_client.py

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ def test_pragma_client_list_resources_returns_typed_resources_with_model() -> No
8383
@respx.mock
8484
def test_pragma_client_get_resource_returns_dict_without_model() -> None:
8585
"""Returns dict when no model parameter provided."""
86-
respx.get("http://localhost:8000/resources/postgres/database/mydb").mock(
86+
respx.get(
87+
"http://localhost:8000/resources/by-name",
88+
params={"provider": "postgres", "resource": "database", "name": "mydb"},
89+
).mock(
8790
return_value=httpx.Response(
8891
200,
8992
json={
@@ -104,7 +107,10 @@ def test_pragma_client_get_resource_returns_dict_without_model() -> None:
104107
@respx.mock
105108
def test_pragma_client_get_resource_returns_typed_resource_with_model() -> None:
106109
"""Returns typed Resource instance when model parameter provided."""
107-
respx.get("http://localhost:8000/resources/test/stub/mydb").mock(
110+
respx.get(
111+
"http://localhost:8000/resources/by-name",
112+
params={"provider": "test", "resource": "stub", "name": "mydb"},
113+
).mock(
108114
return_value=httpx.Response(
109115
200,
110116
json={
@@ -169,7 +175,10 @@ def test_pragma_client_apply_resource_returns_typed_resource_with_model() -> Non
169175
@respx.mock
170176
def test_pragma_client_deactivate_resource_returns_dict_without_model() -> None:
171177
"""Returns dict when no model parameter provided."""
172-
respx.post("http://localhost:8000/resources/postgres/database/mydb/deactivate").mock(
178+
respx.post(
179+
"http://localhost:8000/resources/deactivate",
180+
params={"provider": "postgres", "resource": "database", "name": "mydb"},
181+
).mock(
173182
return_value=httpx.Response(
174183
200,
175184
json={
@@ -190,7 +199,10 @@ def test_pragma_client_deactivate_resource_returns_dict_without_model() -> None:
190199
@respx.mock
191200
def test_pragma_client_deactivate_resource_returns_typed_resource_with_model() -> None:
192201
"""Returns typed Resource instance when model parameter provided."""
193-
respx.post("http://localhost:8000/resources/test/stub/mydb/deactivate").mock(
202+
respx.post(
203+
"http://localhost:8000/resources/deactivate",
204+
params={"provider": "test", "resource": "stub", "name": "mydb"},
205+
).mock(
194206
return_value=httpx.Response(
195207
200,
196208
json={
@@ -209,12 +221,28 @@ def test_pragma_client_deactivate_resource_returns_typed_resource_with_model() -
209221
assert result.lifecycle_state == LifecycleState.DELETING
210222

211223

224+
@respx.mock
225+
def test_pragma_client_delete_resource_sends_delete_request() -> None:
226+
"""Sends DELETE to /resources/by-name with correct params and returns None."""
227+
route = respx.delete(
228+
"http://localhost:8000/resources/by-name",
229+
params={"provider": "postgres", "resource": "database", "name": "mydb"},
230+
).mock(return_value=httpx.Response(200, json=None))
231+
232+
with PragmaClient(auth_token=None) as client:
233+
result = client.delete_resource("postgres", "database", "mydb")
234+
235+
assert result is None
236+
assert route.called
237+
238+
212239
@respx.mock
213240
def test_pragma_client_raises_on_not_found() -> None:
214241
"""Raises HTTPStatusError when resource not found."""
215-
respx.get("http://localhost:8000/resources/test/db/notfound").mock(
216-
return_value=httpx.Response(404, json={"detail": "Not found"})
217-
)
242+
respx.get(
243+
"http://localhost:8000/resources/by-name",
244+
params={"provider": "test", "resource": "db", "name": "notfound"},
245+
).mock(return_value=httpx.Response(404, json={"detail": "Not found"}))
218246

219247
with PragmaClient(auth_token=None) as client:
220248
with pytest.raises(httpx.HTTPStatusError) as exc_info:
@@ -315,7 +343,10 @@ async def test_async_pragma_client_list_resources_returns_typed_resources_with_m
315343
@respx.mock
316344
async def test_async_pragma_client_get_resource_returns_dict_without_model() -> None:
317345
"""Returns dict when no model parameter provided."""
318-
respx.get("http://localhost:8000/resources/postgres/database/mydb").mock(
346+
respx.get(
347+
"http://localhost:8000/resources/by-name",
348+
params={"provider": "postgres", "resource": "database", "name": "mydb"},
349+
).mock(
319350
return_value=httpx.Response(
320351
200,
321352
json={
@@ -336,7 +367,10 @@ async def test_async_pragma_client_get_resource_returns_dict_without_model() ->
336367
@respx.mock
337368
async def test_async_pragma_client_get_resource_returns_typed_resource_with_model() -> None:
338369
"""Returns typed Resource instance when model parameter provided."""
339-
respx.get("http://localhost:8000/resources/test/stub/mydb").mock(
370+
respx.get(
371+
"http://localhost:8000/resources/by-name",
372+
params={"provider": "test", "resource": "stub", "name": "mydb"},
373+
).mock(
340374
return_value=httpx.Response(
341375
200,
342376
json={
@@ -403,7 +437,10 @@ async def test_async_pragma_client_apply_resource_returns_typed_resource_with_mo
403437
@respx.mock
404438
async def test_async_pragma_client_deactivate_resource_returns_dict_without_model() -> None:
405439
"""Returns dict when no model parameter provided."""
406-
respx.post("http://localhost:8000/resources/postgres/database/mydb/deactivate").mock(
440+
respx.post(
441+
"http://localhost:8000/resources/deactivate",
442+
params={"provider": "postgres", "resource": "database", "name": "mydb"},
443+
).mock(
407444
return_value=httpx.Response(
408445
200,
409446
json={
@@ -424,7 +461,10 @@ async def test_async_pragma_client_deactivate_resource_returns_dict_without_mode
424461
@respx.mock
425462
async def test_async_pragma_client_deactivate_resource_returns_typed_resource_with_model() -> None:
426463
"""Returns typed Resource instance when model parameter provided."""
427-
respx.post("http://localhost:8000/resources/test/stub/mydb/deactivate").mock(
464+
respx.post(
465+
"http://localhost:8000/resources/deactivate",
466+
params={"provider": "test", "resource": "stub", "name": "mydb"},
467+
).mock(
428468
return_value=httpx.Response(
429469
200,
430470
json={
@@ -443,12 +483,28 @@ async def test_async_pragma_client_deactivate_resource_returns_typed_resource_wi
443483
assert result.lifecycle_state == LifecycleState.DELETING
444484

445485

486+
@respx.mock
487+
async def test_async_pragma_client_delete_resource_sends_delete_request() -> None:
488+
"""Sends DELETE to /resources/by-name with correct params and returns None."""
489+
route = respx.delete(
490+
"http://localhost:8000/resources/by-name",
491+
params={"provider": "postgres", "resource": "database", "name": "mydb"},
492+
).mock(return_value=httpx.Response(200, json=None))
493+
494+
async with AsyncPragmaClient(auth_token=None) as client:
495+
result = await client.delete_resource("postgres", "database", "mydb")
496+
497+
assert result is None
498+
assert route.called
499+
500+
446501
@respx.mock
447502
async def test_async_pragma_client_raises_on_not_found() -> None:
448503
"""Raises HTTPStatusError when resource not found."""
449-
respx.get("http://localhost:8000/resources/test/db/notfound").mock(
450-
return_value=httpx.Response(404, json={"detail": "Not found"})
451-
)
504+
respx.get(
505+
"http://localhost:8000/resources/by-name",
506+
params={"provider": "test", "resource": "db", "name": "notfound"},
507+
).mock(return_value=httpx.Response(404, json={"detail": "Not found"}))
452508

453509
async with AsyncPragmaClient(auth_token=None) as client:
454510
with pytest.raises(httpx.HTTPStatusError) as exc_info:

0 commit comments

Comments
 (0)