From 59dd9f7da4ea9d92d7543a996440a972843cb2c7 Mon Sep 17 00:00:00 2001 From: winklemad Date: Fri, 10 Jul 2026 20:27:13 +0530 Subject: [PATCH] fix(postgrest): normalize boolean values in is_() to lowercase literals `is_()` already converts Python `None` to the PostgREST literal `"null"`, but Python booleans fell through to `filter()`, which formats them as `is.True` / `is.False`. PostgREST's `is` operator is case-sensitive and only accepts lowercase `null`/`true`/`false`/`unknown`, so those requests are rejected by the server. Convert `True`/`False` to `"true"`/`"false"` (matching supabase-js, which emits `is.true`). --- .../src/postgrest/base_request_builder.py | 4 ++++ .../_async/test_filter_request_builder.py | 20 +++++++++++++++++++ .../_sync/test_filter_request_builder.py | 20 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/src/postgrest/src/postgrest/base_request_builder.py b/src/postgrest/src/postgrest/base_request_builder.py index b00e5da8..4ea32b37 100644 --- a/src/postgrest/src/postgrest/base_request_builder.py +++ b/src/postgrest/src/postgrest/base_request_builder.py @@ -366,6 +366,10 @@ def is_(self: Self, column: str, value: Any) -> Self: """ if value is None: value = "null" + elif value is True: + value = "true" + elif value is False: + value = "false" return self.filter(column, Filters.IS, value) def like(self: Self, column: str, pattern: str) -> Self: diff --git a/src/postgrest/tests/_async/test_filter_request_builder.py b/src/postgrest/tests/_async/test_filter_request_builder.py index f6e6fecd..dc5d98d9 100644 --- a/src/postgrest/tests/_async/test_filter_request_builder.py +++ b/src/postgrest/tests/_async/test_filter_request_builder.py @@ -282,6 +282,26 @@ def test_is_(filter_request_builder): assert str(builder.request.params) == "x=is.a" +def test_is_true(filter_request_builder): + # PostgREST's `is` operator is case-sensitive and only accepts lowercase + # trilean literals, so a Python ``True`` must become ``is.true``. + builder = filter_request_builder.is_("x", True) + + assert str(builder.request.params) == "x=is.true" + + +def test_is_false(filter_request_builder): + builder = filter_request_builder.is_("x", False) + + assert str(builder.request.params) == "x=is.false" + + +def test_is_none(filter_request_builder): + builder = filter_request_builder.is_("x", None) + + assert str(builder.request.params) == "x=is.null" + + def test_in_(filter_request_builder): builder = filter_request_builder.in_("x", ["a", "b"]) diff --git a/src/postgrest/tests/_sync/test_filter_request_builder.py b/src/postgrest/tests/_sync/test_filter_request_builder.py index 11cc9959..4ace4f58 100644 --- a/src/postgrest/tests/_sync/test_filter_request_builder.py +++ b/src/postgrest/tests/_sync/test_filter_request_builder.py @@ -282,6 +282,26 @@ def test_is_(filter_request_builder): assert str(builder.request.params) == "x=is.a" +def test_is_true(filter_request_builder): + # PostgREST's `is` operator is case-sensitive and only accepts lowercase + # trilean literals, so a Python ``True`` must become ``is.true``. + builder = filter_request_builder.is_("x", True) + + assert str(builder.request.params) == "x=is.true" + + +def test_is_false(filter_request_builder): + builder = filter_request_builder.is_("x", False) + + assert str(builder.request.params) == "x=is.false" + + +def test_is_none(filter_request_builder): + builder = filter_request_builder.is_("x", None) + + assert str(builder.request.params) == "x=is.null" + + def test_in_(filter_request_builder): builder = filter_request_builder.in_("x", ["a", "b"])