qs_codec.models package

Submodules

qs_codec.models.decode_options module

This module contains the DecodeOptions class that configures the output of decode.

Keys are decoded identically to values by the default decoder; whether a decoded . splits segments is controlled by parsing options (allow_dots / decode_dot_in_keys) elsewhere.

class qs_codec.models.decode_options.DecodeOptions(allow_dots: bool | None = None, decode_dot_in_keys: bool | None = None, allow_empty_lists: bool = False, list_limit: int = 20, charset: ~qs_codec.enums.charset.Charset = Charset.UTF8, charset_sentinel: bool = False, comma: bool = False, delimiter: str | ~typing.Pattern[str] = '&', depth: int = 5, parameter_limit: int | float = 1000, duplicates: ~qs_codec.enums.duplicates.Duplicates = Duplicates.COMBINE, ignore_query_prefix: bool = False, interpret_numeric_entities: bool = False, parse_lists: bool = True, strict_depth: bool = False, strict_null_handling: bool = False, raise_on_limit_exceeded: bool = False, decoder: ~typing.Callable[[...], str | None] | None = <bound method DecodeUtils.decode of <class 'qs_codec.utils.decode_utils.DecodeUtils'>>, legacy_decoder: ~typing.Callable[[...], str | None] | None = None)[source]

Bases: object

Options that configure the output of decode.

allow_dots: bool | None = None

Set to True to decode dot dict notation in the encoded input. When None (default), it inherits the value of decode_dot_in_keys.

allow_empty_lists: bool = False

Set to True to allow empty list values inside dicts in the encoded input.

charset: Charset = _CharsetDataMixin(encoding='utf-8')

The character encoding to use when decoding the input.

charset_sentinel: bool = False

Some services add an initial utf8=✓ value to forms so that old InternetExplorer versions are more likely to submit the form as utf-8. Additionally, the server can check the value against wrong encodings of the checkmark character and detect that a query string or application/x-www-form-urlencoded body was not sent as utf-8, e.g. if the form had an accept-charset parameter or the containing page had a different character set.

qs_codec supports this mechanism via the charset_sentinel option. If specified, the utf-8 parameter will be omitted from the returned dict. It will be used to switch to LATIN1 or UTF8 mode depending on how the checkmark is encoded.

Important: When you specify both the charset option and the charset_sentinel option, the charset will be overridden when the request contains a utf-8 parameter from which the actual charset can be deduced. In that sense the charset will behave as the default charset rather than the authoritative charset.

comma: bool = False

Set to True to parse the input as a comma-separated value. Note: nested dict s, such as 'a={b:1},{c:d}' are not supported.

decode(value: str | None, charset: Charset | None = None, *, kind: DecodeKind = DecodeKind.VALUE) Any | None[source]

Unified scalar decode with key/value context.

Uses the configured decoder (or legacy_decoder) when provided; otherwise falls back to DecodeUtils.decode(). The default library behavior decodes keys identically to values; whether a . participates in key splitting is decided later by the parser.

decode_dot_in_keys: bool | None = None

Set to True to decode percent‑encoded dots in keys (e.g., %2E.). Note: it implies allow_dots, so decode will error if you set decode_dot_in_keys to True, and allow_dots to False. When None (default), it defaults to False.

Inside bracket segments, percent-decoding naturally yields . from %2E/%2e. This option controls whether top‑level encoded dots are treated as additional split points; it does not affect the literal . produced by percent-decoding inside bracket segments.

decode_key(value: str | None, charset: Charset | None = None) str | None[source]

Decode a key (or key segment). Always returns a string or None.

Note: custom decoders returning non-strings for keys are coerced via str().

decode_value(value: str | None, charset: Charset | None = None) Any | None[source]

Decode a value token. Returns any scalar or None.

classmethod decoder(string: str | None, charset: Charset | None = Charset.UTF8, kind: DecodeKind = DecodeKind.VALUE) str | None

Custom scalar decoder invoked for each raw token prior to interpretation.

The built-in decoder supports kind and is invoked as decoder(string, charset, kind=DecodeKind.KEY|VALUE). Custom decoders that omit kind (or charset) are automatically adapted for compatibility. Returning None from the decoder uses None as the scalar value.

delimiter: str | Pattern[str] = '&'

The delimiter to use when splitting key-value pairs in the encoded input. Can be a str or a Pattern.

depth: int = 5

By default, when nesting dicts qs_codec will only decode up to 5 children deep. This depth can be overridden by setting the depth. The depth limit helps mitigate abuse when qs_codec is used to parse user input, and it is recommended to keep it a reasonably small number.

duplicates: Duplicates = 1

Strategy for handling duplicate keys in the input.

  • COMBINE (default): merge values into a list (e.g., a=1&a=2{"a": [1, 2]}).

  • FIRST: keep the first value and ignore subsequent ones ({"a": 1}).

  • LAST: keep only the last value seen ({"a": 2}).

ignore_query_prefix: bool = False

Set to True to ignore the leading question mark query prefix in the encoded input.

interpret_numeric_entities: bool = False

Set to True to interpret HTML numeric entities (&#...;) in the encoded input.

legacy_decoder: Callable[[...], str | None] | None = None

Back‑compat adapter for legacy decoders of the form decoder(value, charset). Prefer decoder which may optionally accept a kind argument. When both are supplied, decoder takes precedence (mirroring Kotlin/C#/Swift/Dart behavior).

list_limit: int = 20

20).

During decoding, keys like a[0], a[1], … are treated as list indices. If an index exceeds this limit, the container is treated as a dict instead, with the numeric index kept as a string key (e.g., {"999": "x"}) to prevent creation of massive sparse lists (e.g., a[999999999]).

This limit also applies to comma–split lists when comma=True. Set a larger value if you explicitly need more items, or set a smaller one to harden against abuse.

Type:

Maximum number of indexed items allowed in a single list (default

parameter_limit: int | float = 1000

For similar reasons, by default qs_codec will only parse up to 1000 parameters. This can be overridden by passing a parameter_limit option.

parse_lists: bool = True

To disable list parsing entirely, set parse_lists to False.

raise_on_limit_exceeded: bool = False

Raise instead of degrading gracefully when limits are exceeded.

When True, the decoder raises: - a DecodeError for parameter and list limit violations; and - an IndexError when nesting deeper than depth and strict_depth=True.

When False (default), the decoder degrades gracefully: it slices the parameter list at parameter_limit, stops adding items beyond list_limit, and—if strict_depth=True—stops descending once depth is reached without raising.

strict_depth: bool = False

Enforce the depth limit when decoding nested structures.

When True, the decoder will not descend beyond depth levels. Combined with raise_on_limit_exceeded:

  • if raise_on_limit_exceeded=True, exceeding the depth raises an IndexError;

  • if False, the decoder stops descending and treats deeper content as a terminal value, preserving the last valid container without raising.

strict_null_handling: bool = False

Set to True to decode values without = to None.

qs_codec.models.encode_options module

Configuration object for encode.

EncodeOptions mirrors the behavior and defaults of the reference qs implementation. It controls how Python values (dicts/lists/scalars) are turned into a URL-encoded query string. The options here are intentionally close to the Node.js library so behavior is predictable across languages.

Key interactions to be aware of: - allow_dots vs encode_dot_in_keys: when unspecified, allow_dots mirrors the value of encode_dot_in_keys (see __post_init__). - indices is deprecated and mapped to list_format for parity with newer ports. - encoder and serialize_date let you customize scalar/date serialization, while encode=False short-circuits the encoder entirely. - sort may return -1/0/+1 (like strcmp/NSComparisonResult.rawValue) to deterministically order keys.

class qs_codec.models.encode_options.EncodeOptions(allow_dots: bool = None, add_query_prefix: bool = False, allow_empty_lists: bool = False, indices: bool | None = None, list_format: ~qs_codec.enums.list_format.ListFormat = ListFormat.INDICES, charset: ~qs_codec.enums.charset.Charset = Charset.UTF8, charset_sentinel: bool = False, delimiter: str = '&', encode: bool = True, encode_dot_in_keys: bool = None, encode_values_only: bool = False, format: ~qs_codec.enums.format.Format = Format.RFC3986, filter: ~typing.Callable | ~typing.List[str | int] | None = None, skip_nulls: bool = False, serialize_date: ~typing.Callable[[~datetime.datetime], str | None] = <function EncodeUtils.serialize_date>, encoder: ~typing.Callable[[~typing.Any, ~qs_codec.enums.charset.Charset | None, ~qs_codec.enums.format.Format | None], str] = <property object>, strict_null_handling: bool = False, comma_round_trip: bool | None = None, sort: ~typing.Callable[[~typing.Any, ~typing.Any], int] | None = None)[source]

Bases: object

Options that configure the output of encode.

Each field corresponds to a knob in the query-string encoder. Defaults aim to be unsurprising and compatible with other Techouse qs ports. See per-field docs below for details and caveats.

add_query_prefix: bool = False

When True, prefix the output with a ? (useful when appending to a base URL).

allow_dots: bool = None

When True, interpret dotted keys as object paths during encoding (e.g. a.b=1). If None, mirrors encode_dot_in_keys (see __post_init__).

allow_empty_lists: bool = False

When True, include empty lists in the output (e.g. a[]= instead of omitting).

charset: Charset = _CharsetDataMixin(encoding='utf-8')

Character encoding used by the encoder (defaults to UTF‑8).

charset_sentinel: bool = False

When True, include a sentinel parameter announcing the charset (e.g. utf8=✓).

comma_round_trip: bool | None = None

Only used with ListFormat.COMMA. When True, single‑item lists append [] so they round‑trip back to a list on decode.

delimiter: str = '&'

Pair delimiter between tokens (typically &; ; and others are allowed).

encode: bool = True

Master switch. When False, values/keys are not percent‑encoded (joined as-is).

encode_dot_in_keys: bool = None

When True, encode dots in keys literally. With encode_values_only=True, only key dots are encoded while values remain untouched.

encode_values_only: bool = False

When True, the encoder is applied to values only; keys are left unencoded.

property encoder: Callable[[Any, Charset | None, Format | None], str]

(value, charset|None, format|None) -> str. Note: when encode=False, this is bypassed and values are joined without percent‑encoding.

Type:

Custom scalar encoder. Signature

filter: Callable | List[str | int] | None = None

Restrict which keys get included. - If a callable is provided, it is invoked for each key and should return the replacement value (or None to drop when skip_nulls applies). - If a list is provided, only those keys/indices are retained.

format: Format = _FormatDataMixin(format_name='RFC3986', formatter=<function Formatter.rfc3986>)

Space handling and percent‑encoding style. RFC3986 encodes spaces as %20, while RFC1738 uses +.

indices: bool | None = None

prefer list_format. If set, maps to ListFormat.INDICES when True or ListFormat.REPEAT when False.

Type:

Deprecated

list_format: ListFormat = _ListFormatDataMixin(list_format_name='INDICES', generator=<function ListFormatGenerator.indices>)

Controls how lists are encoded (indices/brackets/repeat/comma). See ListFormat.

serialize_date() str

Hook to stringify datetime values before encoding; returning None is treated as a null value (subject to null-handling options), not as a fallback to ISO-8601.

skip_nulls: bool = False

When True, omit keys whose value is None entirely (no trailing =).

sort: Callable[[Any, Any], int] | None = None

Optional comparator for deterministic key ordering. Must return -1, 0, or +1.

strict_null_handling: bool = False

Nonea (no =), empty string → a=.

Type:

When True, distinguish empty strings from None

qs_codec.models.undefined module

Undefined sentinel.

This module defines a tiny singleton Undefined used as a sentinel to mean “no value provided / omit this key”, similar to JavaScript’s undefined.

Unlike None (which commonly means an explicit null), Undefined is used by the encoder and helper utilities to skip emitting a key or to signal that a value is intentionally absent and should not be serialized.

The sentinel is identity-based: every construction returns the same instance, so is comparisons are reliable (e.g., value is Undefined()).

class qs_codec.models.undefined.Undefined[source]

Bases: object

Singleton sentinel object representing an “undefined” value.

Notes

  • This is not the same as None. Use None to represent a null value and Undefined() to represent “no value / omit”.

  • All calls to Undefined() return the same instance. Prefer identity checks (is) over equality checks.

Examples

>>> from qs_codec.models.undefined import Undefined
>>> a = Undefined()
>>> b = Undefined()
>>> a is b
True
>>> # Use it to indicate a key should be omitted when encoding:
>>> maybe_value = Undefined()
>>> if maybe_value is Undefined():
...     pass  # skip emitting the key

qs_codec.models.weak_wrapper module

Weakly wrap any object with identity equality and deep content hashing.

class qs_codec.models.weak_wrapper.WeakWrapper(value: Any)[source]

Bases: object

Wrapper suitable for use as a WeakKeyDictionary key.

  • Holds a strong reference to the proxy (keeps proxy alive while wrapper exists).

  • Exposes a weakref to the proxy via _wref so tests can observe/force GC.

  • Equality is proxy identity; hash is a deep hash of the underlying value.

__init__(value: Any) None[source]

Initialize the wrapper with a value.

property value: Any

Guard with the weakref so tests can simulate GC by swapping _wref.

Module contents