Skip to content

Commit 7808ea3

Browse files
authored
Merge pull request #1 from Qwizi/update
Added nested filters
2 parents 7386ba7 + 735a880 commit 7808ea3

File tree

4 files changed

+283
-200
lines changed

4 files changed

+283
-200
lines changed

model_generator.py

Lines changed: 0 additions & 156 deletions
This file was deleted.

src/swapi_client/query_builder.py

Lines changed: 113 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,24 @@
1-
from typing import Any, Dict, List, Optional
1+
from typing import Any, Dict, List, Optional, Union
22

33

44
class SWQueryBuilder:
5-
"""Helper class to build complex SW API query parameters"""
5+
"""
6+
Helper class to build complex SW API query parameters.
7+
8+
Enhanced filtering capabilities:
9+
- Basic filtering: filter("name", "value") -> filter[name][eq]=value
10+
- Operator filtering: filter("age", 18, "gte") -> filter[age][gte]=18
11+
- Nested filtering: filter(["attributes", "476"], "some text", "hasText")
12+
-> filter[attributes][476][hasText]=some text
13+
- Complex OR filters: filter_or({("attributes", "476"): {"hasText": "text"}})
14+
-> filterOr[0][attributes][476][hasText]=text
15+
- Complex AND filters: filter_and({("nested", "field"): "value"})
16+
-> filterAnd[0][nested][field][eq]=value
17+
18+
Note: Operator is always placed at the end of the filter structure for consistency.
19+
Supported operators: eq, ne, gt, lt, gte, lte, in, notIn, like, ilike, notLike,
20+
isNull, isNotNull, hasText, and more.
21+
"""
622

723
def __init__(self):
824
self.params = {}
@@ -74,67 +90,140 @@ def page_number(self, number: int = 1) -> "SWQueryBuilder":
7490
return self
7591

7692
def filter(
77-
self, field: str, value: Any = None, operator: str = "eq"
93+
self,
94+
field: Union[str, List[str]],
95+
value: Any = None,
96+
operator: str = "eq"
7897
) -> "SWQueryBuilder":
79-
"""Add filter parameter"""
98+
"""
99+
Add filter parameter with support for nested field paths.
100+
101+
Args:
102+
field: Field name as string or list of nested field names.
103+
Examples:
104+
- "name" -> filter[name][operator]
105+
- ["attributes", "476"] -> filter[attributes][476][operator]
106+
value: The value to filter by
107+
operator: The operator to use (eq, ne, gt, lt, gte, lte, in, notIn,
108+
like, ilike, notLike, isNull, isNotNull, hasText, etc.)
109+
"""
110+
# Build the field path
111+
if isinstance(field, list):
112+
field_path = "][".join(field)
113+
filter_key = f"filter[{field_path}]"
114+
else:
115+
filter_key = f"filter[{field}]"
116+
117+
# Always add operator at the end for consistency
80118
if operator in ["isNull", "isNotNull"]:
81-
self.params[f"filter[{field}][{operator}]"] = ""
82-
elif operator == "eq":
83-
self.params[f"filter[{field}]"] = str(value)
119+
self.params[f"{filter_key}[{operator}]"] = ""
84120
else:
85-
self.params[f"filter[{field}][{operator}]"] = (
121+
self.params[f"{filter_key}[{operator}]"] = (
86122
str(value)
87123
if not isinstance(value, list)
88124
else ",".join(map(str, value))
89125
)
90126
return self
91127

92128
def filter_or(
93-
self, filters: Dict[str, Any], group_index: int = 0
129+
self, filters: Dict[Union[str, tuple], Any], group_index: int = 0
94130
) -> "SWQueryBuilder":
95-
"""Add filterOr parameters"""
131+
"""
132+
Add filterOr parameters with support for nested field paths.
133+
134+
Args:
135+
filters: Dictionary where keys can be:
136+
- Simple field names (str): "name"
137+
- Nested field tuples: ("attributes", "476", "hasText")
138+
Values can be:
139+
- Simple values for equality comparison
140+
- Dict with operator and value: {"hasText": "some_value"}
141+
group_index: The OR group index
142+
"""
96143
for field, filter_config in filters.items():
144+
# Build field path
145+
if isinstance(field, tuple):
146+
field_path = "][".join(field)
147+
base_key = f"filterOr[{group_index}][{field_path}]"
148+
else:
149+
base_key = f"filterOr[{group_index}][{field}]"
150+
97151
if isinstance(filter_config, dict):
98152
for operator, value in filter_config.items():
99153
if operator in ["isNull", "isNotNull"]:
100-
self.params[f"filterOr[{group_index}][{field}][{operator}]"] = ""
154+
self.params[f"{base_key}[{operator}]"] = ""
101155
else:
102156
filter_value = (
103157
str(value)
104158
if not isinstance(value, list)
105159
else ",".join(map(str, value))
106160
)
107-
self.params[
108-
f"filterOr[{group_index}][{field}][{operator}]"
109-
] = filter_value
161+
self.params[f"{base_key}[{operator}]"] = filter_value
110162
else:
111-
self.params[f"filterOr[{group_index}][{field}]"] = str(filter_config)
163+
# For simple values, default to 'eq' operator
164+
self.params[f"{base_key}[eq]"] = str(filter_config)
112165
return self
113166

114167
def filter_and(
115-
self, filters: Dict[str, Any], group_index: int = 0
168+
self, filters: Dict[Union[str, tuple], Any], group_index: int = 0
116169
) -> "SWQueryBuilder":
117-
"""Add filterAnd parameters"""
170+
"""
171+
Add filterAnd parameters with support for nested field paths.
172+
173+
Args:
174+
filters: Dictionary where keys can be:
175+
- Simple field names (str): "name"
176+
- Nested field tuples: ("attributes", "476")
177+
Values can be:
178+
- Simple values for equality comparison
179+
- Dict with operator and value: {"hasText": "some_value"}
180+
group_index: The AND group index
181+
"""
118182
for field, filter_config in filters.items():
183+
# Build field path
184+
if isinstance(field, tuple):
185+
field_path = "][".join(field)
186+
base_key = f"filterAnd[{group_index}][{field_path}]"
187+
else:
188+
base_key = f"filterAnd[{group_index}][{field}]"
189+
119190
if isinstance(filter_config, dict):
120191
for operator, value in filter_config.items():
121192
if operator in ["isNull", "isNotNull"]:
122-
self.params[
123-
f"filterAnd[{group_index}][{field}][{operator}]"
124-
] = ""
193+
self.params[f"{base_key}[{operator}]"] = ""
125194
else:
126195
filter_value = (
127196
str(value)
128197
if not isinstance(value, list)
129198
else ",".join(map(str, value))
130199
)
131-
self.params[
132-
f"filterAnd[{group_index}][{field}][{operator}]"
133-
] = filter_value
200+
self.params[f"{base_key}[{operator}]"] = filter_value
134201
else:
135-
self.params[f"filterAnd[{group_index}][{field}]"] = str(filter_config)
202+
# For simple values, default to 'eq' operator
203+
self.params[f"{base_key}[eq]"] = str(filter_config)
136204
return self
137205

206+
def filter_nested(
207+
self,
208+
field_path: str,
209+
value: Any = None,
210+
operator: str = "eq"
211+
) -> "SWQueryBuilder":
212+
"""
213+
Convenience method for creating nested filters using dot notation.
214+
215+
Args:
216+
field_path: Dot-separated field path like "attributes.476"
217+
value: The value to filter by
218+
operator: The operator to use
219+
220+
Example:
221+
filter_nested("attributes.476", "some text", "hasText")
222+
-> filter[attributes][476][hasText]=some text
223+
"""
224+
field_list = field_path.split(".")
225+
return self.filter(field_list, value, operator)
226+
138227
def build(self) -> Dict[str, str]:
139228
"""Build and return the query parameters"""
140229
return self.params.copy()

0 commit comments

Comments
 (0)