Skip to content

Commit d115f0d

Browse files
committed
Update RequestBuilder
1 parent 12734e1 commit d115f0d

File tree

5 files changed

+91
-128
lines changed

5 files changed

+91
-128
lines changed

CHANGELOG.md

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
## CHANGELOG
22

3+
### Unreleased
4+
5+
#### Changed
6+
7+
- Change behavior of `RequestBuilder.filter()`
8+
- Change signature of general filters
9+
10+
#### Removed
11+
12+
- Remove `RequestBuilder.filter_in()` and `RequestBuilder.filter_out()`
13+
314
### v0.2.0
415

516
#### Added

TODO.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
- [ ] Multi-criteria ordering
44
- [ ] Exact match filtering
5-
- [ ] OR filter
5+
- [ ] AND and OR filters
6+
- [ ] Counting
67
- [ ] Input validation
78
- [ ] Tests
89
- [ ] CI/CD

postgrest_py/client.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77

88
class PostgrestClient:
9+
"""PostgREST client."""
10+
911
def __init__(self, base_url: str, *, schema="public") -> None:
1012
headers = {
1113
"Accept": "application/json",
@@ -24,39 +26,40 @@ async def __aexit__(self, exc_type, exc, tb) -> None:
2426
async def aclose(self) -> None:
2527
await self.session.aclose()
2628

27-
def auth(self, bearer_token: str, *, username: str = None, password: str = None):
28-
"""Authenticate the request, with either bearer token or basic authentication."""
29-
29+
def auth(self, token: str, *, username: str = None, password: str = None):
30+
"""Authenticate the client with either bearer token or basic authentication."""
3031
if username:
3132
self.session.auth = (username, password)
3233
else:
33-
self.session.headers["Authorization"] = f"Bearer {bearer_token}"
34+
self.session.headers["Authorization"] = f"Bearer {token}"
3435
return self
3536

3637
def schema(self, schema: str):
38+
"""Switch to another schema."""
3739
self.session.merge_headers(
3840
{"Accept-Profile": schema, "Content-Profile": schema}
3941
)
4042
return self
4143

4244
def from_(self, table: str) -> RequestBuilder:
45+
"""Perform a table operation."""
4346
return RequestBuilder(self.session, f"/{table}")
4447

4548
@deprecated("0.2.0", "1.0.0", __version__, "Use PostgrestClient.from_() instead")
4649
def from_table(self, table: str) -> RequestBuilder:
4750
"""Alias to Self.from_()."""
48-
4951
return self.from_(table)
5052

5153
async def rpc(self, func: str, params: dict) -> Response:
52-
"""Execute a stored procedure call."""
53-
54+
"""Perform a stored procedure call."""
5455
path = f"/rpc/{func}"
5556
r = await self.session.post(path, json=params)
5657
return r
5758

5859

5960
class Client(PostgrestClient):
61+
"""Alias to PostgrestClient."""
62+
6063
@deprecated("0.2.0", "1.0.0", __version__, "Use PostgrestClient instead")
6164
def __init__(self, *args, **kwargs):
6265
super().__init__(*args, **kwargs)

postgrest_py/request_builder.py

+63-120
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
from typing import Iterable, Tuple
2+
13
from httpx import AsyncClient, Response
24

5+
from postgrest_py.utils import sanitize_param
6+
37

48
class RequestBuilder:
59
def __init__(self, session: AsyncClient, path: str) -> None:
@@ -42,150 +46,89 @@ async def execute(self) -> Response:
4246
r = await self.session.request(self.http_method, self.path, json=self.json)
4347
return r
4448

45-
def filter_in(self, column: str, operator: str, criteria: str):
46-
self.session.params[column] = f"{operator}.{criteria}"
47-
return self
48-
4949
def filter(self, column: str, operator: str, criteria: str):
50-
"""Alias to Self.filter_in()."""
51-
52-
return self.filter_in(column, operator, criteria)
53-
54-
def filter_out(self, column: str, operator: str, criteria: str):
55-
self.session.params[column] = f"not.{operator}.{criteria}"
56-
return self
57-
58-
def eq(self, column: str, criteria: str):
50+
"""Either filter in or filter out based on Self.negate_next."""
5951
if self.negate_next == True:
6052
self.negate_next = False
61-
return self.filter_out(column, "eq", criteria)
62-
return self.filter_in(column, "eq", criteria)
53+
operator = f"not.{operator}"
54+
self.session.params[sanitize_param(column)] = f"{operator}.{criteria}"
55+
return self
6356

64-
def neq(self, column: str, criteria: str):
65-
if self.negate_next == True:
66-
self.negate_next = False
67-
return self.filter_out(column, "neq", criteria)
68-
return self.filter_in(column, "neq", criteria)
57+
def eq(self, column: str, value: str):
58+
return self.filter(column, "eq", sanitize_param(value))
6959

70-
def gt(self, column: str, criteria: str):
71-
if self.negate_next == True:
72-
self.negate_next = False
73-
return self.filter_out(column, "gt", criteria)
74-
return self.filter_in(column, "gt", criteria)
60+
def neq(self, column: str, value: str):
61+
return self.filter(column, "neq", sanitize_param(value))
7562

76-
def gte(self, column: str, criteria: str):
77-
if self.negate_next == True:
78-
self.negate_next = False
79-
return self.filter_out(column, "gte", criteria)
80-
return self.filter_in(column, "gte", criteria)
63+
def gt(self, column: str, value: str):
64+
return self.filter(column, "gt", sanitize_param(value))
8165

82-
def lt(self, column: str, criteria: str):
83-
if self.negate_next == True:
84-
self.negate_next = False
85-
return self.filter_out(column, "lt", criteria)
86-
return self.filter_in(column, "lt", criteria)
66+
def gte(self, column: str, value: str):
67+
return self.filter(column, "gte", sanitize_param(value))
8768

88-
def lte(self, column: str, criteria: str):
89-
if self.negate_next == True:
90-
self.negate_next = False
91-
return self.filter_out(column, "lte", criteria)
92-
return self.filter_in(column, "lte", criteria)
69+
def lt(self, column: str, value: str):
70+
return self.filter(column, "lt", sanitize_param(value))
9371

94-
def like(self, column: str, criteria: str):
95-
if self.negate_next == True:
96-
self.negate_next = False
97-
return self.filter_out(column, "like", criteria)
98-
return self.filter_in(column, "like", criteria)
72+
def lte(self, column: str, value: str):
73+
return self.filter(column, "lte", sanitize_param(value))
9974

100-
def ilike(self, column: str, criteria: str):
101-
if self.negate_next == True:
102-
self.negate_next = False
103-
return self.filter_out(column, "ilike", criteria)
104-
return self.filter_in(column, "ilike", criteria)
75+
def is_(self, column: str, value: str):
76+
return self.filter(column, "is", sanitize_param(value))
10577

106-
def is_(self, column: str, criteria: str):
107-
if self.negate_next == True:
108-
self.negate_next = False
109-
return self.filter_out(column, "is", criteria)
110-
return self.filter_in(column, "is", criteria)
78+
def like(self, column: str, pattern: str):
79+
pattern = pattern.replace("%", "*")
80+
return self.filter(column, "like", pattern)
11181

112-
def in_(self, column: str, criteria: str):
113-
if self.negate_next == True:
114-
self.negate_next = False
115-
return self.filter_out(column, "in", criteria)
116-
return self.filter_in(column, "in", criteria)
82+
def ilike(self, column: str, pattern: str):
83+
pattern = pattern.replace("%", "*")
84+
return self.filter(column, "ilike", pattern)
11785

118-
def fts(self, column: str, criteria: str):
119-
if self.negate_next == True:
120-
self.negate_next = False
121-
return self.filter_out(column, "fts", criteria)
122-
return self.filter_in(column, "fts", criteria)
86+
def in_(self, column: str, values: Iterable[str]):
87+
values = map(sanitize_param, values)
88+
values = ",".join(values)
89+
return self.filter(column, "in", f"({values})")
12390

124-
def plfts(self, column: str, criteria: str):
125-
if self.negate_next == True:
126-
self.negate_next = False
127-
return self.filter_out(column, "plfts", criteria)
128-
return self.filter_in(column, "plfts", criteria)
91+
def cs(self, column: str, values: Iterable[str]):
92+
values = map(sanitize_param, values)
93+
values = ",".join(values)
94+
return self.filter(column, "cs", f"{{values}}")
12995

130-
def phfts(self, column: str, criteria: str):
131-
if self.negate_next == True:
132-
self.negate_next = False
133-
return self.filter_out(column, "phfts", criteria)
134-
return self.filter_in(column, "phfts", criteria)
96+
def cd(self, column: str, values: Iterable[str]):
97+
values = map(sanitize_param, values)
98+
values = ",".join(values)
99+
return self.filter(column, "cd", f"{{values}}")
135100

136-
def wfts(self, column: str, criteria: str):
137-
if self.negate_next == True:
138-
self.negate_next = False
139-
return self.filter_out(column, "wfts", criteria)
140-
return self.filter_in(column, "wfts", criteria)
101+
def ov(self, column: str, values: Iterable[str]):
102+
values = map(sanitize_param, values)
103+
values = ",".join(values)
104+
return self.filter(column, "ov", f"{{values}}")
141105

142-
def cs(self, column: str, criteria: str):
143-
if self.negate_next == True:
144-
self.negate_next = False
145-
return self.filter_out(column, "cs", criteria)
146-
return self.filter_in(column, "cs", criteria)
106+
def fts(self, column: str, query: str):
107+
return self.filter(column, "fts", sanitize_param(query))
147108

148-
def cd(self, column: str, criteria: str):
149-
if self.negate_next == True:
150-
self.negate_next = False
151-
return self.filter_out(column, "cd", criteria)
152-
return self.filter_in(column, "cd", criteria)
109+
def plfts(self, column: str, query: str):
110+
return self.filter(column, "plfts", sanitize_param(query))
153111

154-
def ov(self, column: str, criteria: str):
155-
if self.negate_next == True:
156-
self.negate_next = False
157-
return self.filter_out(column, "ov", criteria)
158-
return self.filter_in(column, "ov", criteria)
112+
def phfts(self, column: str, query: str):
113+
return self.filter(column, "phfts", sanitize_param(query))
159114

160-
def sl(self, column: str, criteria: str):
161-
if self.negate_next == True:
162-
self.negate_next = False
163-
return self.filter_out(column, "sl", criteria)
164-
return self.filter_in(column, "sl", criteria)
115+
def wfts(self, column: str, query: str):
116+
return self.filter(column, "wfts", sanitize_param(query))
165117

166-
def sr(self, column: str, criteria: str):
167-
if self.negate_next == True:
168-
self.negate_next = False
169-
return self.filter_out(column, "sr", criteria)
170-
return self.filter_in(column, "sr", criteria)
118+
def sl(self, column: str, range: Tuple[int, int]):
119+
return self.filter(column, "sl", f"({range[0]},{range[1]})")
171120

172-
def nxr(self, column: str, criteria: str):
173-
if self.negate_next == True:
174-
self.negate_next = False
175-
return self.filter_out(column, "nxr", criteria)
176-
return self.filter_in(column, "nxr", criteria)
121+
def sr(self, column: str, range: Tuple[int, int]):
122+
return self.filter(column, "sr", f"({range[0]},{range[1]})")
177123

178-
def nxl(self, column: str, criteria: str):
179-
if self.negate_next == True:
180-
self.negate_next = False
181-
return self.filter_out(column, "nxl", criteria)
182-
return self.filter_in(column, "nxl", criteria)
124+
def nxl(self, column: str, range: Tuple[int, int]):
125+
return self.filter(column, "nxl", f"({range[0]},{range[1]})")
183126

184-
def adj(self, column: str, criteria: str):
185-
if self.negate_next == True:
186-
self.negate_next = False
187-
return self.filter_out(column, "adj", criteria)
188-
return self.filter_in(column, "adj", criteria)
127+
def nxr(self, column: str, range: Tuple[int, int]):
128+
return self.filter(column, "nxr", f"({range[0]},{range[1]})")
129+
130+
def adj(self, column: str, range: Tuple[int, int]):
131+
return self.filter(column, "adj", f"({range[0]},{range[1]})")
189132

190133

191134
class GetRequestBuilder(RequestBuilder):

postgrest_py/utils.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def sanitize_param(param: str) -> str:
2+
reserved_chars = ",.:()"
3+
if any(char in param for char in reserved_chars):
4+
return f'"{param}"'
5+
return param

0 commit comments

Comments
 (0)