|
1 | | -from typing import Any, Dict, List, Optional |
| 1 | +from typing import Any, Dict, List, Optional, Union |
2 | 2 |
|
3 | 3 |
|
4 | 4 | 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 | + """ |
6 | 22 |
|
7 | 23 | def __init__(self): |
8 | 24 | self.params = {} |
@@ -74,67 +90,140 @@ def page_number(self, number: int = 1) -> "SWQueryBuilder": |
74 | 90 | return self |
75 | 91 |
|
76 | 92 | 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" |
78 | 97 | ) -> "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 |
80 | 118 | 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}]"] = "" |
84 | 120 | else: |
85 | | - self.params[f"filter[{field}][{operator}]"] = ( |
| 121 | + self.params[f"{filter_key}[{operator}]"] = ( |
86 | 122 | str(value) |
87 | 123 | if not isinstance(value, list) |
88 | 124 | else ",".join(map(str, value)) |
89 | 125 | ) |
90 | 126 | return self |
91 | 127 |
|
92 | 128 | 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 |
94 | 130 | ) -> "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 | + """ |
96 | 143 | 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 | + |
97 | 151 | if isinstance(filter_config, dict): |
98 | 152 | for operator, value in filter_config.items(): |
99 | 153 | if operator in ["isNull", "isNotNull"]: |
100 | | - self.params[f"filterOr[{group_index}][{field}][{operator}]"] = "" |
| 154 | + self.params[f"{base_key}[{operator}]"] = "" |
101 | 155 | else: |
102 | 156 | filter_value = ( |
103 | 157 | str(value) |
104 | 158 | if not isinstance(value, list) |
105 | 159 | else ",".join(map(str, value)) |
106 | 160 | ) |
107 | | - self.params[ |
108 | | - f"filterOr[{group_index}][{field}][{operator}]" |
109 | | - ] = filter_value |
| 161 | + self.params[f"{base_key}[{operator}]"] = filter_value |
110 | 162 | 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) |
112 | 165 | return self |
113 | 166 |
|
114 | 167 | 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 |
116 | 169 | ) -> "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 | + """ |
118 | 182 | 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 | + |
119 | 190 | if isinstance(filter_config, dict): |
120 | 191 | for operator, value in filter_config.items(): |
121 | 192 | if operator in ["isNull", "isNotNull"]: |
122 | | - self.params[ |
123 | | - f"filterAnd[{group_index}][{field}][{operator}]" |
124 | | - ] = "" |
| 193 | + self.params[f"{base_key}[{operator}]"] = "" |
125 | 194 | else: |
126 | 195 | filter_value = ( |
127 | 196 | str(value) |
128 | 197 | if not isinstance(value, list) |
129 | 198 | else ",".join(map(str, value)) |
130 | 199 | ) |
131 | | - self.params[ |
132 | | - f"filterAnd[{group_index}][{field}][{operator}]" |
133 | | - ] = filter_value |
| 200 | + self.params[f"{base_key}[{operator}]"] = filter_value |
134 | 201 | 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) |
136 | 204 | return self |
137 | 205 |
|
| 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 | + |
138 | 227 | def build(self) -> Dict[str, str]: |
139 | 228 | """Build and return the query parameters""" |
140 | 229 | return self.params.copy() |
0 commit comments