Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/postgrest/src/postgrest/base_request_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,26 @@ def match(self: Self, query: Dict[str, Any]) -> Self:

return updated_query

def max_affected(self: Self, value: int) -> Self:
"""Set the maximum number of rows that can be affected by the query.

Only available in PostgREST v13+ and only works with PATCH and DELETE methods.

Args:
value: The maximum number of rows that can be affected
"""
prefer_header = self.headers.get("Prefer", "")
if prefer_header:
if "handling=strict" not in prefer_header:
prefer_header += ",handling=strict"
else:
prefer_header = "handling=strict"

prefer_header += f",max-affected={value}"

self.headers["Prefer"] = prefer_header
return self


class BaseSelectRequestBuilder(BaseFilterRequestBuilder[_ReturnT]):
def __init__(
Expand Down
31 changes: 31 additions & 0 deletions src/postgrest/tests/_async/test_filter_request_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,34 @@ def test_or_in_contain(filter_request_builder):
str(builder.params)
== "or=%28id.in.%285%2C6%2C7%29%2C+arraycol.cs.%7B%27a%27%2C%27b%27%7D%29"
)


def test_max_affected(filter_request_builder):
builder = filter_request_builder.max_affected(5)

assert builder.headers["prefer"] == "handling=strict,max-affected=5"


def test_max_affected_with_existing_prefer_header(filter_request_builder):
# Set an existing prefer header
filter_request_builder.headers["prefer"] = "return=representation"
builder = filter_request_builder.max_affected(10)

assert (
builder.headers["prefer"]
== "return=representation,handling=strict,max-affected=10"
)


def test_max_affected_with_existing_handling_strict(filter_request_builder):
# Set an existing prefer header with handling=strict
filter_request_builder.headers["prefer"] = "handling=strict,return=minimal"
builder = filter_request_builder.max_affected(3)

assert builder.headers["prefer"] == "handling=strict,return=minimal,max-affected=3"


def test_max_affected_returns_self(filter_request_builder):
builder = filter_request_builder.max_affected(1)

assert builder is filter_request_builder
18 changes: 18 additions & 0 deletions src/postgrest/tests/_async/test_request_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,15 @@ def test_update_with_count(self, request_builder: AsyncRequestBuilder):
assert builder.http_method == "PATCH"
assert builder.json == {"key1": "val1"}

def test_update_with_max_affected(self, request_builder: AsyncRequestBuilder):
builder = request_builder.update({"key1": "val1"}).max_affected(5)

assert "handling=strict" in builder.headers["prefer"]
assert "max-affected=5" in builder.headers["prefer"]
assert "return=representation" in builder.headers["prefer"]
assert builder.http_method == "PATCH"
assert builder.json == {"key1": "val1"}


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

def test_delete_with_max_affected(self, request_builder: AsyncRequestBuilder):
builder = request_builder.delete().max_affected(10)

assert "handling=strict" in builder.headers["prefer"]
assert "max-affected=10" in builder.headers["prefer"]
assert "return=representation" in builder.headers["prefer"]
assert builder.http_method == "DELETE"
assert builder.json == {}


class TestTextSearch:
def test_text_search(self, request_builder: AsyncRequestBuilder):
Expand Down
31 changes: 31 additions & 0 deletions src/postgrest/tests/_sync/test_filter_request_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,34 @@ def test_or_in_contain(filter_request_builder):
str(builder.params)
== "or=%28id.in.%285%2C6%2C7%29%2C+arraycol.cs.%7B%27a%27%2C%27b%27%7D%29"
)


def test_max_affected(filter_request_builder):
builder = filter_request_builder.max_affected(5)

assert builder.headers["prefer"] == "handling=strict,max-affected=5"


def test_max_affected_with_existing_prefer_header(filter_request_builder):
# Set an existing prefer header
filter_request_builder.headers["prefer"] = "return=representation"
builder = filter_request_builder.max_affected(10)

assert (
builder.headers["prefer"]
== "return=representation,handling=strict,max-affected=10"
)


def test_max_affected_with_existing_handling_strict(filter_request_builder):
# Set an existing prefer header with handling=strict
filter_request_builder.headers["prefer"] = "handling=strict,return=minimal"
builder = filter_request_builder.max_affected(3)

assert builder.headers["prefer"] == "handling=strict,return=minimal,max-affected=3"


def test_max_affected_returns_self(filter_request_builder):
builder = filter_request_builder.max_affected(1)

assert builder is filter_request_builder
18 changes: 18 additions & 0 deletions src/postgrest/tests/_sync/test_request_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,15 @@ def test_update_with_count(self, request_builder: SyncRequestBuilder):
assert builder.http_method == "PATCH"
assert builder.json == {"key1": "val1"}

def test_update_with_max_affected(self, request_builder: SyncRequestBuilder):
builder = request_builder.update({"key1": "val1"}).max_affected(5)

assert "handling=strict" in builder.headers["prefer"]
assert "max-affected=5" in builder.headers["prefer"]
assert "return=representation" in builder.headers["prefer"]
assert builder.http_method == "PATCH"
assert builder.json == {"key1": "val1"}


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

def test_delete_with_max_affected(self, request_builder: SyncRequestBuilder):
builder = request_builder.delete().max_affected(10)

assert "handling=strict" in builder.headers["prefer"]
assert "max-affected=10" in builder.headers["prefer"]
assert "return=representation" in builder.headers["prefer"]
assert builder.http_method == "DELETE"
assert builder.json == {}


class TestTextSearch:
def test_text_search(self, request_builder: SyncRequestBuilder):
Expand Down
13 changes: 0 additions & 13 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.