python3-10.qs
12def list_to_dict(lst: list[Any]) -> dict[int, Any]: 13 """Maps a list to an equivalent dictionary 14 15 e.g: ["a","b","c"] -> {0:"a",1:"b",2:"c"} 16 17 Used to convert lists to PHP-style arrays 18 19 Args: 20 lst (list[Any]): The list to transform 21 22 Returns: 23 dict[int, Any]: The "PHP-style array" dict 24 """ 25 26 return {i: value for i, value in enumerate(lst)}
Maps a list to an equivalent dictionary
e.g: ["a","b","c"] -> {0:"a",1:"b",2:"c"}
Used to convert lists to PHP-style arrays
Args: lst (list[Any]): The list to transform
Returns: dict[int, Any]: The "PHP-style array" dict
29def is_valid_php_query_name(name: str) -> bool: 30 """ 31 Check if a given name follows PHP query string syntax. 32 This implementation assumes that names will be structured like: 33 field 34 field[key] 35 field[key1][key2] 36 field[] 37 """ 38 pattern = r""" 39 ^ # Asserts the start of the line, it means to start matching from the beginning of the string. 40 [^\[\]&]+ # Matches one or more characters that are not `[`, `]`, or `&`. It describes the base key. 41 ( # Opens a group. This group is used to match any subsequent keys within brackets. 42 \[ # Matches a literal `[`, which is the start of a key. 43 [^\[\]&]* # Matches zero or more characters that are not `[`, `]`, or `&`, which is the content of a key. 44 \] # Matches a literal `]`, which is the end of a key. 45 )* # Closes the group and asserts that the group can appear zero or more times, for nested keys. 46 $ # Asserts the end of the line, meaning the string should end with the preceding group. 47 """ 48 return bool(re.match(pattern, name, re.VERBOSE))
Check if a given name follows PHP query string syntax. This implementation assumes that names will be structured like: field field[key] field[key1][key2] field[]
125def merge_dict_in_list(source: dict, destination: list) -> list | dict: 126 """ 127 Merge a dictionary into a list. 128 129 Only the values of integer keys from the dictionary are merged into the list. 130 131 If the dictionary contains only integer keys, returns a merged list. 132 If the dictionary contains other keys as well, returns a merged dict. 133 134 Args: 135 source (dict): The dictionary to merge. 136 destination (list): The list to merge. 137 138 Returns: 139 list | dict: Merged data. 140 """ 141 # Retain only integer keys: 142 int_keys = sorted([key for key in source.keys() if isinstance(key, int)]) 143 array_values = [source[key] for key in int_keys] 144 merged_array = array_values + destination 145 146 if len(int_keys) == len(source.keys()): 147 return merged_array 148 149 return merge(source, list_to_dict(merged_array))
Merge a dictionary into a list.
Only the values of integer keys from the dictionary are merged into the list.
If the dictionary contains only integer keys, returns a merged list. If the dictionary contains other keys as well, returns a merged dict.
Args: source (dict): The dictionary to merge. destination (list): The list to merge.
Returns: list | dict: Merged data.
152def merge(source: dict | list, destination: dict | list, shallow: bool = True): 153 """ 154 Merge the `source` and `destination`. 155 Performs a shallow or deep merge based on the `shallow` flag. 156 Args: 157 source (Any): The source data to merge. 158 destination (Any): The destination data to merge into. 159 shallow (bool): If True, perform a shallow merge. Defaults to True. 160 Returns: 161 Any: Merged data. 162 """ 163 if not shallow: 164 source = deepcopy(source) 165 destination = deepcopy(destination) 166 167 match (source, destination): 168 case (list(), list()): 169 return source + destination 170 case (dict(), list()): 171 return merge_dict_in_list(source, destination) 172 173 items = cast(Mapping, source).items() 174 for key, value in items: 175 if isinstance(value, dict) and isinstance(destination, dict): 176 # get node or create one 177 node = destination.setdefault(key, {}) 178 node = merge(value, node) 179 destination[key] = node 180 else: 181 if ( 182 isinstance(value, list) or isinstance(value, tuple) 183 ) and key in destination: 184 value = merge(destination[key], list(value)) 185 186 if isinstance(key, str) and isinstance(destination, list): 187 destination = list_to_dict( 188 destination 189 ) # << WRITE TEST THAT WILL REACH THIS LINE 190 191 cast(dict, destination)[key] = value 192 return destination
Merge the source
and destination
.
Performs a shallow or deep merge based on the shallow
flag.
Args:
source (Any): The source data to merge.
destination (Any): The destination data to merge into.
shallow (bool): If True, perform a shallow merge. Defaults to True.
Returns:
Any: Merged data.
195def qs_parse( 196 qs: str, keep_blank_values: bool = True, strict_parsing: bool = False 197) -> dict: 198 """ 199 Parses a query string using PHP's nesting syntax, and returns a dict. 200 201 Args: 202 qs (str): The query string to parse. 203 keep_blank_values (bool): If True, includes keys with blank values. Defaults to True. 204 strict_parsing (bool): If True, raises ValueError on any errors. Defaults to False. 205 206 Returns: 207 dict: A dictionary representing the parsed query string. 208 """ 209 210 tokens = {} 211 pairs = [ 212 pair for query_segment in qs.split("&") for pair in query_segment.split(";") 213 ] 214 215 for name_val in pairs: 216 if not name_val and not strict_parsing: 217 continue 218 nv = name_val.split("=") 219 220 if len(nv) != 2: 221 if strict_parsing: 222 raise ValueError(f"Bad query field: {name_val}") 223 # Handle case of a control-name with no equal sign 224 if keep_blank_values: 225 nv.append("") 226 else: 227 continue 228 229 if len(nv[1]) or keep_blank_values: 230 _get_name_value(tokens, nv[0], nv[1], urlencoded=True) 231 232 return tokens
Parses a query string using PHP's nesting syntax, and returns a dict.
Args: qs (str): The query string to parse. keep_blank_values (bool): If True, includes keys with blank values. Defaults to True. strict_parsing (bool): If True, raises ValueError on any errors. Defaults to False.
Returns: dict: A dictionary representing the parsed query string.
235def build_qs(query: Mapping) -> str: 236 """ 237 Build a query string from a dictionary or list of 2-tuples. 238 Coerces data types before serialization. 239 Args: 240 query (Mapping): The query data to build the string from. 241 Returns: 242 str: A query string. 243 """ 244 245 def dict_generator(indict, pre=None): 246 pre = pre[:] if pre else [] 247 if isinstance(indict, dict): 248 for key, value in indict.items(): 249 if isinstance(value, dict): 250 for d in dict_generator(value, pre + [key]): 251 yield d 252 else: 253 yield pre + [key, value] 254 else: 255 yield indict 256 257 paths = [i for i in dict_generator(query)] 258 qs = [] 259 260 for path in paths: 261 names = path[:-1] 262 value = path[-1] 263 s: list[str] = [] 264 for i, n in enumerate(names): 265 n = f"[{n}]" if i > 0 else str(n) 266 s.append(n) 267 268 match value: 269 case list() | tuple(): 270 for v in value: 271 multi = s[:] 272 if not s[-1].endswith("[]"): 273 multi.append("[]") 274 multi.append("=") 275 # URLEncode value 276 multi.append(quote_plus(str(v))) 277 qs.append("".join(multi)) 278 case _: 279 s.append("=") 280 # URLEncode value 281 s.append(quote_plus(str(value))) 282 qs.append("".join(s)) 283 284 return "&".join(qs)
Build a query string from a dictionary or list of 2-tuples. Coerces data types before serialization. Args: query (Mapping): The query data to build the string from. Returns: str: A query string.
287def qs_parse_pairs( 288 pairs: Sequence[tuple[str, str] | tuple[str]], 289 keep_blank_values: bool = True, 290 strict_parsing: bool = False, 291) -> dict: 292 """ 293 Parses a list of key/value pairs and returns a dict. 294 295 Args: 296 pairs (list[tuple[str, str]]): The list of key/value pairs. 297 keep_blank_values (bool): If True, includes keys with blank values. Defaults to True. 298 strict_parsing (bool): If True, raises ValueError on any errors. Defaults to False. 299 300 Returns: 301 dict: A dictionary representing the parsed pairs. 302 """ 303 304 tokens = {} 305 306 for name_val in pairs: 307 if not name_val and not strict_parsing: 308 continue 309 nv = name_val 310 311 if len(nv) != 2: 312 if strict_parsing: 313 raise ValueError(f"Bad query field: {name_val}") 314 # Handle case of a control-name with no equal sign 315 if keep_blank_values: 316 nv = (nv[0], "") 317 else: 318 continue 319 320 if len(nv[1]) or keep_blank_values: 321 _get_name_value(tokens, nv[0], nv[1], False) 322 323 return tokens
Parses a list of key/value pairs and returns a dict.
Args: pairs (list[tuple[str, str]]): The list of key/value pairs. keep_blank_values (bool): If True, includes keys with blank values. Defaults to True. strict_parsing (bool): If True, raises ValueError on any errors. Defaults to False.
Returns: dict: A dictionary representing the parsed pairs.