qs_codec package

Subpackages

Submodules

qs_codec.decode module

Query string decoder (a.k.a. parser) with feature parity to the Node.js qs package.

Highlights

  • Accepts either a raw query string or a pre-tokenized mapping (mirrors qs.parse).

  • Supports RFC 3986 / 1738 percent-decoding via DecodeOptions.decoder.

  • Handles bracket notation, indices, dotted keys (opt-in), and duplicate keys strategies.

  • Respects list parsing limits, depth limits, and charset sentinels (utf8=%E2%9C%93 / utf8=%26%2310003%3B).

  • Returns plain dict / list containers and never mutates the caller’s input.

This module intentionally keeps the control flow close to the original reference implementation so that behavior across ports stays predictable and easy to verify with shared test vectors.

qs_codec.decode.decode(value: str | Mapping[str, Any] | None, options: DecodeOptions | None = None) Dict[str, Any][source]

Decode a query string (or a pre-tokenized mapping) into a nested Dict[str, Any].

Parameters:
  • value – Either a raw query string (str) or an already-parsed mapping (Mapping[str, Any]). Passing a mapping is useful in tests or when a custom tokenizer is used upstream.

  • optionsDecodeOptions controlling delimiter, duplicates policy, list & depth limits, dot-notation, decoding charset, and more.

Returns:

A freshly-allocated mapping containing nested dicts/lists/values.

Return type:

Dict[str, Any]

Raises:

ValueError – If value is neither str nor Mapping, or when limits are violated under raise_on_limit_exceeded=True.

Notes

  • Empty/falsey value returns an empty dict.

  • When the number of top-level tokens exceeds list_limit and parse_lists is enabled, the parser temporarily disables list parsing for this invocation to avoid quadratic work. This mirrors the behavior of other ports and keeps large flat query strings efficient.

qs_codec.decode.load(value: str | Mapping[str, Any] | None, options: DecodeOptions | None = None) Dict[str, Any]

Decode a query string (or a pre-tokenized mapping) into a nested Dict[str, Any].

Parameters:
  • value – Either a raw query string (str) or an already-parsed mapping (Mapping[str, Any]). Passing a mapping is useful in tests or when a custom tokenizer is used upstream.

  • optionsDecodeOptions controlling delimiter, duplicates policy, list & depth limits, dot-notation, decoding charset, and more.

Returns:

A freshly-allocated mapping containing nested dicts/lists/values.

Return type:

Dict[str, Any]

Raises:

ValueError – If value is neither str nor Mapping, or when limits are violated under raise_on_limit_exceeded=True.

Notes

  • Empty/falsey value returns an empty dict.

  • When the number of top-level tokens exceeds list_limit and parse_lists is enabled, the parser temporarily disables list parsing for this invocation to avoid quadratic work. This mirrors the behavior of other ports and keeps large flat query strings efficient.

qs_codec.decode.loads(value: str | None, options: DecodeOptions | None = None) Dict[str, Any][source]

Alias for decode. Decodes a query string into a Dict[str, Any].

Use decode if you want to pass a Dict[str, Any].

qs_codec.encode module

Query‑string encoder (stringifier).

This module converts Python mappings and sequences into a percent‑encoded query string with feature parity to the Node.js qs package where it makes sense for Python. It supports:

  • Stable, deterministic key ordering with an optional custom comparator.

  • Multiple list encodings (indices, brackets, repeat key, comma) including the “comma round‑trip” behavior to preserve single‑element lists.

  • Custom per‑scalar encoder and datetime serializer hooks.

  • RFC 3986 vs RFC 1738 formatting and optional charset sentinels.

  • Dots vs brackets in key paths (allow_dots, encode_dot_in_keys).

  • Strict/null handling, empty‑list emission, and cycle detection.

Nothing in this module mutates caller objects: inputs are shallow‑normalized and deep‑copied only where safe/necessary to honor options.

qs_codec.encode.dumps(value: ~typing.Any, options: ~qs_codec.models.encode_options.EncodeOptions = EncodeOptions(allow_dots=False, add_query_prefix=False, allow_empty_lists=False, indices=None, list_format=<ListFormat.INDICES: list_format_name='INDICES', generator=<function ListFormatGenerator.indices>>, charset=<Charset.UTF8: encoding='utf-8'>, charset_sentinel=False, delimiter='&', encode=True, encode_dot_in_keys=False, encode_values_only=False, format=<Format.RFC3986: format_name='RFC3986', formatter=<function Formatter.rfc3986>>, filter=None, skip_nulls=False, serialize_date=<function EncodeUtils.serialize_date>, encoder=<function EncodeOptions.encoder.<locals>.<lambda>>, strict_null_handling=False, comma_round_trip=None, sort=None)) str

Stringify a Python value into a query string.

Parameters:
  • value – The object to encode. Accepted shapes: * Mapping -> encoded as-is. * Sequence (list/tuple) -> treated as an object with string indices. * Other/None -> treated as empty input.

  • options – Encoding behavior (parity with the Node.js qs API).

Returns:

The encoded query string (possibly prefixed with “?” if requested), or an empty string when there is nothing to encode.

Notes

  • Caller input is not mutated. When a mapping is provided it is deep-copied; sequences are projected to a temporary mapping.

  • If a callable filter is provided, it can transform the root object.

  • If an iterable filter is provided, it selects which root keys to emit.

qs_codec.encode.encode(value: ~typing.Any, options: ~qs_codec.models.encode_options.EncodeOptions = EncodeOptions(allow_dots=False, add_query_prefix=False, allow_empty_lists=False, indices=None, list_format=<ListFormat.INDICES: list_format_name='INDICES', generator=<function ListFormatGenerator.indices>>, charset=<Charset.UTF8: encoding='utf-8'>, charset_sentinel=False, delimiter='&', encode=True, encode_dot_in_keys=False, encode_values_only=False, format=<Format.RFC3986: format_name='RFC3986', formatter=<function Formatter.rfc3986>>, filter=None, skip_nulls=False, serialize_date=<function EncodeUtils.serialize_date>, encoder=<function EncodeOptions.encoder.<locals>.<lambda>>, strict_null_handling=False, comma_round_trip=None, sort=None)) str[source]

Stringify a Python value into a query string.

Parameters:
  • value – The object to encode. Accepted shapes: * Mapping -> encoded as-is. * Sequence (list/tuple) -> treated as an object with string indices. * Other/None -> treated as empty input.

  • options – Encoding behavior (parity with the Node.js qs API).

Returns:

The encoded query string (possibly prefixed with “?” if requested), or an empty string when there is nothing to encode.

Notes

  • Caller input is not mutated. When a mapping is provided it is deep-copied; sequences are projected to a temporary mapping.

  • If a callable filter is provided, it can transform the root object.

  • If an iterable filter is provided, it selects which root keys to emit.

Module contents

Query string encoding/decoding for Python.

This package is a Python port of the popular qs library for JavaScript/Node.js. It strives to match qs semantics and edge cases — including list/array formats, duplicate key handling, RFC 3986 vs RFC 1738 formatting, character set sentinels, and other interoperability details.

The package root re-exports the most commonly used functions and enums so you can:

>>> from qs_codec import encode, decode, ListFormat, EncodeOptions
>>> encode({"a": [1, 2, 3]}, options=EncodeOptions(list_format=ListFormat.brackets))
'a[]=1&a[]=2&a[]=3'
class qs_codec.Charset(*values)[source]

Bases: _CharsetDataMixin, Enum

Supported character sets for query-string processing.

Each enum member’s value is the codec string understood by Python’s encoding APIs. Prefer accessing encoding instead of hard-coding literals.

LATIN1 = _CharsetDataMixin(encoding='iso-8859-1')

ISO-8859-1 (Latin-1) character encoding.

UTF8 = _CharsetDataMixin(encoding='utf-8')

UTF-8 character encoding.

class qs_codec.DecodeKind(*values)[source]

Bases: str, Enum

Decoding context for query string tokens.

KEY

Decode a key (or key segment). Note that the default scalar decoder (qs_codec.utils.decode_utils.decode) ignores kind and fully decodes percent-encoded dots (%2E/%2e). Dot-splitting behavior is applied later by higher-level parser options.

VALUE

Decode a value. Implementations typically perform full percent decoding.

KEY = 'key'
VALUE = 'value'
class qs_codec.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

Decode a URL‑encoded scalar.

Notes

The kind parameter is accepted for API compatibility but is currently ignored; keys and values are decoded identically. It may be removed in a future major release.

Behavior: - Replace + with a literal space before decoding. - If charset is LATIN1, decode only %XX byte sequences (no %uXXXX). %uXXXX sequences are left as‑is to mimic older browser/JS behavior. - Otherwise (UTF‑8), defer to urllib.parse.unquote(). - Keys and values are decoded identically; whether a literal . acts as a key separator is decided later by the key‑splitting logic.

Returns:

None when the input is None.

Return type:

Optional[str]

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.

class qs_codec.Duplicates(*values)[source]

Bases: Enum

Defines how to resolve duplicate keys produced during parsing.

This is consulted by the decoder when more than one value is encountered for the same key. It does not affect encoding.

Members

COMBINE

Combine duplicate keys into a single list of values (preserves order).

FIRST

Keep only the first occurrence and discard subsequent ones.

LAST

Keep only the last occurrence, overwriting prior ones.

COMBINE = 1

Combine duplicate keys into a single list of values (preserves order).

FIRST = 2

Keep only the first value encountered for the key.

LAST = 3

Keep only the last value encountered for the key.

class qs_codec.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]

Return a view of the encoder bound to the current charset and format.

The returned callable has signature (value) -> str and internally calls the underlying _encoder(value, self.charset, self.format).

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

Serialize a datetime to ISO‑8601 using datetime.isoformat().

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

class qs_codec.Format(*values)[source]

Bases: _FormatDataMixin, Enum

Supported URI component formatting profiles.

Each enum value packs a (format_name, formatter) tuple. After raw percent-encoding is performed by the encoder, the selected profile’s formatter is called to adjust the final textual representation (e.g., mapping "%20" to "+" for RFC 1738).

RFC1738 = _FormatDataMixin(format_name='RFC1738', formatter=<function Formatter.rfc1738>)

RFC 1738.

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

RFC 3986.

class qs_codec.ListFormat(*values)[source]

Bases: _ListFormatDataMixin, Enum

Available list formatting options for the encoder.

Each member pairs a stable name with a generator function from ListFormatGenerator:

  • BRACKETS: foo[] for each element.

  • INDICES: foo[0], foo[1], …

  • REPEAT: repeat the key per value (foo=1&foo=2).

  • COMMA: single key with comma‑joined values (foo=1,2).

These options control only how keys are produced; value encoding and delimiter handling are governed by other options.

BRACKETS = _ListFormatDataMixin(list_format_name='BRACKETS', generator=<function ListFormatGenerator.brackets>)

Use brackets to represent list items, for example foo[]=123&foo[]=456&foo[]=789.

COMMA = _ListFormatDataMixin(list_format_name='COMMA', generator=<function ListFormatGenerator.comma>)

Use commas to represent list items, for example foo=123,456,789.

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

Use indices to represent list items, for example foo[0]=123&foo[1]=456&foo[2]=789.

REPEAT = _ListFormatDataMixin(list_format_name='REPEAT', generator=<function ListFormatGenerator.repeat>)

Use a repeat key to represent list items, for example foo=123&foo=456&foo=789.

class qs_codec.Sentinel(*values)[source]

Bases: _SentinelDataMixin, Enum

All supported utf8 sentinels.

Each enum member provides:
  • raw: the source token a browser starts with, and

  • encoded: the final, percent-encoded utf8=… fragment.

CHARSET = _SentinelDataMixin(raw='✓', encoded='utf8=%E2%9C%93')

UTF‑8 sentinel indicating the request is UTF‑8 encoded.

This is the percent‑encoded UTF‑8 sequence for ✓, yielding the fragment utf8=%E2%9C%93.

ISO = _SentinelDataMixin(raw='&#10003;', encoded='utf8=%26%2310003%3B')

HTML‑entity sentinel used by non‑UTF‑8 submissions.

When a check mark (✓) appears but the page/form encoding is iso-8859-1 (or another charset that lacks ✓), browsers first HTML‑entity‑escape it as "&#10003;" and then URL‑encode it, producing utf8=%26%2310003%3B.

class qs_codec.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.decode(value: str | Mapping[str, Any] | None, options: DecodeOptions | None = None) Dict[str, Any][source]

Decode a query string (or a pre-tokenized mapping) into a nested Dict[str, Any].

Parameters:
  • value – Either a raw query string (str) or an already-parsed mapping (Mapping[str, Any]). Passing a mapping is useful in tests or when a custom tokenizer is used upstream.

  • optionsDecodeOptions controlling delimiter, duplicates policy, list & depth limits, dot-notation, decoding charset, and more.

Returns:

A freshly-allocated mapping containing nested dicts/lists/values.

Return type:

Dict[str, Any]

Raises:

ValueError – If value is neither str nor Mapping, or when limits are violated under raise_on_limit_exceeded=True.

Notes

  • Empty/falsey value returns an empty dict.

  • When the number of top-level tokens exceeds list_limit and parse_lists is enabled, the parser temporarily disables list parsing for this invocation to avoid quadratic work. This mirrors the behavior of other ports and keeps large flat query strings efficient.

qs_codec.dumps(value: ~typing.Any, options: ~qs_codec.models.encode_options.EncodeOptions = EncodeOptions(allow_dots=False, add_query_prefix=False, allow_empty_lists=False, indices=None, list_format=<ListFormat.INDICES: list_format_name='INDICES', generator=<function ListFormatGenerator.indices>>, charset=<Charset.UTF8: encoding='utf-8'>, charset_sentinel=False, delimiter='&', encode=True, encode_dot_in_keys=False, encode_values_only=False, format=<Format.RFC3986: format_name='RFC3986', formatter=<function Formatter.rfc3986>>, filter=None, skip_nulls=False, serialize_date=<function EncodeUtils.serialize_date>, encoder=<function EncodeOptions.encoder.<locals>.<lambda>>, strict_null_handling=False, comma_round_trip=None, sort=None)) str

Stringify a Python value into a query string.

Parameters:
  • value – The object to encode. Accepted shapes: * Mapping -> encoded as-is. * Sequence (list/tuple) -> treated as an object with string indices. * Other/None -> treated as empty input.

  • options – Encoding behavior (parity with the Node.js qs API).

Returns:

The encoded query string (possibly prefixed with “?” if requested), or an empty string when there is nothing to encode.

Notes

  • Caller input is not mutated. When a mapping is provided it is deep-copied; sequences are projected to a temporary mapping.

  • If a callable filter is provided, it can transform the root object.

  • If an iterable filter is provided, it selects which root keys to emit.

qs_codec.encode(value: ~typing.Any, options: ~qs_codec.models.encode_options.EncodeOptions = EncodeOptions(allow_dots=False, add_query_prefix=False, allow_empty_lists=False, indices=None, list_format=<ListFormat.INDICES: list_format_name='INDICES', generator=<function ListFormatGenerator.indices>>, charset=<Charset.UTF8: encoding='utf-8'>, charset_sentinel=False, delimiter='&', encode=True, encode_dot_in_keys=False, encode_values_only=False, format=<Format.RFC3986: format_name='RFC3986', formatter=<function Formatter.rfc3986>>, filter=None, skip_nulls=False, serialize_date=<function EncodeUtils.serialize_date>, encoder=<function EncodeOptions.encoder.<locals>.<lambda>>, strict_null_handling=False, comma_round_trip=None, sort=None)) str[source]

Stringify a Python value into a query string.

Parameters:
  • value – The object to encode. Accepted shapes: * Mapping -> encoded as-is. * Sequence (list/tuple) -> treated as an object with string indices. * Other/None -> treated as empty input.

  • options – Encoding behavior (parity with the Node.js qs API).

Returns:

The encoded query string (possibly prefixed with “?” if requested), or an empty string when there is nothing to encode.

Notes

  • Caller input is not mutated. When a mapping is provided it is deep-copied; sequences are projected to a temporary mapping.

  • If a callable filter is provided, it can transform the root object.

  • If an iterable filter is provided, it selects which root keys to emit.

qs_codec.load(value: str | Mapping[str, Any] | None, options: DecodeOptions | None = None) Dict[str, Any]

Decode a query string (or a pre-tokenized mapping) into a nested Dict[str, Any].

Parameters:
  • value – Either a raw query string (str) or an already-parsed mapping (Mapping[str, Any]). Passing a mapping is useful in tests or when a custom tokenizer is used upstream.

  • optionsDecodeOptions controlling delimiter, duplicates policy, list & depth limits, dot-notation, decoding charset, and more.

Returns:

A freshly-allocated mapping containing nested dicts/lists/values.

Return type:

Dict[str, Any]

Raises:

ValueError – If value is neither str nor Mapping, or when limits are violated under raise_on_limit_exceeded=True.

Notes

  • Empty/falsey value returns an empty dict.

  • When the number of top-level tokens exceeds list_limit and parse_lists is enabled, the parser temporarily disables list parsing for this invocation to avoid quadratic work. This mirrors the behavior of other ports and keeps large flat query strings efficient.

qs_codec.loads(value: str | None, options: DecodeOptions | None = None) Dict[str, Any][source]

Alias for decode. Decodes a query string into a Dict[str, Any].

Use decode if you want to pass a Dict[str, Any].