Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ jobs:
py: "3.13"
- toxenv: "python3.14"
py: "3.14"
- toxenv: "pypy3.8"
py: "pypy-3.8"
- toxenv: "pypy3.9"
py: "pypy-3.9"
- toxenv: "pypy3.10"
py: "pypy-3.10"
- toxenv: "pypy3.11"
py: "pypy-3.11"
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.py }}
Expand Down Expand Up @@ -128,4 +136,4 @@ jobs:
else
echo "The outputs are different."
exit 1
fi
fi
6 changes: 6 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ Highlights
- Safety limits: configurable nesting depth, parameter limit, and list index limit; optional strict-depth errors; duplicate-key strategies (combine/first/last).
- Extras: numeric entity decoding (e.g. ``☺`` → ☺), alternate delimiters/regex, and query-prefix helpers.

Compatibility
-------------

- CPython 3.8–3.14 (default tox envs).
- PyPy 3.8–3.11 (run ``tox -e pypy3.8`` through ``tox -e pypy3.11`` locally; CI mirrors this matrix).

Usage
-----

Expand Down
6 changes: 6 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ Highlights
- Safety limits: configurable nesting depth, parameter limit, and list index limit; optional strict-depth errors; duplicate-key strategies (combine/first/last).
- Extras: numeric entity decoding (e.g. ``☺`` → ☺), alternate delimiters/regex, and query-prefix helpers.

Compatibility
-------------

- CPython 3.8–3.14 (default tox envs).
- PyPy 3.8–3.11 (run ``tox -e pypy3.8`` through ``tox -e pypy3.11`` locally; CI mirrors this matrix).

Usage
-----

Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ classifiers = [
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Text Processing :: General",
"Topic :: Utilities",
Expand Down
10 changes: 9 additions & 1 deletion tests/unit/decode_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,15 @@ def test_continues_parsing_when_no_parent_is_found(

def test_does_not_error_when_parsing_a_very_long_list(self) -> None:
buf: str = "a[]=a"
while getsizeof(buf) < 128 * 1024:

def _approx_size(value: str) -> int:
try:
return getsizeof(value)
except TypeError:
# PyPy does not implement getsizeof; fall back to len, which matches byte size for ASCII payloads.
return len(value)

while _approx_size(buf) < 128 * 1024:
buf += "&"
buf += buf

Expand Down
21 changes: 19 additions & 2 deletions tests/unit/example_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,16 @@ def custom_decoder(value: t.Any, charset: t.Optional[qs_codec.Charset]):
else datetime.datetime.utcfromtimestamp(7)
)
},
qs_codec.EncodeOptions(encode=False, serialize_date=lambda date: str(int(date.timestamp()))),
qs_codec.EncodeOptions(
encode=False,
serialize_date=lambda date: str(
int(
(
date if date.tzinfo is not None else date.replace(tzinfo=datetime.timezone.utc)
).timestamp()
)
),
),
)
== "a=7"
)
Expand Down Expand Up @@ -334,7 +343,15 @@ def custom_decoder(value: t.Any, charset: t.Optional[qs_codec.Charset]):
encode=False,
filter=lambda prefix, value: {
"b": None,
"e[f]": int(value.timestamp()) if isinstance(value, datetime.datetime) else value,
"e[f]": (
int(
(
value if value.tzinfo is not None else value.replace(tzinfo=datetime.timezone.utc)
).timestamp()
)
if isinstance(value, datetime.datetime)
else value
),
"e[g][0]": value * 2 if isinstance(value, int) else value,
}.get(prefix, value),
),
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/weakref_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def test_weak_key_dict_with_dict_keys(self) -> None:
assert d.get(foo) == 123
assert d.get(foo_copy) == 123
del foo
gc.collect()
assert len(d) == 0
assert d.get(foo_copy) is None

Expand All @@ -31,6 +32,7 @@ def test_weak_key_dict_with_nested_dict_keys(self) -> None:
assert d.get(foo) == 123
assert d.get(foo_copy) == 123
del foo
gc.collect()
assert len(d) == 0
assert d.get(foo_copy) is None

Expand Down
20 changes: 20 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ envlist =
python3.12,
python3.13,
python3.14,
pypy3.8,
pypy3.9,
pypy3.10,
pypy3.11,
black,
flake8,
linters,
Expand All @@ -22,6 +26,10 @@ python =
3.12: python3.12
3.13: python3.13
3.14: python3.14
pypy-3.8: pypy3.8
pypy-3.9: pypy3.9
pypy-3.10: pypy3.10
pypy-3.11: pypy3.11

[testenv]
deps =
Expand Down Expand Up @@ -66,6 +74,18 @@ deps =
commands =
pylint --rcfile=tox.ini src/qs_codec

[testenv:pypy3.8]
basepython = pypy3.8

[testenv:pypy3.9]
basepython = pypy3.9

[testenv:pypy3.10]
basepython = pypy3.10

[testenv:pypy3.11]
basepython = pypy3.11

[testenv:bandit]
basepython = python3
skip_install = true
Expand Down