From 360bf4e5d0ce1418f57983afefb313b8bbd7c56b Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Mon, 15 Jan 2024 04:47:43 +0000 Subject: [PATCH 1/5] add or filter along with tests --- Makefile | 9 +- infra/docker-compose.yaml | 6 +- infra/init.sql | 54 ++++ postgrest/base_request_builder.py | 11 + tests/_async/client.py | 9 + tests/_async/test_filter_request_builder.py | 6 + ...test_filter_request_builder_integration.py | 280 ++++++++++++++++++ tests/_sync/client.py | 9 + tests/_sync/test_filter_request_builder.py | 6 + ...test_filter_request_builder_integration.py | 273 +++++++++++++++++ 10 files changed, 659 insertions(+), 4 deletions(-) create mode 100644 infra/init.sql create mode 100644 tests/_async/client.py create mode 100644 tests/_async/test_filter_request_builder_integration.py create mode 100644 tests/_sync/client.py create mode 100644 tests/_sync/test_filter_request_builder_integration.py diff --git a/Makefile b/Makefile index 0de05cb3..a02124d6 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,11 @@ clean_infra: docker-compose down --remove-orphans &&\ docker system prune -a --volumes -f -run_tests: tests +stop_infra: + cd infra &&\ + docker-compose down --remove-orphans + +run_tests: run_infra sleep tests run_unasync: poetry run unasync postgrest tests @@ -31,3 +35,6 @@ build_sync: run_unasync remove_pytest_asyncio_from_sync remove_pytest_asyncio_from_sync: sed -i 's/@pytest.mark.asyncio//g' tests/_sync/test_client.py + +sleep: + sleep 20 diff --git a/infra/docker-compose.yaml b/infra/docker-compose.yaml index cdca0d08..5be08072 100644 --- a/infra/docker-compose.yaml +++ b/infra/docker-compose.yaml @@ -20,6 +20,6 @@ services: POSTGRES_USER: app_user POSTGRES_PASSWORD: password # Uncomment this if you want to persist the data. Create your boostrap SQL file in the project root -# volumes: -# - "./pgdata:/var/lib/postgresql/data" -# - "./init.sql:/docker-entrypoint-initdb.d/init.sql" + volumes: + # - "./pgdata:/var/lib/postgresql/data" + - "./init.sql:/docker-entrypoint-initdb.d/init.sql" diff --git a/infra/init.sql b/infra/init.sql new file mode 100644 index 00000000..0b9f8ba4 --- /dev/null +++ b/infra/init.sql @@ -0,0 +1,54 @@ +CREATE TABLE public.countries ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + iso CHAR (2) NOT NULL, + country_name VARCHAR (80) NOT NULL, + nicename VARCHAR (80) NOT NULL, + iso3 CHAR (3) DEFAULT NULL, + numcode SMALLINT DEFAULT NULL, + phonecode INT NOT NULL +); + +INSERT INTO public.countries (iso, country_name, nicename, iso3, numcode, phonecode) VALUES + ('AF', 'AFGHANISTAN', 'Afghanistan', 'AFG', 4, 93), + ('AL', 'ALBANIA', 'Albania', 'ALB', 8, 355), + ('DZ', 'ALGERIA', 'Algeria', 'DZA', 12, 213), + ('AQ', 'ANTARCTICA', 'Antarctica', NULL, NULL, 0), + ('CR', 'COSTA RICA', 'Costa Rica', 'CRI', 188, 506), + ('ES', 'SPAIN', 'Spain', 'ESP', 724, 34), + ('TH', 'THAILAND', 'Thailand', 'THA', 764, 66), + ('TG', 'TOGO', 'Togo', 'TGO', 768, 228), + ('TT', 'TRINIDAD AND TOBAGO', 'Trinidad and Tobago', 'TTO', 780, 1868), + ('GB', 'UNITED KINGDOM', 'United Kingdom', 'GBR', 826, 44), + ('US', 'UNITED STATES', 'United States', 'USA', 840, 1), + ('ZW', 'ZIMBABWE', 'Zimbabwe', 'ZWE', 716, 263); + +create table public.users ( + id int8 primary key, + name text, + address jsonb +); + +insert into public.users (id, name, address) values + (1, 'Michael', '{ "postcode": 90210, "street": "Melrose Place" }'), + (2, 'Jane', '{}'); + +create table public.reservations ( + id int8 primary key, + room_name text, + during tsrange +); + +insert into public.reservations (id, room_name, during) values + (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), + (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); + + +create table public.issues ( + id int8 primary key, + title text, + tags text[] +); + +insert into public.issues (id, title, tags) values + (1, 'Cache invalidation is not working', array['is:open', 'severity:high', 'priority:low']), + (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']); diff --git a/postgrest/base_request_builder.py b/postgrest/base_request_builder.py index 303fe2be..cd84acf3 100644 --- a/postgrest/base_request_builder.py +++ b/postgrest/base_request_builder.py @@ -330,6 +330,17 @@ def ilike(self: Self, column: str, pattern: str) -> Self: """ return self.filter(column, Filters.ILIKE, pattern) + def or_(self: Self, filters: str, reference_table: Union[str, None] = None) -> Self: + """An 'or' filter + + Args: + filters: The filters to use, following PostgREST syntax + reference_table: Set this to filter on referenced tables instead of the parent table + """ + key = f"{sanitize_param(reference_table)}.or" if reference_table else "or" + self.params = self.params.add(key, f"({filters})") + return self + def fts(self: Self, column: str, query: Any) -> Self: return self.filter(column, Filters.FTS, query) diff --git a/tests/_async/client.py b/tests/_async/client.py new file mode 100644 index 00000000..b0a066e9 --- /dev/null +++ b/tests/_async/client.py @@ -0,0 +1,9 @@ +from postgrest import AsyncPostgrestClient + +REST_URL = f"http://127.0.0.1:3000" + + +def rest_client(): + return AsyncPostgrestClient( + base_url=REST_URL, + ) diff --git a/tests/_async/test_filter_request_builder.py b/tests/_async/test_filter_request_builder.py index 3e1b9abf..ede40add 100644 --- a/tests/_async/test_filter_request_builder.py +++ b/tests/_async/test_filter_request_builder.py @@ -200,3 +200,9 @@ def test_in_(filter_request_builder): builder = filter_request_builder.in_("x", ["a", "b"]) assert str(builder.params) == "x=in.%28a%2Cb%29" + + +def test_in_(filter_request_builder): + builder = filter_request_builder.or_("x.eq.1") + + assert str(builder.params) == "or=%28x.eq.1%29" diff --git a/tests/_async/test_filter_request_builder_integration.py b/tests/_async/test_filter_request_builder_integration.py new file mode 100644 index 00000000..0e2951d3 --- /dev/null +++ b/tests/_async/test_filter_request_builder_integration.py @@ -0,0 +1,280 @@ +from .client import rest_client + + +async def test_multivalued_param(): + res = ( + await rest_client() + .from_("countries") + .select("country_name, iso", count="exact") + .lte("numcode", 8) + .gte("numcode", 4) + .execute() + ) + + assert res.count == 2 + assert res.data == [ + {"country_name": "AFGHANISTAN", "iso": "AF"}, + {"country_name": "ALBANIA", "iso": "AL"}, + ] + + +async def test_match(): + res = ( + await rest_client() + .from_("countries") + .select("country_name, iso") + .match({"numcode": 8, "nicename": "Albania"}) + .single() + .execute() + ) + + assert res.data == {"country_name": "ALBANIA", "iso": "AL"} + + +async def test_equals(): + res = ( + await rest_client() + .from_("countries") + .select("country_name, iso") + .eq("nicename", "Albania") + .single() + .execute() + ) + + assert res.data == {"country_name": "ALBANIA", "iso": "AL"} + + +async def test_not_equal(): + res = ( + await rest_client() + .from_("users") + .select("id, name") + .neq("name", "Jane") + .single() + .execute() + ) + + assert res.data == {"id": 1, "name": "Michael"} + + +async def test_greater_than(): + res = ( + await rest_client() + .from_("users") + .select("id, name") + .gt("id", 1) + .single() + .execute() + ) + + assert res.data == {"id": 2, "name": "Jane"} + + +async def test_greater_than_or_equals_to(): + res = await rest_client().from_("users").select("id, name").gte("id", 1).execute() + + assert res.data == [{"id": 1, "name": "Michael"}, {"id": 2, "name": "Jane"}] + + +async def test_contains_dictionary(): + res = ( + await rest_client() + .from_("users") + .select("name") + .contains("address", {"postcode": 90210}) + .single() + .execute() + ) + + assert res.data == {"name": "Michael"} + + +async def test_contains_any_item(): + res = ( + await rest_client() + .from_("issues") + .select("title") + .contains("tags", ["is:open", "priority:low"]) + .execute() + ) + + assert res.data == [{"title": "Cache invalidation is not working"}] + + +async def test_contains_on_range(): + res = ( + await rest_client() + .from_("reservations") + .select("id, room_name") + .contains("during", "[2000-01-01 13:00, 2000-01-01 13:30)") + .execute() + ) + + assert res.data == [{"id": 1, "room_name": "Emerald"}] + + +async def test_contained_by_mixed_items(): + res = ( + await rest_client() + .from_("reservations") + .select("id, room_name") + .contained_by("during", "[2000-01-01 00:00, 2000-01-01 23:59)") + .execute() + ) + + assert res.data == [{"id": 1, "room_name": "Emerald"}] + + +async def test_range_greater_than(): + res = ( + await rest_client() + .from_("reservations") + .select("id, room_name") + .range_gt("during", ["2000-01-02 08:00", "2000-01-02 09:00"]) + .execute() + ) + + assert res.data == [{"id": 2, "room_name": "Topaz"}] + + +async def test_range_greater_than_or_equal_to(): + res = ( + await rest_client() + .from_("reservations") + .select("id, room_name") + .range_gte("during", ["2000-01-02 08:30", "2000-01-02 09:30"]) + .execute() + ) + + assert res.data == [{"id": 2, "room_name": "Topaz"}] + + +async def test_range_less_than(): + res = ( + await rest_client() + .from_("reservations") + .select("id, room_name") + .range_lt("during", ["2000-01-01 15:00", "2000-01-02 16:00"]) + .execute() + ) + + assert res.data == [{"id": 1, "room_name": "Emerald"}] + + +async def test_range_less_than_or_equal_to(): + res = ( + await rest_client() + .from_("reservations") + .select("id, room_name") + .range_lte("during", ["2000-01-01 14:00", "2000-01-01 16:00"]) + .execute() + ) + + assert res.data == [{"id": 1, "room_name": "Emerald"}] + + +async def test_range_adjacent(): + res = ( + await rest_client() + .from_("reservations") + .select("id, room_name") + .range_adjacent("during", ["2000-01-01 12:00", "2000-01-01 13:00"]) + .execute() + ) + + assert res.data == [{"id": 1, "room_name": "Emerald"}] + + +async def test_overlaps(): + res = ( + await rest_client() + .from_("issues") + .select("title") + .overlaps("tags", ["is:closed", "severity:high"]) + .execute() + ) + + assert res.data == [{"title": "Cache invalidation is not working"}] + + +async def test_like(): + res = ( + await rest_client() + .from_("countries") + .select("country_name, iso") + .like("nicename", "%Alba%") + .execute() + ) + + assert res.data == [{"country_name": "ALBANIA", "iso": "AL"}] + + +async def test_ilike(): + res = ( + await rest_client() + .from_("countries") + .select("country_name, iso") + .ilike("nicename", "%alban%") + .execute() + ) + + assert res.data == [{"country_name": "ALBANIA", "iso": "AL"}] + + +async def test_is_(): + res = ( + await rest_client() + .from_("countries") + .select("country_name, iso") + .is_("numcode", "null") + .limit(1) + .order("nicename") + .execute() + ) + + assert res.data == [{"country_name": "ANTARCTICA", "iso": "AQ"}] + + +async def test_in_(): + res = ( + await rest_client() + .from_("countries") + .select("country_name, iso") + .in_("nicename", ["Albania", "Algeria"]) + .execute() + ) + + assert res.data == [ + {"country_name": "ALBANIA", "iso": "AL"}, + {"country_name": "ALGERIA", "iso": "DZ"}, + ] + + +async def test_or_(): + res = ( + await rest_client() + .from_("countries") + .select("country_name, iso") + .or_("iso.eq.DZ,nicename.eq.Albania") + .execute() + ) + + assert res.data == [ + {"country_name": "ALBANIA", "iso": "AL"}, + {"country_name": "ALGERIA", "iso": "DZ"}, + ] + + +async def test_or_with_and(): + res = ( + await rest_client() + .from_("countries") + .select("country_name, iso") + .or_("phonecode.gt.506,and(iso.eq.AL,nicename.eq.Albania)") + .execute() + ) + + assert res.data == [ + {"country_name": "ALBANIA", "iso": "AL"}, + {"country_name": "TRINIDAD AND TOBAGO", "iso": "TT"}, + ] diff --git a/tests/_sync/client.py b/tests/_sync/client.py new file mode 100644 index 00000000..14baddea --- /dev/null +++ b/tests/_sync/client.py @@ -0,0 +1,9 @@ +from postgrest import SyncPostgrestClient + +REST_URL = f"http://127.0.0.1:3000" + + +def rest_client(): + return SyncPostgrestClient( + base_url=REST_URL, + ) diff --git a/tests/_sync/test_filter_request_builder.py b/tests/_sync/test_filter_request_builder.py index a5454b97..010bdd18 100644 --- a/tests/_sync/test_filter_request_builder.py +++ b/tests/_sync/test_filter_request_builder.py @@ -200,3 +200,9 @@ def test_in_(filter_request_builder): builder = filter_request_builder.in_("x", ["a", "b"]) assert str(builder.params) == "x=in.%28a%2Cb%29" + + +def test_in_(filter_request_builder): + builder = filter_request_builder.or_("x.eq.1") + + assert str(builder.params) == "or=%28x.eq.1%29" diff --git a/tests/_sync/test_filter_request_builder_integration.py b/tests/_sync/test_filter_request_builder_integration.py new file mode 100644 index 00000000..4f945ae4 --- /dev/null +++ b/tests/_sync/test_filter_request_builder_integration.py @@ -0,0 +1,273 @@ +from .client import rest_client + + +def test_multivalued_param(): + res = ( + rest_client() + .from_("countries") + .select("country_name, iso", count="exact") + .lte("numcode", 8) + .gte("numcode", 4) + .execute() + ) + + assert res.count == 2 + assert res.data == [ + {"country_name": "AFGHANISTAN", "iso": "AF"}, + {"country_name": "ALBANIA", "iso": "AL"}, + ] + + +def test_match(): + res = ( + rest_client() + .from_("countries") + .select("country_name, iso") + .match({"numcode": 8, "nicename": "Albania"}) + .single() + .execute() + ) + + assert res.data == {"country_name": "ALBANIA", "iso": "AL"} + + +def test_equals(): + res = ( + rest_client() + .from_("countries") + .select("country_name, iso") + .eq("nicename", "Albania") + .single() + .execute() + ) + + assert res.data == {"country_name": "ALBANIA", "iso": "AL"} + + +def test_not_equal(): + res = ( + rest_client() + .from_("users") + .select("id, name") + .neq("name", "Jane") + .single() + .execute() + ) + + assert res.data == {"id": 1, "name": "Michael"} + + +def test_greater_than(): + res = rest_client().from_("users").select("id, name").gt("id", 1).single().execute() + + assert res.data == {"id": 2, "name": "Jane"} + + +def test_greater_than_or_equals_to(): + res = rest_client().from_("users").select("id, name").gte("id", 1).execute() + + assert res.data == [{"id": 1, "name": "Michael"}, {"id": 2, "name": "Jane"}] + + +def test_contains_dictionary(): + res = ( + rest_client() + .from_("users") + .select("name") + .contains("address", {"postcode": 90210}) + .single() + .execute() + ) + + assert res.data == {"name": "Michael"} + + +def test_contains_any_item(): + res = ( + rest_client() + .from_("issues") + .select("title") + .contains("tags", ["is:open", "priority:low"]) + .execute() + ) + + assert res.data == [{"title": "Cache invalidation is not working"}] + + +def test_contains_on_range(): + res = ( + rest_client() + .from_("reservations") + .select("id, room_name") + .contains("during", "[2000-01-01 13:00, 2000-01-01 13:30)") + .execute() + ) + + assert res.data == [{"id": 1, "room_name": "Emerald"}] + + +def test_contained_by_mixed_items(): + res = ( + rest_client() + .from_("reservations") + .select("id, room_name") + .contained_by("during", "[2000-01-01 00:00, 2000-01-01 23:59)") + .execute() + ) + + assert res.data == [{"id": 1, "room_name": "Emerald"}] + + +def test_range_greater_than(): + res = ( + rest_client() + .from_("reservations") + .select("id, room_name") + .range_gt("during", ["2000-01-02 08:00", "2000-01-02 09:00"]) + .execute() + ) + + assert res.data == [{"id": 2, "room_name": "Topaz"}] + + +def test_range_greater_than_or_equal_to(): + res = ( + rest_client() + .from_("reservations") + .select("id, room_name") + .range_gte("during", ["2000-01-02 08:30", "2000-01-02 09:30"]) + .execute() + ) + + assert res.data == [{"id": 2, "room_name": "Topaz"}] + + +def test_range_less_than(): + res = ( + rest_client() + .from_("reservations") + .select("id, room_name") + .range_lt("during", ["2000-01-01 15:00", "2000-01-02 16:00"]) + .execute() + ) + + assert res.data == [{"id": 1, "room_name": "Emerald"}] + + +def test_range_less_than_or_equal_to(): + res = ( + rest_client() + .from_("reservations") + .select("id, room_name") + .range_lte("during", ["2000-01-01 14:00", "2000-01-01 16:00"]) + .execute() + ) + + assert res.data == [{"id": 1, "room_name": "Emerald"}] + + +def test_range_adjacent(): + res = ( + rest_client() + .from_("reservations") + .select("id, room_name") + .range_adjacent("during", ["2000-01-01 12:00", "2000-01-01 13:00"]) + .execute() + ) + + assert res.data == [{"id": 1, "room_name": "Emerald"}] + + +def test_overlaps(): + res = ( + rest_client() + .from_("issues") + .select("title") + .overlaps("tags", ["is:closed", "severity:high"]) + .execute() + ) + + assert res.data == [{"title": "Cache invalidation is not working"}] + + +def test_like(): + res = ( + rest_client() + .from_("countries") + .select("country_name, iso") + .like("nicename", "%Alba%") + .execute() + ) + + assert res.data == [{"country_name": "ALBANIA", "iso": "AL"}] + + +def test_ilike(): + res = ( + rest_client() + .from_("countries") + .select("country_name, iso") + .ilike("nicename", "%alban%") + .execute() + ) + + assert res.data == [{"country_name": "ALBANIA", "iso": "AL"}] + + +def test_is_(): + res = ( + rest_client() + .from_("countries") + .select("country_name, iso") + .is_("numcode", "null") + .limit(1) + .order("nicename") + .execute() + ) + + assert res.data == [{"country_name": "ANTARCTICA", "iso": "AQ"}] + + +def test_in_(): + res = ( + rest_client() + .from_("countries") + .select("country_name, iso") + .in_("nicename", ["Albania", "Algeria"]) + .execute() + ) + + assert res.data == [ + {"country_name": "ALBANIA", "iso": "AL"}, + {"country_name": "ALGERIA", "iso": "DZ"}, + ] + + +def test_or_(): + res = ( + rest_client() + .from_("countries") + .select("country_name, iso") + .or_("iso.eq.DZ,nicename.eq.Albania") + .execute() + ) + + assert res.data == [ + {"country_name": "ALBANIA", "iso": "AL"}, + {"country_name": "ALGERIA", "iso": "DZ"}, + ] + + +def test_or_with_and(): + res = ( + rest_client() + .from_("countries") + .select("country_name, iso") + .or_("phonecode.gt.506,and(iso.eq.AL,nicename.eq.Albania)") + .execute() + ) + + assert res.data == [ + {"country_name": "ALBANIA", "iso": "AL"}, + {"country_name": "TRINIDAD AND TOBAGO", "iso": "TT"}, + ] From 72f4b697c575918b0a907750fee73cf8fe3f888b Mon Sep 17 00:00:00 2001 From: "sourcery-ai[bot]" <58596630+sourcery-ai[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 19:34:09 +0800 Subject: [PATCH 2/5] add or filter along with tests (Sourcery refactored) (#356) Co-authored-by: Sourcery AI <> --- tests/_async/client.py | 2 +- tests/_sync/client.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/_async/client.py b/tests/_async/client.py index b0a066e9..cb97e6d0 100644 --- a/tests/_async/client.py +++ b/tests/_async/client.py @@ -1,6 +1,6 @@ from postgrest import AsyncPostgrestClient -REST_URL = f"http://127.0.0.1:3000" +REST_URL = "http://127.0.0.1:3000" def rest_client(): diff --git a/tests/_sync/client.py b/tests/_sync/client.py index 14baddea..7b3f3e09 100644 --- a/tests/_sync/client.py +++ b/tests/_sync/client.py @@ -1,6 +1,6 @@ from postgrest import SyncPostgrestClient -REST_URL = f"http://127.0.0.1:3000" +REST_URL = "http://127.0.0.1:3000" def rest_client(): From 840a39607df4bcdefdc14daa581703cb005a0dc7 Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Mon, 15 Jan 2024 12:00:24 +0000 Subject: [PATCH 3/5] add reference table test for the or method --- Makefile | 2 +- infra/init.sql | 43 +++++++++++++------ ...test_filter_request_builder_integration.py | 36 ++++++++++++++++ ...test_filter_request_builder_integration.py | 36 ++++++++++++++++ 4 files changed, 102 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index a02124d6..9334d973 100644 --- a/Makefile +++ b/Makefile @@ -37,4 +37,4 @@ remove_pytest_asyncio_from_sync: sed -i 's/@pytest.mark.asyncio//g' tests/_sync/test_client.py sleep: - sleep 20 + sleep 5 diff --git a/infra/init.sql b/infra/init.sql index 0b9f8ba4..a12a53bd 100644 --- a/infra/init.sql +++ b/infra/init.sql @@ -1,5 +1,5 @@ CREATE TABLE public.countries ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + id int8 PRIMARY KEY, iso CHAR (2) NOT NULL, country_name VARCHAR (80) NOT NULL, nicename VARCHAR (80) NOT NULL, @@ -8,19 +8,34 @@ CREATE TABLE public.countries ( phonecode INT NOT NULL ); -INSERT INTO public.countries (iso, country_name, nicename, iso3, numcode, phonecode) VALUES - ('AF', 'AFGHANISTAN', 'Afghanistan', 'AFG', 4, 93), - ('AL', 'ALBANIA', 'Albania', 'ALB', 8, 355), - ('DZ', 'ALGERIA', 'Algeria', 'DZA', 12, 213), - ('AQ', 'ANTARCTICA', 'Antarctica', NULL, NULL, 0), - ('CR', 'COSTA RICA', 'Costa Rica', 'CRI', 188, 506), - ('ES', 'SPAIN', 'Spain', 'ESP', 724, 34), - ('TH', 'THAILAND', 'Thailand', 'THA', 764, 66), - ('TG', 'TOGO', 'Togo', 'TGO', 768, 228), - ('TT', 'TRINIDAD AND TOBAGO', 'Trinidad and Tobago', 'TTO', 780, 1868), - ('GB', 'UNITED KINGDOM', 'United Kingdom', 'GBR', 826, 44), - ('US', 'UNITED STATES', 'United States', 'USA', 840, 1), - ('ZW', 'ZIMBABWE', 'Zimbabwe', 'ZWE', 716, 263); +INSERT INTO public.countries (id, iso, country_name, nicename, iso3, numcode, phonecode) VALUES + (1, 'AF', 'AFGHANISTAN', 'Afghanistan', 'AFG', 4, 93), + (2, 'AL', 'ALBANIA', 'Albania', 'ALB', 8, 355), + (3, 'DZ', 'ALGERIA', 'Algeria', 'DZA', 12, 213), + (4, 'AQ', 'ANTARCTICA', 'Antarctica', NULL, NULL, 0), + (5, 'CR', 'COSTA RICA', 'Costa Rica', 'CRI', 188, 506), + (6, 'ES', 'SPAIN', 'Spain', 'ESP', 724, 34), + (7, 'TH', 'THAILAND', 'Thailand', 'THA', 764, 66), + (8, 'TG', 'TOGO', 'Togo', 'TGO', 768, 228), + (9, 'TT', 'TRINIDAD AND TOBAGO', 'Trinidad and Tobago', 'TTO', 780, 1868), + (10, 'GB', 'UNITED KINGDOM', 'United Kingdom', 'GBR', 826, 44), + (11, 'US', 'UNITED STATES', 'United States', 'USA', 840, 1), + (12, 'ZW', 'ZIMBABWE', 'Zimbabwe', 'ZWE', 716, 263); + +create table public.cities ( + id int8 primary key, + country_id int8 not null references public.countries, + name text +); + +insert into public.cities (id, name, country_id) values + (1, 'London', 10), + (2, 'Manchester', 10), + (3, 'Liverpool', 10), + (4, 'Bristol', 10), + (5, 'Miami', 11), + (6, 'Huston', 11), + (7, 'Atlanta', 11); create table public.users ( id int8 primary key, diff --git a/tests/_async/test_filter_request_builder_integration.py b/tests/_async/test_filter_request_builder_integration.py index 0e2951d3..8ba9cdcc 100644 --- a/tests/_async/test_filter_request_builder_integration.py +++ b/tests/_async/test_filter_request_builder_integration.py @@ -235,6 +235,20 @@ async def test_is_(): assert res.data == [{"country_name": "ANTARCTICA", "iso": "AQ"}] +async def test_is_not(): + res = ( + await rest_client() + .from_("countries") + .select("country_name, iso") + .not_.is_("numcode", "null") + .limit(1) + .order("nicename") + .execute() + ) + + assert res.data == [{"country_name": "AFGHANISTAN", "iso": "AF"}] + + async def test_in_(): res = ( await rest_client() @@ -278,3 +292,25 @@ async def test_or_with_and(): {"country_name": "ALBANIA", "iso": "AL"}, {"country_name": "TRINIDAD AND TOBAGO", "iso": "TT"}, ] + + +async def test_or_on_reference_table(): + res = ( + await rest_client() + .from_("countries") + .select("country_name, cities!inner(name)") + .or_("country_id.eq.10,name.eq.Paris", reference_table="cities") + .execute() + ) + + assert res.data == [ + { + "country_name": "UNITED KINGDOM", + "cities": [ + {"name": "London"}, + {"name": "Manchester"}, + {"name": "Liverpool"}, + {"name": "Bristol"}, + ], + }, + ] diff --git a/tests/_sync/test_filter_request_builder_integration.py b/tests/_sync/test_filter_request_builder_integration.py index 4f945ae4..35751fb3 100644 --- a/tests/_sync/test_filter_request_builder_integration.py +++ b/tests/_sync/test_filter_request_builder_integration.py @@ -228,6 +228,20 @@ def test_is_(): assert res.data == [{"country_name": "ANTARCTICA", "iso": "AQ"}] +def test_is_not(): + res = ( + rest_client() + .from_("countries") + .select("country_name, iso") + .not_.is_("numcode", "null") + .limit(1) + .order("nicename") + .execute() + ) + + assert res.data == [{"country_name": "AFGHANISTAN", "iso": "AF"}] + + def test_in_(): res = ( rest_client() @@ -271,3 +285,25 @@ def test_or_with_and(): {"country_name": "ALBANIA", "iso": "AL"}, {"country_name": "TRINIDAD AND TOBAGO", "iso": "TT"}, ] + + +def test_or_on_reference_table(): + res = ( + rest_client() + .from_("countries") + .select("country_name, cities!inner(name)") + .or_("country_id.eq.10,name.eq.Paris", reference_table="cities") + .execute() + ) + + assert res.data == [ + { + "country_name": "UNITED KINGDOM", + "cities": [ + {"name": "London"}, + {"name": "Manchester"}, + {"name": "Liverpool"}, + {"name": "Bristol"}, + ], + }, + ] From fe3cb7fa59a1d4f8998daf6899556e11aff23be4 Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Mon, 15 Jan 2024 14:03:25 +0000 Subject: [PATCH 4/5] add tests for or in and contains --- infra/init.sql | 4 +++- tests/_async/test_filter_request_builder.py | 11 +++++++++- ...test_filter_request_builder_integration.py | 21 ++++++++++++++++++- tests/_sync/test_filter_request_builder.py | 11 +++++++++- ...test_filter_request_builder_integration.py | 21 ++++++++++++++++++- 5 files changed, 63 insertions(+), 5 deletions(-) diff --git a/infra/init.sql b/infra/init.sql index a12a53bd..3aad2eb2 100644 --- a/infra/init.sql +++ b/infra/init.sql @@ -66,4 +66,6 @@ create table public.issues ( insert into public.issues (id, title, tags) values (1, 'Cache invalidation is not working', array['is:open', 'severity:high', 'priority:low']), - (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']); + (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']), + (3, 'Add missing postgrest filters', array['is:open', 'severity:low', 'priority:high']), + (4, 'Add alias to filters', array['is:closed', 'severity:low', 'priority:medium']); diff --git a/tests/_async/test_filter_request_builder.py b/tests/_async/test_filter_request_builder.py index ede40add..a8ead1b1 100644 --- a/tests/_async/test_filter_request_builder.py +++ b/tests/_async/test_filter_request_builder.py @@ -202,7 +202,16 @@ def test_in_(filter_request_builder): assert str(builder.params) == "x=in.%28a%2Cb%29" -def test_in_(filter_request_builder): +def test_or_(filter_request_builder): builder = filter_request_builder.or_("x.eq.1") assert str(builder.params) == "or=%28x.eq.1%29" + + +def test_or_in_contain(filter_request_builder): + builder = filter_request_builder.or_("id.in.(5,6,7), arraycol.cs.{'a','b'}") + + assert ( + str(builder.params) + == "or=%28id.in.%285%2C6%2C7%29%2C%20arraycol.cs.%7B%27a%27%2C%27b%27%7D%29" + ) diff --git a/tests/_async/test_filter_request_builder_integration.py b/tests/_async/test_filter_request_builder_integration.py index 8ba9cdcc..14a651ab 100644 --- a/tests/_async/test_filter_request_builder_integration.py +++ b/tests/_async/test_filter_request_builder_integration.py @@ -194,7 +194,10 @@ async def test_overlaps(): .execute() ) - assert res.data == [{"title": "Cache invalidation is not working"}] + assert res.data == [ + {"title": "Cache invalidation is not working"}, + {"title": "Add alias to filters"}, + ] async def test_like(): @@ -294,6 +297,22 @@ async def test_or_with_and(): ] +async def test_or_in(): + res = ( + await rest_client() + .from_("issues") + .select("id, title") + .or_("id.in.(1,4),tags.cs.{is:open,priority:high}") + .execute() + ) + + assert res.data == [ + {"id": 1, "title": "Cache invalidation is not working"}, + {"id": 3, "title": "Add missing postgrest filters"}, + {"id": 4, "title": "Add alias to filters"}, + ] + + async def test_or_on_reference_table(): res = ( await rest_client() diff --git a/tests/_sync/test_filter_request_builder.py b/tests/_sync/test_filter_request_builder.py index 010bdd18..c43da95b 100644 --- a/tests/_sync/test_filter_request_builder.py +++ b/tests/_sync/test_filter_request_builder.py @@ -202,7 +202,16 @@ def test_in_(filter_request_builder): assert str(builder.params) == "x=in.%28a%2Cb%29" -def test_in_(filter_request_builder): +def test_or_(filter_request_builder): builder = filter_request_builder.or_("x.eq.1") assert str(builder.params) == "or=%28x.eq.1%29" + + +def test_or_in_contain(filter_request_builder): + builder = filter_request_builder.or_("id.in.(5,6,7), arraycol.cs.{'a','b'}") + + assert ( + str(builder.params) + == "or=%28id.in.%285%2C6%2C7%29%2C%20arraycol.cs.%7B%27a%27%2C%27b%27%7D%29" + ) diff --git a/tests/_sync/test_filter_request_builder_integration.py b/tests/_sync/test_filter_request_builder_integration.py index 35751fb3..e16178d3 100644 --- a/tests/_sync/test_filter_request_builder_integration.py +++ b/tests/_sync/test_filter_request_builder_integration.py @@ -187,7 +187,10 @@ def test_overlaps(): .execute() ) - assert res.data == [{"title": "Cache invalidation is not working"}] + assert res.data == [ + {"title": "Cache invalidation is not working"}, + {"title": "Add alias to filters"}, + ] def test_like(): @@ -287,6 +290,22 @@ def test_or_with_and(): ] +def test_or_in(): + res = ( + rest_client() + .from_("issues") + .select("id, title") + .or_("id.in.(1,4),tags.cs.{is:open,priority:high}") + .execute() + ) + + assert res.data == [ + {"id": 1, "title": "Cache invalidation is not working"}, + {"id": 3, "title": "Add missing postgrest filters"}, + {"id": 4, "title": "Add alias to filters"}, + ] + + def test_or_on_reference_table(): res = ( rest_client() From a3d4210eaa0ce5ed421244770af45ac06da8010f Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Mon, 15 Jan 2024 14:10:11 +0000 Subject: [PATCH 5/5] update dependencies --- poetry.lock | 6 +++--- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 07edf35d..1829aed1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1145,13 +1145,13 @@ pytest = ">=3" [[package]] name = "python-gitlab" -version = "4.3.0" +version = "4.4.0" description = "A python wrapper for the GitLab API" optional = false python-versions = ">=3.8.0" files = [ - {file = "python-gitlab-4.3.0.tar.gz", hash = "sha256:eb31d1f2bfd8653f74996f9d0bf84ce7afb0843f9122a257c9a93b0e027d1df0"}, - {file = "python_gitlab-4.3.0-py3-none-any.whl", hash = "sha256:cc1dc49c562c02ffbad3656e668234c45ea6210688ade59865b284313f45000d"}, + {file = "python-gitlab-4.4.0.tar.gz", hash = "sha256:1d117bf7b433ae8255e5d74e72c660978f50ee85eb62248c9fb52ef43c3e3814"}, + {file = "python_gitlab-4.4.0-py3-none-any.whl", hash = "sha256:cdad39d016f59664cdaad0f878f194c79cb4357630776caa9a92c1da25c8d986"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index 6a8981c4..c0c86b40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "postgrest" version = "0.13.2" description = "PostgREST client for Python. This library provides an ORM interface to PostgREST." -authors = ["Lương Quang Mạnh ", "Joel Lee ", "Anand", "Oliver Rice"] +authors = ["Lương Quang Mạnh ", "Joel Lee ", "Anand", "Oliver Rice", "Andrew Smith "] homepage = "https://github.com/supabase-community/postgrest-py" repository = "https://github.com/supabase-community/postgrest-py" documentation = "https://postgrest-py.rtfd.io"