From 6252bd9172feb8898d1a9d6c3bb08cccfbbd1bbe Mon Sep 17 00:00:00 2001 From: Isaac Parker Date: Sat, 12 Mar 2022 14:17:19 -0700 Subject: [PATCH 1/4] Add .contains and .contained_by operators to match JS client --- postgrest_py/base_request_builder.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/postgrest_py/base_request_builder.py b/postgrest_py/base_request_builder.py index 5853c0cc..de66396b 100644 --- a/postgrest_py/base_request_builder.py +++ b/postgrest_py/base_request_builder.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json from re import search from typing import Any, Dict, Iterable, Optional, Tuple, Type, Union @@ -209,6 +210,27 @@ def cd(self, column: str, values: Iterable[Any]): values = ",".join(values) return self.filter(column, Filters.CD, f"{{{values}}}") + def contains(self, column: str, value: Union[Iterable[Any], str, Dict[Any, Any]]): + if isinstance(value, str): + # range types can be inclusive '[', ']' or exclusive '(', ')' so just + # keep it simple and accept a string + return self.filter(column, Filters.CS, value) + if not isinstance(value, dict) and isinstance(value, Iterable): + # Expected to be some type of iterable + stringified_values = ",".join(value) + return self.filter(column, Filters.CS, f"{{{stringified_values}}}") + + return self.filter(column, Filters.CS, json.dumps(value)) + + def contained_by(self, column: str, value: Union[Iterable[Any], str, Dict[Any, Any]]): + if isinstance(value, str): + # range + return self.filter(column, Filters.CD, value) + if not isinstance(value, dict) and isinstance(value, Iterable): + stringified_values = ",".join(value) + return self.filter(column, Filters.CD, f"{{{stringified_values}}}") + return self.filter(column, Filters.CD, json.dumps(value)) + def ov(self, column: str, values: Iterable[Any]): values = map(sanitize_param, values) values = ",".join(values) From 70f380aab06b2a348f29812872557cc8b02caf17 Mon Sep 17 00:00:00 2001 From: Isaac Parker Date: Sat, 12 Mar 2022 15:46:09 -0700 Subject: [PATCH 2/4] Fix whitespace --- postgrest_py/base_request_builder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/postgrest_py/base_request_builder.py b/postgrest_py/base_request_builder.py index de66396b..ac7591cc 100644 --- a/postgrest_py/base_request_builder.py +++ b/postgrest_py/base_request_builder.py @@ -219,9 +219,9 @@ def contains(self, column: str, value: Union[Iterable[Any], str, Dict[Any, Any]] # Expected to be some type of iterable stringified_values = ",".join(value) return self.filter(column, Filters.CS, f"{{{stringified_values}}}") - + return self.filter(column, Filters.CS, json.dumps(value)) - + def contained_by(self, column: str, value: Union[Iterable[Any], str, Dict[Any, Any]]): if isinstance(value, str): # range From 0c3b99bf42780d0bdac08b9d4ad1f030a2ab71b7 Mon Sep 17 00:00:00 2001 From: Isaac Parker Date: Sun, 13 Mar 2022 19:51:40 +0000 Subject: [PATCH 3/4] Add tests --- tests/_async/test_filter_request_builder.py | 30 +++++++++++++++++++++ tests/_sync/test_filter_request_builder.py | 30 +++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/tests/_async/test_filter_request_builder.py b/tests/_async/test_filter_request_builder.py index 28554271..d60ad39d 100644 --- a/tests/_async/test_filter_request_builder.py +++ b/tests/_async/test_filter_request_builder.py @@ -40,3 +40,33 @@ def test_multivalued_param(filter_request_builder): def test_match(filter_request_builder): builder = filter_request_builder.match({"id": "1", "done": "false"}) assert str(builder.session.params) == "id=eq.1&done=eq.false" + + +def test_contains(filter_request_builder): + builder = filter_request_builder.contains("x", "a") + + assert str(builder.session.params) == "x=cs.a" + + +def test_contains_dictionary(filter_request_builder): + builder = filter_request_builder.contains("x", {"a": "b"}) + + assert str(builder.session.params) == "x=cs.%7B%22a%22%3A+%22b%22%7D" + + +def test_contains_any_item(filter_request_builder): + builder = filter_request_builder.contains("x", ["a", "b"]) + + assert str(builder.session.params) == "x=cs.%7Ba%2Cb%7D" + + +def test_contains_in_list(filter_request_builder): + builder = filter_request_builder.contains("x", '[{"a": "b"}]') + + assert str(builder.session.params) == "x=cs.%5B%7B%22a%22%3A+%22b%22%7D%5D" + + +def test_contained_by_mixed_items(filter_request_builder): + builder = filter_request_builder.contained_by("x", ["a", '["b", "c"]']) + + assert str(builder.session.params) == "x=cd.%7Ba%2C%5B%22b%22%2C+%22c%22%5D%7D" diff --git a/tests/_sync/test_filter_request_builder.py b/tests/_sync/test_filter_request_builder.py index a5a4c6ca..37476924 100644 --- a/tests/_sync/test_filter_request_builder.py +++ b/tests/_sync/test_filter_request_builder.py @@ -40,3 +40,33 @@ def test_multivalued_param(filter_request_builder): def test_match(filter_request_builder): builder = filter_request_builder.match({"id": "1", "done": "false"}) assert str(builder.session.params) == "id=eq.1&done=eq.false" + + +def test_contains(filter_request_builder): + builder = filter_request_builder.contains("x", "a") + + assert str(builder.session.params) == "x=cs.a" + + +def test_contains_dictionary(filter_request_builder): + builder = filter_request_builder.contains("x", {"a": "b"}) + + assert str(builder.session.params) == "x=cs.%7B%22a%22%3A+%22b%22%7D" + + +def test_contains_any_item(filter_request_builder): + builder = filter_request_builder.contains("x", ["a", "b"]) + + assert str(builder.session.params) == "x=cs.%7Ba%2Cb%7D" + + +def test_contains_in_list(filter_request_builder): + builder = filter_request_builder.contains("x", '[{"a": "b"}]') + + assert str(builder.session.params) == "x=cs.%5B%7B%22a%22%3A+%22b%22%7D%5D" + + +def test_contained_by_mixed_items(filter_request_builder): + builder = filter_request_builder.contained_by("x", ["a", '["b", "c"]']) + + assert str(builder.session.params) == "x=cd.%7Ba%2C%5B%22b%22%2C+%22c%22%5D%7D" From 4337de7335948b6cfe781993f988a72efb8eb63d Mon Sep 17 00:00:00 2001 From: Isaac Parker Date: Sun, 13 Mar 2022 14:30:25 -0600 Subject: [PATCH 4/4] Describe percent-encoded strings --- tests/_async/test_filter_request_builder.py | 4 ++++ tests/_sync/test_filter_request_builder.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/tests/_async/test_filter_request_builder.py b/tests/_async/test_filter_request_builder.py index d60ad39d..78c2054a 100644 --- a/tests/_async/test_filter_request_builder.py +++ b/tests/_async/test_filter_request_builder.py @@ -51,22 +51,26 @@ def test_contains(filter_request_builder): def test_contains_dictionary(filter_request_builder): builder = filter_request_builder.contains("x", {"a": "b"}) + # {"a":"b"} assert str(builder.session.params) == "x=cs.%7B%22a%22%3A+%22b%22%7D" def test_contains_any_item(filter_request_builder): builder = filter_request_builder.contains("x", ["a", "b"]) + # {a,b} assert str(builder.session.params) == "x=cs.%7Ba%2Cb%7D" def test_contains_in_list(filter_request_builder): builder = filter_request_builder.contains("x", '[{"a": "b"}]') + # [{"a":+"b"}] (the + represents the space) assert str(builder.session.params) == "x=cs.%5B%7B%22a%22%3A+%22b%22%7D%5D" def test_contained_by_mixed_items(filter_request_builder): builder = filter_request_builder.contained_by("x", ["a", '["b", "c"]']) + # {a,["b",+"c"]} assert str(builder.session.params) == "x=cd.%7Ba%2C%5B%22b%22%2C+%22c%22%5D%7D" diff --git a/tests/_sync/test_filter_request_builder.py b/tests/_sync/test_filter_request_builder.py index 37476924..6d06d44e 100644 --- a/tests/_sync/test_filter_request_builder.py +++ b/tests/_sync/test_filter_request_builder.py @@ -51,22 +51,26 @@ def test_contains(filter_request_builder): def test_contains_dictionary(filter_request_builder): builder = filter_request_builder.contains("x", {"a": "b"}) + # {"a":"b"} assert str(builder.session.params) == "x=cs.%7B%22a%22%3A+%22b%22%7D" def test_contains_any_item(filter_request_builder): builder = filter_request_builder.contains("x", ["a", "b"]) + # {a,b} assert str(builder.session.params) == "x=cs.%7Ba%2Cb%7D" def test_contains_in_list(filter_request_builder): builder = filter_request_builder.contains("x", '[{"a": "b"}]') + # [{"a":+"b"}] (the + represents the space) assert str(builder.session.params) == "x=cs.%5B%7B%22a%22%3A+%22b%22%7D%5D" def test_contained_by_mixed_items(filter_request_builder): builder = filter_request_builder.contained_by("x", ["a", '["b", "c"]']) + # {a,["b",+"c"]} assert str(builder.session.params) == "x=cd.%7Ba%2C%5B%22b%22%2C+%22c%22%5D%7D"