Repository: supabase/postgrest-py
Pull request #593: fix: pass params as query params for get/head requests
Author: login: abhishekg999
Created: 2025-05-10T21:01:24Z | Merged: 2025-05-13T10:08:00Z

## What kind of change does this PR introduce?

Allows using GET and HEAD methods on Postgrest RPC functions with arguments.

## What is the current behavior?

RPC functions always try to send params in the body. This will fail for GET and HEAD methods.

Please link any relevant issues here.

 https://github.com/supabase/postgrest-py/issues/592

## What is the new behavior?

You can now properly use get=True / head=True in postgrest RPC func calls.



Diff:
diff --git a/infra/init.sql b/infra/init.sql
index ca01de31..20edecee 100644
--- a/infra/init.sql
+++ b/infra/init.sql
@@ -77,6 +77,12 @@ as $function$
     select * from countries;
 $function$;
 
+create or replace function public.search_countries_by_name(search_name text)
+    returns setof countries
+    language sql
+as $function$
+    select * from countries where nicename ilike '%' || search_name || '%';
+$function$;
 
 create table
   orchestral_sections (id int8 primary key, name text);
diff --git a/postgrest/_async/client.py b/postgrest/_async/client.py
index 94c9c7fa..b2994d32 100644
--- a/postgrest/_async/client.py
+++ b/postgrest/_async/client.py
@@ -133,6 +133,15 @@ def rpc(
 
         headers = Headers({"Prefer": f"count={count}"}) if count else Headers()
 
+        if method in ("HEAD", "GET"):
+            return AsyncRPCFilterRequestBuilder[Any](
+                self.session,
+                f"/rpc/{func}",
+                method,
+                headers,
+                QueryParams(params),
+                json={},
+            )
         # the params here are params to be sent to the RPC and not the queryparams!
         return AsyncRPCFilterRequestBuilder[Any](
             self.session, f"/rpc/{func}", method, headers, QueryParams(), json=params
diff --git a/postgrest/_sync/client.py b/postgrest/_sync/client.py
index a9dd3079..1a27cfb2 100644
--- a/postgrest/_sync/client.py
+++ b/postgrest/_sync/client.py
@@ -133,6 +133,15 @@ def rpc(
 
         headers = Headers({"Prefer": f"count={count}"}) if count else Headers()
 
+        if method in ("HEAD", "GET"):
+            return SyncRPCFilterRequestBuilder[Any](
+                self.session,
+                f"/rpc/{func}",
+                method,
+                headers,
+                QueryParams(params),
+                json={},
+            )
         # the params here are params to be sent to the RPC and not the queryparams!
         return SyncRPCFilterRequestBuilder[Any](
             self.session, f"/rpc/{func}", method, headers, QueryParams(), json=params
diff --git a/postgrest/base_request_builder.py b/postgrest/base_request_builder.py
index 6fffcb95..36c9fe87 100644
--- a/postgrest/base_request_builder.py
+++ b/postgrest/base_request_builder.py
@@ -660,7 +660,11 @@ def select(
         """
         method, params, headers, json = pre_select(*columns, count=None)
         self.params = self.params.add("select", params.get("select"))
-        self.headers["Prefer"] = "return=representation"
+        if self.headers.get("Prefer"):
+            self.headers["Prefer"] += ",return=representation"
+        else:
+            self.headers["Prefer"] = "return=representation"
+
         return self
 
     def single(self) -> Self:
diff --git a/tests/_sync/test_filter_request_builder_integration.py b/tests/_sync/test_filter_request_builder_integration.py
index fd05bd7d..d52fc2ca 100644
--- a/tests/_sync/test_filter_request_builder_integration.py
+++ b/tests/_sync/test_filter_request_builder_integration.py
@@ -476,6 +476,48 @@ def test_rpc_with_range():
     ]
 
 
+def test_rpc_post_with_args():
+    res = (
+        rest_client()
+        .rpc("search_countries_by_name", {"search_name": "Alban"})
+        .select("nicename, iso")
+        .execute()
+    )
+    assert res.data == [{"nicename": "Albania", "iso": "AL"}]
+
+
+def test_rpc_get_with_args():
+    res = (
+        rest_client()
+        .rpc("search_countries_by_name", {"search_name": "Alger"}, get=True)
+        .select("nicename, iso")
+        .execute()
+    )
+    assert res.data == [{"nicename": "Algeria", "iso": "DZ"}]
+
+
+def test_rpc_get_with_count():
+    res = (
+        rest_client()
+        .rpc("search_countries_by_name", {"search_name": "Al"}, get=True, count="exact")
+        .select("nicename")
+        .execute()
+    )
+    assert res.count == 2
+    assert res.data == [{"nicename": "Albania"}, {"nicename": "Algeria"}]
+
+
+def test_rpc_head_count():
+    res = (
+        rest_client()
+        .rpc("search_countries_by_name", {"search_name": "Al"}, head=True, count="exact")
+        .execute()
+    )
+
+    assert res.count == 2
+    assert res.data == []
+
+
 def test_order():
     res = (
         rest_client()


Reviews:
user: login: grdsdev
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-13T08:40:04Z
user: login: abhishekg999
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T08:45:21Z
user: login: abhishekg999
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T08:45:48Z
user: login: grdsdev
body: LGTM! Thanks for the contribution @abhishekg999 
state: APPROVED
author_association: NONE
submitted_at: 2025-05-13T10:07:53Z

Review comments:
diff_hunk: @@ -660,7 +660,11 @@ def select(
         """
         method, params, headers, json = pre_select(*columns, count=None)
         self.params = self.params.add("select", params.get("select"))
-        self.headers["Prefer"] = "return=representation"
+        if self.headers.get("prefer"):
path: postgrest/base_request_builder.py
user: login: grdsdev
body: Is headers in python case-insensitive? This line is checking for `prefer`, and the line below for `Prefer`.
created_at: 2025-05-13T08:40:04Z
author_association: NONE
diff_hunk: @@ -660,7 +660,11 @@ def select(
         """
         method, params, headers, json = pre_select(*columns, count=None)
         self.params = self.params.add("select", params.get("select"))
-        self.headers["Prefer"] = "return=representation"
+        if self.headers.get("prefer"):
path: postgrest/base_request_builder.py
user: login: abhishekg999
body: Actually it should both normalize to lowercase,I just did that to match the get("select") in the line above, but I like keeping it uppercase
created_at: 2025-05-13T08:45:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -660,7 +660,11 @@ def select(
         """
         method, params, headers, json = pre_select(*columns, count=None)
         self.params = self.params.add("select", params.get("select"))
-        self.headers["Prefer"] = "return=representation"
+        if self.headers.get("prefer"):
path: postgrest/base_request_builder.py
user: login: abhishekg999
body: It is case-insensitive*

created_at: 2025-05-13T08:45:48Z
author_association: CONTRIBUTOR

PR comments:
user: login: coveralls
created_at: 2025-05-12T12:03:55Z
body: ## Pull Request Test Coverage Report for [Build 14993454641](https://coveralls.io/builds/73641493)


### Details

* **20** of **21**   **(95.24%)**  changed or added relevant lines in **4** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.006%**) to **95.506%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [postgrest/_async/client.py](https://coveralls.io/builds/73641493/source?filename=postgrest%2F_async%2Fclient.py#L137) | 1 | 2 | 50.0%
<!-- | **Total:** | **20** | **21** | **95.24%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/73641493/badge)](https://coveralls.io/builds/73641493) |
| :-- | --: |
| Change from base [Build 14248123638](https://coveralls.io/builds/73067026): |  -0.006% |
| Covered Lines: | 1764 |
| Relevant Lines: | 1847 |

---
##### 💛  - [Coveralls](https://coveralls.io)

author_association: NONE
user: login: grdsdev
created_at: 2025-05-12T12:47:46Z
body: Hi @abhishekg999 thank you for this fix.

Can you also add a test for asserting the behavior?

Thanks.
author_association: NONE
user: login: abhishekg999
created_at: 2025-05-13T02:57:29Z
body: @grdsdev TY, also noticed BaseRPCRequestBuilder also doesnt merge the prefer header. I made an update to that to match the behavior in postgrest-js. 
author_association: CONTRIBUTOR