diff --git a/src/postgrest/src/postgrest/base_request_builder.py b/src/postgrest/src/postgrest/base_request_builder.py index c9c4af2f..32f696bb 100644 --- a/src/postgrest/src/postgrest/base_request_builder.py +++ b/src/postgrest/src/postgrest/base_request_builder.py @@ -274,11 +274,27 @@ def __init__(self, request: RequestConfig[C]) -> None: self.request: RequestConfig[C] = request self.negate_next = False + def _clone(self: Self) -> Self: + clone = self.__class__( + RequestConfig( + session=self.request.session, + path=self.request.path, + http_method=self.request.http_method, + headers=Headers(self.request.headers), + params=QueryParams(self.request.params), + auth=self.request.auth, + json=self.request.json, + ) + ) + clone.negate_next = self.negate_next + return clone + @property def not_(self: Self) -> Self: """Whether the filter applied next should be negated.""" - self.negate_next = True - return self + builder = self._clone() + builder.negate_next = True + return builder def filter(self: Self, column: str, operator: str, criteria: str) -> Self: """Apply filters on a query. @@ -288,12 +304,13 @@ def filter(self: Self, column: str, operator: str, criteria: str) -> Self: operator: The operator to use while filtering criteria: The value to filter by """ - if self.negate_next is True: - self.negate_next = False + builder = self._clone() + if builder.negate_next is True: + builder.negate_next = False operator = f"{Filters.NOT}.{operator}" key, val = sanitize_param(column), f"{operator}.{criteria}" - self.request.params = self.request.params.add(key, val) - return self + builder.request.params = builder.request.params.add(key, val) + return builder def eq(self: Self, column: str, value: Any) -> Self: """An 'equal to' filter. @@ -425,9 +442,10 @@ def or_(self: Self, filters: str, reference_table: Optional[str] = None) -> Self filters: The filters to use, following PostgREST syntax reference_table: Set this to filter on referenced tables instead of the parent table """ + builder = self._clone() key = f"{sanitize_param(reference_table)}.or" if reference_table else "or" - self.request.params = self.request.params.add(key, f"({filters})") - return self + builder.request.params = builder.request.params.add(key, f"({filters})") + return builder def fts(self: Self, column: str, query: Any) -> Self: return self.filter(column, Filters.FTS, query) @@ -532,7 +550,7 @@ def match(self: Self, query: Dict[str, Any]) -> Self: ) for key, value in query.items(): - updated_query = self.eq(key, value) + updated_query = updated_query.eq(key, value) return updated_query diff --git a/src/postgrest/tests/_async/test_filter_request_builder.py b/src/postgrest/tests/_async/test_filter_request_builder.py index e4a63eb9..22ac68aa 100644 --- a/src/postgrest/tests/_async/test_filter_request_builder.py +++ b/src/postgrest/tests/_async/test_filter_request_builder.py @@ -298,3 +298,13 @@ def test_max_affected_returns_self(filter_request_builder): builder = filter_request_builder.max_affected(1) assert builder is filter_request_builder + + +def test_filter_builders_are_immutable(filter_request_builder): + base_query = filter_request_builder.eq("account_id", "abc") + first_query = base_query.in_("id", ["1", "2", "3"]) + second_query = base_query.in_("id", ["4", "5", "6"]) + + assert str(base_query.request.params) == "account_id=eq.abc" + assert str(first_query.request.params) == "account_id=eq.abc&id=in.%281%2C2%2C3%29" + assert str(second_query.request.params) == "account_id=eq.abc&id=in.%284%2C5%2C6%29" diff --git a/src/postgrest/tests/_sync/test_filter_request_builder.py b/src/postgrest/tests/_sync/test_filter_request_builder.py index c5c5c1d8..163ea770 100644 --- a/src/postgrest/tests/_sync/test_filter_request_builder.py +++ b/src/postgrest/tests/_sync/test_filter_request_builder.py @@ -298,3 +298,13 @@ def test_max_affected_returns_self(filter_request_builder): builder = filter_request_builder.max_affected(1) assert builder is filter_request_builder + + +def test_filter_builders_are_immutable(filter_request_builder): + base_query = filter_request_builder.eq("account_id", "abc") + first_query = base_query.in_("id", ["1", "2", "3"]) + second_query = base_query.in_("id", ["4", "5", "6"]) + + assert str(base_query.request.params) == "account_id=eq.abc" + assert str(first_query.request.params) == "account_id=eq.abc&id=in.%281%2C2%2C3%29" + assert str(second_query.request.params) == "account_id=eq.abc&id=in.%284%2C5%2C6%29"