Skip to content

Commit 7189e09

Browse files
authored
feat: add .contains and .contained_by operators to match JS client (#100)
* Add .contains and .contained_by operators to match JS client * Fix whitespace * Add tests * Describe percent-encoded strings
1 parent 7d156b3 commit 7189e09

File tree

3 files changed

+90
-0
lines changed

3 files changed

+90
-0
lines changed

postgrest_py/base_request_builder.py

+22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import json
34
from re import search
45
from typing import Any, Dict, Iterable, Optional, Tuple, Type, Union
56

@@ -209,6 +210,27 @@ def cd(self, column: str, values: Iterable[Any]):
209210
values = ",".join(values)
210211
return self.filter(column, Filters.CD, f"{{{values}}}")
211212

213+
def contains(self, column: str, value: Union[Iterable[Any], str, Dict[Any, Any]]):
214+
if isinstance(value, str):
215+
# range types can be inclusive '[', ']' or exclusive '(', ')' so just
216+
# keep it simple and accept a string
217+
return self.filter(column, Filters.CS, value)
218+
if not isinstance(value, dict) and isinstance(value, Iterable):
219+
# Expected to be some type of iterable
220+
stringified_values = ",".join(value)
221+
return self.filter(column, Filters.CS, f"{{{stringified_values}}}")
222+
223+
return self.filter(column, Filters.CS, json.dumps(value))
224+
225+
def contained_by(self, column: str, value: Union[Iterable[Any], str, Dict[Any, Any]]):
226+
if isinstance(value, str):
227+
# range
228+
return self.filter(column, Filters.CD, value)
229+
if not isinstance(value, dict) and isinstance(value, Iterable):
230+
stringified_values = ",".join(value)
231+
return self.filter(column, Filters.CD, f"{{{stringified_values}}}")
232+
return self.filter(column, Filters.CD, json.dumps(value))
233+
212234
def ov(self, column: str, values: Iterable[Any]):
213235
values = map(sanitize_param, values)
214236
values = ",".join(values)

tests/_async/test_filter_request_builder.py

+34
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,37 @@ def test_multivalued_param(filter_request_builder):
4040
def test_match(filter_request_builder):
4141
builder = filter_request_builder.match({"id": "1", "done": "false"})
4242
assert str(builder.session.params) == "id=eq.1&done=eq.false"
43+
44+
45+
def test_contains(filter_request_builder):
46+
builder = filter_request_builder.contains("x", "a")
47+
48+
assert str(builder.session.params) == "x=cs.a"
49+
50+
51+
def test_contains_dictionary(filter_request_builder):
52+
builder = filter_request_builder.contains("x", {"a": "b"})
53+
54+
# {"a":"b"}
55+
assert str(builder.session.params) == "x=cs.%7B%22a%22%3A+%22b%22%7D"
56+
57+
58+
def test_contains_any_item(filter_request_builder):
59+
builder = filter_request_builder.contains("x", ["a", "b"])
60+
61+
# {a,b}
62+
assert str(builder.session.params) == "x=cs.%7Ba%2Cb%7D"
63+
64+
65+
def test_contains_in_list(filter_request_builder):
66+
builder = filter_request_builder.contains("x", '[{"a": "b"}]')
67+
68+
# [{"a":+"b"}] (the + represents the space)
69+
assert str(builder.session.params) == "x=cs.%5B%7B%22a%22%3A+%22b%22%7D%5D"
70+
71+
72+
def test_contained_by_mixed_items(filter_request_builder):
73+
builder = filter_request_builder.contained_by("x", ["a", '["b", "c"]'])
74+
75+
# {a,["b",+"c"]}
76+
assert str(builder.session.params) == "x=cd.%7Ba%2C%5B%22b%22%2C+%22c%22%5D%7D"

tests/_sync/test_filter_request_builder.py

+34
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,37 @@ def test_multivalued_param(filter_request_builder):
4040
def test_match(filter_request_builder):
4141
builder = filter_request_builder.match({"id": "1", "done": "false"})
4242
assert str(builder.session.params) == "id=eq.1&done=eq.false"
43+
44+
45+
def test_contains(filter_request_builder):
46+
builder = filter_request_builder.contains("x", "a")
47+
48+
assert str(builder.session.params) == "x=cs.a"
49+
50+
51+
def test_contains_dictionary(filter_request_builder):
52+
builder = filter_request_builder.contains("x", {"a": "b"})
53+
54+
# {"a":"b"}
55+
assert str(builder.session.params) == "x=cs.%7B%22a%22%3A+%22b%22%7D"
56+
57+
58+
def test_contains_any_item(filter_request_builder):
59+
builder = filter_request_builder.contains("x", ["a", "b"])
60+
61+
# {a,b}
62+
assert str(builder.session.params) == "x=cs.%7Ba%2Cb%7D"
63+
64+
65+
def test_contains_in_list(filter_request_builder):
66+
builder = filter_request_builder.contains("x", '[{"a": "b"}]')
67+
68+
# [{"a":+"b"}] (the + represents the space)
69+
assert str(builder.session.params) == "x=cs.%5B%7B%22a%22%3A+%22b%22%7D%5D"
70+
71+
72+
def test_contained_by_mixed_items(filter_request_builder):
73+
builder = filter_request_builder.contained_by("x", ["a", '["b", "c"]'])
74+
75+
# {a,["b",+"c"]}
76+
assert str(builder.session.params) == "x=cd.%7Ba%2C%5B%22b%22%2C+%22c%22%5D%7D"

0 commit comments

Comments
 (0)