|
14 | 14 | """ |
15 | 15 |
|
16 | 16 | import typing as t |
| 17 | +from collections.abc import Sequence as ABCSequence |
17 | 18 | from copy import deepcopy |
18 | 19 | from datetime import datetime |
19 | 20 | from functools import cmp_to_key |
@@ -72,12 +73,13 @@ def encode(value: t.Any, options: EncodeOptions = EncodeOptions()) -> str: |
72 | 73 |
|
73 | 74 | # If an iterable filter is provided for the root, restrict emission to those keys. |
74 | 75 | obj_keys: t.Optional[t.List[t.Any]] = None |
75 | | - if options.filter is not None: |
76 | | - if callable(options.filter): |
| 76 | + filter_opt = options.filter |
| 77 | + if filter_opt is not None: |
| 78 | + if callable(filter_opt): |
77 | 79 | # Callable filter may transform the root object. |
78 | | - obj = options.filter("", obj) |
79 | | - elif isinstance(options.filter, (list, tuple)): |
80 | | - obj_keys = list(options.filter) |
| 80 | + obj = filter_opt("", obj) |
| 81 | + elif isinstance(filter_opt, ABCSequence) and not isinstance(filter_opt, (str, bytes, bytearray)): |
| 82 | + obj_keys = list(filter_opt) |
81 | 83 |
|
82 | 84 | # Single-item list round-trip marker when using comma format. |
83 | 85 | comma_round_trip: bool = options.list_format == ListFormat.COMMA and options.comma_round_trip is True |
@@ -113,7 +115,7 @@ def encode(value: t.Any, options: EncodeOptions = EncodeOptions()) -> str: |
113 | 115 | encoder=options.encoder if options.encode else None, |
114 | 116 | serialize_date=options.serialize_date, |
115 | 117 | sort=options.sort, |
116 | | - filter=options.filter, |
| 118 | + filter_=options.filter, |
117 | 119 | formatter=options.format.formatter, |
118 | 120 | allow_empty_lists=options.allow_empty_lists, |
119 | 121 | strict_null_handling=options.strict_null_handling, |
@@ -167,7 +169,7 @@ def _encode( |
167 | 169 | encoder: t.Optional[t.Callable[[t.Any, t.Optional[Charset], t.Optional[Format]], str]], |
168 | 170 | serialize_date: t.Callable[[datetime], t.Optional[str]], |
169 | 171 | sort: t.Optional[t.Callable[[t.Any, t.Any], int]], |
170 | | - filter: t.Optional[t.Union[t.Callable, t.List[t.Union[str, int]]]], |
| 172 | + filter_: t.Optional[t.Union[t.Callable, t.Sequence[t.Union[str, int]]]], |
171 | 173 | formatter: t.Optional[t.Callable[[str], str]], |
172 | 174 | format: Format = Format.RFC3986, |
173 | 175 | generate_array_prefix: t.Callable[[str, t.Optional[str]], str] = ListFormat.INDICES.generator, |
@@ -199,7 +201,7 @@ def _encode( |
199 | 201 | encoder: Custom per-scalar encoder; if None, falls back to `str(value)` for primitives. |
200 | 202 | serialize_date: Optional `datetime` serializer hook. |
201 | 203 | sort: Optional comparator for object/array key ordering. |
202 | | - filter: Callable (transform value) or iterable of keys/indices (select). |
| 204 | + filter_: Callable (transform value) or iterable of keys/indices (select). |
203 | 205 | formatter: Percent-escape function chosen by `format` (RFC3986/1738). |
204 | 206 | format: Format enum (only used to choose a default `formatter` if none provided). |
205 | 207 | generate_array_prefix: Strategy used to build array key segments (indices/brackets/repeat/comma). |
@@ -251,9 +253,10 @@ def _encode( |
251 | 253 | step = 0 |
252 | 254 |
|
253 | 255 | # --- Pre-processing: filter & datetime handling --------------------------------------- |
254 | | - if callable(filter): |
| 256 | + filter_opt = filter_ |
| 257 | + if callable(filter_opt): |
255 | 258 | # Callable filter can transform the object for this prefix. |
256 | | - obj = filter(prefix, obj) |
| 259 | + obj = filter_opt(prefix, obj) |
257 | 260 | else: |
258 | 261 | # Normalize datetimes both for scalars and (in COMMA mode) list elements. |
259 | 262 | if isinstance(obj, datetime): |
@@ -315,9 +318,13 @@ def _encode( |
315 | 318 | obj_keys = [{"value": obj_keys_value if obj_keys_value else None}] |
316 | 319 | else: |
317 | 320 | obj_keys = [{"value": UNDEFINED}] |
318 | | - elif isinstance(filter, (list, tuple)): |
| 321 | + elif ( |
| 322 | + filter_opt is not None |
| 323 | + and isinstance(filter_opt, ABCSequence) |
| 324 | + and not isinstance(filter_opt, (str, bytes, bytearray)) |
| 325 | + ): |
319 | 326 | # Iterable filter restricts traversal to a fixed key/index set. |
320 | | - obj_keys = list(filter) |
| 327 | + obj_keys = list(filter_opt) |
321 | 328 | else: |
322 | 329 | # Default: enumerate keys/indices from mappings or sequences. |
323 | 330 | if isinstance(obj, t.Mapping): |
@@ -358,8 +365,12 @@ def _encode( |
358 | 365 | _value = obj.get(_key) |
359 | 366 | _value_undefined = _key not in obj |
360 | 367 | elif isinstance(obj, (list, tuple)): |
361 | | - _value = obj[_key] |
362 | | - _value_undefined = False |
| 368 | + if isinstance(_key, int): |
| 369 | + _value = obj[_key] |
| 370 | + _value_undefined = False |
| 371 | + else: |
| 372 | + _value = None |
| 373 | + _value_undefined = True |
363 | 374 | else: |
364 | 375 | _value = obj[_key] |
365 | 376 | _value_undefined = False |
@@ -403,7 +414,7 @@ def _encode( |
403 | 414 | ), |
404 | 415 | serialize_date=serialize_date, |
405 | 416 | sort=sort, |
406 | | - filter=filter, |
| 417 | + filter_=filter_, |
407 | 418 | formatter=formatter, |
408 | 419 | format=format, |
409 | 420 | generate_array_prefix=generate_array_prefix, |
|
0 commit comments