Skip to content

Commit 3f75daf

Browse files
grdsdevclaude
andauthored
feat(postgrest): implement max_affected method (#1222)
Co-authored-by: Claude <[email protected]>
1 parent ea44ab6 commit 3f75daf

File tree

6 files changed

+118
-13
lines changed

6 files changed

+118
-13
lines changed

src/postgrest/src/postgrest/base_request_builder.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,26 @@ def match(self: Self, query: Dict[str, Any]) -> Self:
526526

527527
return updated_query
528528

529+
def max_affected(self: Self, value: int) -> Self:
530+
"""Set the maximum number of rows that can be affected by the query.
531+
532+
Only available in PostgREST v13+ and only works with PATCH and DELETE methods.
533+
534+
Args:
535+
value: The maximum number of rows that can be affected
536+
"""
537+
prefer_header = self.headers.get("Prefer", "")
538+
if prefer_header:
539+
if "handling=strict" not in prefer_header:
540+
prefer_header += ",handling=strict"
541+
else:
542+
prefer_header = "handling=strict"
543+
544+
prefer_header += f",max-affected={value}"
545+
546+
self.headers["Prefer"] = prefer_header
547+
return self
548+
529549

530550
class BaseSelectRequestBuilder(BaseFilterRequestBuilder[_ReturnT]):
531551
def __init__(

src/postgrest/tests/_async/test_filter_request_builder.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,34 @@ def test_or_in_contain(filter_request_builder):
241241
str(builder.params)
242242
== "or=%28id.in.%285%2C6%2C7%29%2C+arraycol.cs.%7B%27a%27%2C%27b%27%7D%29"
243243
)
244+
245+
246+
def test_max_affected(filter_request_builder):
247+
builder = filter_request_builder.max_affected(5)
248+
249+
assert builder.headers["prefer"] == "handling=strict,max-affected=5"
250+
251+
252+
def test_max_affected_with_existing_prefer_header(filter_request_builder):
253+
# Set an existing prefer header
254+
filter_request_builder.headers["prefer"] = "return=representation"
255+
builder = filter_request_builder.max_affected(10)
256+
257+
assert (
258+
builder.headers["prefer"]
259+
== "return=representation,handling=strict,max-affected=10"
260+
)
261+
262+
263+
def test_max_affected_with_existing_handling_strict(filter_request_builder):
264+
# Set an existing prefer header with handling=strict
265+
filter_request_builder.headers["prefer"] = "handling=strict,return=minimal"
266+
builder = filter_request_builder.max_affected(3)
267+
268+
assert builder.headers["prefer"] == "handling=strict,return=minimal,max-affected=3"
269+
270+
271+
def test_max_affected_returns_self(filter_request_builder):
272+
builder = filter_request_builder.max_affected(1)
273+
274+
assert builder is filter_request_builder

src/postgrest/tests/_async/test_request_builder.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ def test_update_with_count(self, request_builder: AsyncRequestBuilder):
147147
assert builder.http_method == "PATCH"
148148
assert builder.json == {"key1": "val1"}
149149

150+
def test_update_with_max_affected(self, request_builder: AsyncRequestBuilder):
151+
builder = request_builder.update({"key1": "val1"}).max_affected(5)
152+
153+
assert "handling=strict" in builder.headers["prefer"]
154+
assert "max-affected=5" in builder.headers["prefer"]
155+
assert "return=representation" in builder.headers["prefer"]
156+
assert builder.http_method == "PATCH"
157+
assert builder.json == {"key1": "val1"}
158+
150159

151160
class TestDelete:
152161
def test_delete(self, request_builder: AsyncRequestBuilder):
@@ -166,6 +175,15 @@ def test_delete_with_count(self, request_builder: AsyncRequestBuilder):
166175
assert builder.http_method == "DELETE"
167176
assert builder.json == {}
168177

178+
def test_delete_with_max_affected(self, request_builder: AsyncRequestBuilder):
179+
builder = request_builder.delete().max_affected(10)
180+
181+
assert "handling=strict" in builder.headers["prefer"]
182+
assert "max-affected=10" in builder.headers["prefer"]
183+
assert "return=representation" in builder.headers["prefer"]
184+
assert builder.http_method == "DELETE"
185+
assert builder.json == {}
186+
169187

170188
class TestTextSearch:
171189
def test_text_search(self, request_builder: AsyncRequestBuilder):

src/postgrest/tests/_sync/test_filter_request_builder.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,34 @@ def test_or_in_contain(filter_request_builder):
241241
str(builder.params)
242242
== "or=%28id.in.%285%2C6%2C7%29%2C+arraycol.cs.%7B%27a%27%2C%27b%27%7D%29"
243243
)
244+
245+
246+
def test_max_affected(filter_request_builder):
247+
builder = filter_request_builder.max_affected(5)
248+
249+
assert builder.headers["prefer"] == "handling=strict,max-affected=5"
250+
251+
252+
def test_max_affected_with_existing_prefer_header(filter_request_builder):
253+
# Set an existing prefer header
254+
filter_request_builder.headers["prefer"] = "return=representation"
255+
builder = filter_request_builder.max_affected(10)
256+
257+
assert (
258+
builder.headers["prefer"]
259+
== "return=representation,handling=strict,max-affected=10"
260+
)
261+
262+
263+
def test_max_affected_with_existing_handling_strict(filter_request_builder):
264+
# Set an existing prefer header with handling=strict
265+
filter_request_builder.headers["prefer"] = "handling=strict,return=minimal"
266+
builder = filter_request_builder.max_affected(3)
267+
268+
assert builder.headers["prefer"] == "handling=strict,return=minimal,max-affected=3"
269+
270+
271+
def test_max_affected_returns_self(filter_request_builder):
272+
builder = filter_request_builder.max_affected(1)
273+
274+
assert builder is filter_request_builder

src/postgrest/tests/_sync/test_request_builder.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ def test_update_with_count(self, request_builder: SyncRequestBuilder):
147147
assert builder.http_method == "PATCH"
148148
assert builder.json == {"key1": "val1"}
149149

150+
def test_update_with_max_affected(self, request_builder: SyncRequestBuilder):
151+
builder = request_builder.update({"key1": "val1"}).max_affected(5)
152+
153+
assert "handling=strict" in builder.headers["prefer"]
154+
assert "max-affected=5" in builder.headers["prefer"]
155+
assert "return=representation" in builder.headers["prefer"]
156+
assert builder.http_method == "PATCH"
157+
assert builder.json == {"key1": "val1"}
158+
150159

151160
class TestDelete:
152161
def test_delete(self, request_builder: SyncRequestBuilder):
@@ -166,6 +175,15 @@ def test_delete_with_count(self, request_builder: SyncRequestBuilder):
166175
assert builder.http_method == "DELETE"
167176
assert builder.json == {}
168177

178+
def test_delete_with_max_affected(self, request_builder: SyncRequestBuilder):
179+
builder = request_builder.delete().max_affected(10)
180+
181+
assert "handling=strict" in builder.headers["prefer"]
182+
assert "max-affected=10" in builder.headers["prefer"]
183+
assert "return=representation" in builder.headers["prefer"]
184+
assert builder.http_method == "DELETE"
185+
assert builder.json == {}
186+
169187

170188
class TestTextSearch:
171189
def test_text_search(self, request_builder: SyncRequestBuilder):

uv.lock

Lines changed: 0 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)