Skip to content

Commit 3cddf99

Browse files
committed
💡 reformat and condense docstrings for improved readability across modules
1 parent 49ccae2 commit 3cddf99

12 files changed

Lines changed: 110 additions & 146 deletions

File tree

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ A query string encoding and decoding library for Python.
1111
Ported from `qs <https://www.npmjs.com/package/qs>`__ for JavaScript.
1212

1313
|PyPI - Version| |PyPI - Downloads| |PyPI - Status| |PyPI - Python Version| |PyPI - Format| |Black|
14-
|Test| |CodeQL| |Publish| |Docs| |codecov| |Codacy| |Black| |flake8| |mypy| |pylint| |isort| |bandit|
14+
|Test| |CodeQL| |Publish| |Docs| |codecov| |Codacy| |flake8| |mypy| |pylint| |isort| |bandit|
1515
|License| |Contributor Covenant| |GitHub Sponsors| |GitHub Repo stars|
1616

1717
Usage

docs/qs_codec.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ qs\_codec.decode module
2121
:members:
2222
:undoc-members:
2323
:show-inheritance:
24+
:noindex:
2425

2526
qs\_codec.encode module
2627
-----------------------
@@ -29,6 +30,7 @@ qs\_codec.encode module
2930
:members:
3031
:undoc-members:
3132
:show-inheritance:
33+
:noindex:
3234

3335
Module contents
3436
---------------

docs/qs_codec.utils.rst

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,6 @@ qs\_codec.utils.encode\_utils module
2020
:undoc-members:
2121
:show-inheritance:
2222

23-
qs\_codec.utils.str\_utils module
24-
---------------------------------
25-
26-
.. automodule:: qs_codec.utils.str_utils
27-
:members:
28-
:undoc-members:
29-
:show-inheritance:
30-
3123
qs\_codec.utils.utils module
3224
----------------------------
3325

src/qs_codec/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
qs_codec — Query string encoding/decoding for Python.
2+
Query string encoding/decoding for Python.
33
44
This package is a Python port of the popular `qs` library for JavaScript/Node.js.
55
It strives to match `qs` semantics and edge cases — including list/array formats,

src/qs_codec/decode.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ def decode(
3838
Either a raw query string (``str``) or an already-parsed mapping (``Mapping[str, Any]``).
3939
Passing a mapping is useful in tests or when a custom tokenizer is used upstream.
4040
options:
41-
``DecodeOptions`` controlling delimiter, duplicates policy, list & depth limits,
42-
dot-notation, decoding charset, and more.
41+
``DecodeOptions`` controlling delimiter, duplicates policy, list & depth limits, dot-notation, decoding charset, and more.
4342
4443
Returns
4544
-------
@@ -49,15 +48,12 @@ def decode(
4948
Raises
5049
------
5150
ValueError
52-
If ``value`` is neither ``str`` nor ``Mapping``, or when limits are violated under
53-
``raise_on_limit_exceeded=True``.
51+
If ``value`` is neither ``str`` nor ``Mapping``, or when limits are violated under ``raise_on_limit_exceeded=True``.
5452
5553
Notes
5654
-----
5755
- Empty/falsey ``value`` returns an empty dict.
58-
- When the *number of top-level tokens* exceeds ``list_limit`` and ``parse_lists`` is enabled,
59-
the parser temporarily **disables list parsing** for this invocation to avoid quadratic work.
60-
This mirrors the behavior of other ports and keeps large flat query strings efficient.
56+
- 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.
6157
"""
6258
obj: t.Dict[str, t.Any] = {}
6359

@@ -120,8 +116,7 @@ def _parse_array_value(value: t.Any, options: DecodeOptions, current_list_length
120116
Behavior
121117
--------
122118
- If `comma=True` and `value` is a string that contains commas, split into a list.
123-
- Otherwise, enforce the per-list length limit by comparing `current_list_length` to
124-
`options.list_limit`. When `raise_on_limit_exceeded=True`, violations raise `ValueError`.
119+
- Otherwise, enforce the per-list length limit by comparing `current_list_length` to `options.list_limit`. When `raise_on_limit_exceeded=True`, violations raise `ValueError`.
125120
126121
Returns either the original value or a list of values, without decoding (that happens later).
127122
"""
@@ -158,8 +153,7 @@ def _parse_query_string_values(value: str, options: DecodeOptions) -> t.Dict[str
158153
* Handle empty brackets ``[]`` as list markers.
159154
* Merge duplicate keys according to ``duplicates`` policy.
160155
161-
The output is a *flat* dict (keys are full key-path strings). Higher-level structure is
162-
constructed later by ``_parse_keys`` / ``_parse_object``.
156+
The output is a *flat* dict (keys are full key-path strings). Higher-level structure is constructed later by ``_parse_keys`` / ``_parse_object``.
163157
"""
164158
obj: t.Dict[str, t.Any] = {}
165159

@@ -274,8 +268,7 @@ def _parse_object(
274268
-----
275269
- Builds lists when encountering ``[]`` (respecting ``allow_empty_lists`` and null handling).
276270
- Converts bracketed numeric segments into list indices when allowed and within ``list_limit``.
277-
- When list parsing is disabled and an empty segment is encountered, coerces to ``{"0": leaf}``
278-
to preserve round-trippability with other ports.
271+
- When list parsing is disabled and an empty segment is encountered, coerces to ``{"0": leaf}`` to preserve round-trippability with other ports.
279272
"""
280273
current_list_length: int = 0
281274

src/qs_codec/encode.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
"""Query‑string *encoder* (stringifier).
22
3-
This module converts Python mappings and sequences into a percent‑encoded
4-
query string with feature parity to the Node.js `qs` package where it makes
5-
sense for Python. It supports:
3+
This module converts Python mappings and sequences into a percent‑encoded query string with feature parity to the
4+
Node.js `qs` package where it makes sense for Python. It supports:
65
76
- Stable, deterministic key ordering with an optional custom comparator.
8-
- Multiple list encodings (indices, brackets, repeat key, comma) including the
9-
"comma round‑trip" behavior to preserve single‑element lists.
7+
- Multiple list encodings (indices, brackets, repeat key, comma) including the "comma round‑trip" behavior to preserve single‑element lists.
108
- Custom per‑scalar encoder and `datetime` serializer hooks.
119
- RFC 3986 vs RFC 1738 formatting and optional charset sentinels.
1210
- Dots vs brackets in key paths (`allow_dots`, `encode_dot_in_keys`).
1311
- Strict/null handling, empty‑list emission, and cycle detection.
1412
15-
Nothing in this module mutates caller objects: inputs are shallow‑normalized
16-
and deep‑copied only where safe/necessary to honor options.
13+
Nothing in this module mutates caller objects: inputs are shallow‑normalized and deep‑copied only where safe/necessary to honor options.
1714
"""
1815

1916
import typing as t
@@ -44,14 +41,12 @@ def encode(value: t.Any, options: EncodeOptions = EncodeOptions()) -> str:
4441
options: Encoding behavior (parity with the Node.js `qs` API).
4542
4643
Returns:
47-
The encoded query string (possibly prefixed with "?" if requested),
48-
or an empty string when there is nothing to encode.
44+
The encoded query string (possibly prefixed with "?" if requested), or an empty string when there is nothing to encode.
4945
5046
Notes:
51-
- Caller input is not mutated. When a mapping is provided it is
52-
deep-copied; sequences are projected to a temporary mapping.
47+
- Caller input is not mutated. When a mapping is provided it is deep-copied; sequences are projected to a temporary mapping.
5348
- If a callable `filter` is provided, it can transform the root object.
54-
If an iterable filter is provided, it selects which *root* keys to emit.
49+
- If an iterable filter is provided, it selects which *root* keys to emit.
5550
"""
5651
# Treat `None` as "nothing to encode".
5752
if value is None:
@@ -190,8 +185,7 @@ def _encode(
190185
* a list/tuple of tokens (strings) to be appended to the parent list, or
191186
* a single token string (when a scalar is reached).
192187
193-
It threads a *side-channel* (a chained `WeakKeyDictionary`) through recursion
194-
to detect cycles by remembering where each visited object last appeared.
188+
It threads a *side-channel* (a chained `WeakKeyDictionary`) through recursion to detect cycles by remembering where each visited object last appeared.
195189
196190
Args:
197191
value: Current subtree value.

src/qs_codec/models/encode_options.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
"""Configuration object for `encode`.
22
3-
`EncodeOptions` mirrors the behavior and defaults of the reference `qs` implementation
4-
(ported across Techouse repos). It controls how Python values (dicts/lists/scalars)
5-
are turned into a URL-encoded query string. The options here are intentionally
6-
close to the Node.js library so behavior is predictable across languages.
3+
`EncodeOptions` mirrors the behavior and defaults of the reference `qs` implementation. It controls how Python values
4+
(dicts/lists/scalars) are turned into a URL-encoded query string. The options here are intentionally close to the Node.js
5+
library so behavior is predictable across languages.
76
87
Key interactions to be aware of:
9-
- `allow_dots` vs `encode_dot_in_keys`: when unspecified, `allow_dots` mirrors the
10-
value of `encode_dot_in_keys` (see `__post_init__`).
8+
- `allow_dots` vs `encode_dot_in_keys`: when unspecified, `allow_dots` mirrors the value of `encode_dot_in_keys` (see `__post_init__`).
119
- `indices` is deprecated and mapped to `list_format` for parity with newer ports.
12-
- `encoder` and `serialize_date` let you customize scalar/date serialization,
13-
while `encode=False` short-circuits the encoder entirely.
14-
- `sort` may return -1/0/+1 (like `strcmp`/`NSComparisonResult.rawValue`) to
15-
deterministically order keys.
10+
- `encoder` and `serialize_date` let you customize scalar/date serialization, while `encode=False` short-circuits the encoder entirely.
11+
- `sort` may return -1/0/+1 (like `strcmp`/`NSComparisonResult.rawValue`) to deterministically order keys.
1612
"""
1713

1814
import typing as t
@@ -74,7 +70,7 @@ class EncodeOptions:
7470
filter: t.Optional[t.Union[t.Callable, t.List[t.Union[str, int]]]] = field(default=None)
7571
"""Restrict which keys get included.
7672
- If a callable is provided, it is invoked for each key and should return the
77-
replacement value (or `None` to drop when `skip_nulls` applies).
73+
replacement value (or `None` to drop when `skip_nulls` applies).
7874
- If a list is provided, only those keys/indices are retained.
7975
"""
8076

src/qs_codec/models/undefined.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
"""Undefined sentinel.
22
3-
This module defines a tiny singleton `Undefined` used as a *sentinel* to mean
4-
“no value provided / omit this key”, similar to JavaScript’s `undefined`.
3+
This module defines a tiny singleton `Undefined` used as a *sentinel* to mean “no value provided / omit this key”,
4+
similar to JavaScript’s `undefined`.
55
6-
Unlike `None` (which commonly means an explicit null), `Undefined` is used by
7-
the encoder and helper utilities to *skip* emitting a key or to signal that a
8-
value is intentionally absent and should not be serialized.
6+
Unlike `None` (which commonly means an explicit null), `Undefined` is used by the encoder and helper utilities to *skip*
7+
emitting a key or to signal that a value is intentionally absent and should not be serialized.
98
10-
The sentinel is identity-based: every construction returns the same instance,
11-
so `is` comparisons are reliable (e.g., `value is Undefined()`).
9+
The sentinel is identity-based: every construction returns the same instance, so `is` comparisons are reliable
10+
(e.g., `value is Undefined()`).
1211
"""
1312

1413
import typing as t
@@ -18,10 +17,8 @@ class Undefined:
1817
"""Singleton sentinel object representing an “undefined” value.
1918
2019
Notes:
21-
* This is **not** the same as `None`. Use `None` to represent a *null*
22-
value and `Undefined()` to represent “no value / omit”.
23-
* All calls to ``Undefined()`` return the same instance. Prefer identity
24-
checks (``is``) over equality checks.
20+
* This is **not** the same as `None`. Use `None` to represent a *null* value and `Undefined()` to represent “no value / omit”.
21+
* All calls to ``Undefined()`` return the same instance. Prefer identity checks (``is``) over equality checks.
2522
2623
Examples:
2724
>>> from qs_codec.models.undefined import Undefined
@@ -40,8 +37,7 @@ class Undefined:
4037
def __new__(cls: t.Type["Undefined"]) -> "Undefined":
4138
"""Return the singleton instance.
4239
43-
Creating `Undefined()` multiple times always returns the same object
44-
reference. This ensures identity checks (``is``) are stable.
40+
Creating `Undefined()` multiple times always returns the same object reference. This ensures identity checks (``is``) are stable.
4541
"""
4642
if cls._instance is None:
4743
cls._instance = super().__new__(cls)

src/qs_codec/models/weak_wrapper.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
class _Proxy:
1818
"""Container for the original object.
1919
20-
NOTE: Proxies must be weak-referenceable because the cache holds them
21-
in a WeakValueDictionary. That requires "__weakref__" in __slots__.
20+
NOTE: Proxies must be weak-referenceable because the cache holds them in a WeakValueDictionary. That requires "__weakref__" in __slots__.
2221
"""
2322

2423
__slots__ = ("value", "__weakref__")

src/qs_codec/utils/decode_utils.py

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
This mirrors the semantics of the Node `qs` library:
44
55
- Decoding handles both UTF‑8 and Latin‑1 code paths.
6-
- Key splitting keeps bracket groups *balanced* and optionally treats dots
7-
as path separators when ``allow_dots=True``.
6+
- Key splitting keeps bracket groups *balanced* and optionally treats dots as path separators when ``allow_dots=True``.
87
"""
98

109
import re
@@ -68,13 +67,16 @@ def decode(
6867
"""Decode a URL‑encoded scalar.
6968
7069
Behavior:
71-
- Always replace ``+`` with a literal space *before* decoding.
72-
- For ``Charset.LATIN1``: unescape only ``%XX`` byte sequences using
73-
:meth:`unescape` (to mimic older browser/JS behavior). ``%uXXXX``
74-
sequences are left untouched here.
75-
- For UTF‑8 (default): defer to :func:`urllib.parse.unquote`.
76-
77-
Returns ``None`` when the input is ``None``.
70+
- Replace ``+`` with a literal space *before* decoding.
71+
- If ``charset`` is :data:`~qs_codec.enums.charset.Charset.LATIN1`,
72+
replace only ``%XX`` byte sequences using :meth:`DecodeUtils.unescape`.
73+
``%uXXXX`` sequences are left as‑is here to mimic older browser/JS behavior.
74+
- Otherwise (UTF‑8), defer to :func:`urllib.parse.unquote`.
75+
76+
Returns
77+
-------
78+
Optional[str]
79+
``None`` when the input is ``None``.
7880
"""
7981
if string is None:
8082
return None
@@ -103,18 +105,11 @@ def split_key_into_segments(
103105
"""
104106
Split a composite key into *balanced* bracket segments.
105107
106-
- If ``allow_dots`` is True, convert dots to bracket groups first
107-
(``a.b[c]`` → ``a[b][c]``) while leaving existing brackets intact.
108-
- The *parent* (non‑bracket) prefix becomes the first segment, e.g.
109-
``"a[b][c]"`` → ``["a", "[b]", "[c]"]``.
110-
- Bracket groups are *balanced* using a counter so nested brackets
111-
within a single group (e.g. ``"[with[inner]]"``) are treated as one
112-
segment.
113-
- When ``max_depth <= 0``, no splitting occurs; the key is returned as
114-
a single segment (qs semantics).
115-
- If there are more groups beyond ``max_depth`` and ``strict_depth`` is
116-
True, an ``IndexError`` is raised. Otherwise, the remainder is added
117-
as one final segment (again mirroring qs).
108+
- If ``allow_dots`` is True, convert dots to bracket groups first (``a.b[c]`` → ``a[b][c]``) while leaving existing brackets intact.
109+
- The *parent* (non‑bracket) prefix becomes the first segment, e.g. ``"a[b][c]"`` → ``["a", "[b]", "[c]"]``.
110+
- Bracket groups are *balanced* using a counter so nested brackets within a single group (e.g. ``"[with[inner]]"``) are treated as one segment.
111+
- When ``max_depth <= 0``, no splitting occurs; the key is returned as a single segment (qs semantics).
112+
- If there are more groups beyond ``max_depth`` and ``strict_depth`` is True, an ``IndexError`` is raised. Otherwise, the remainder is added as one final segment (again mirroring qs).
118113
119114
This runs in O(n) time over the key string.
120115
"""

0 commit comments

Comments
 (0)