Skip to content

Commit 9884e97

Browse files
committed
build: use latest pyo3 API
1 parent 0b7b1eb commit 9884e97

10 files changed

Lines changed: 142 additions & 288 deletions

File tree

Cargo.lock

Lines changed: 85 additions & 194 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,20 @@ readme = "README.md"
1212
license-file = "LICENSE"
1313
repository = "https://github.com/litestar-org/fast-query-parsers"
1414
version = "1.0.3"
15-
edition = "2021"
15+
edition = "2024"
1616

1717
[lib]
1818
name = "fast_query_parsers"
1919
crate-type = ["cdylib"]
2020

21+
[build-dependencies]
22+
pyo3-build-config = { version = "0.27.2", features = ["resolve-config"] }
23+
2124
[dependencies]
22-
lazy_static = "1.4.0"
23-
pyo3 = { version = "0.19.2", features = ["extension-module", "abi3-py38"] }
24-
pythonize = "0.19.0"
25-
regex = { version = "1.9.3", features = ["perf"] }
26-
rustc-hash = "1.1.0"
27-
serde_json = "1.0.105"
25+
lazy_static = "1.5.0"
26+
pyo3 = { version = "0.27.2", default-features = false, features = ["macros"] }
27+
pythonize = "0.27.0"
28+
regex = "1.12.2"
29+
rustc-hash = "2.1.1"
30+
serde_json = "1.0.145"
2831
urlencoding = "2.1.3"

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
| CI/CD | | [![Publish](https://github.com/litestar-org/fast-query-parsers/actions/workflows/publish.yaml/badge.svg)](https://github.com/litestar-org/fast-query-parsers/actions/workflows/publish.yaml) [![CI](https://github.com/litestar-org/fast-query-parsers/actions/workflows/ci.yaml/badge.svg)](https://github.com/litestar-org/fast-query-parsers/actions/workflows/ci.yaml) |
1515
| Package | | [![PyPI - Version](https://img.shields.io/pypi/v/fast-query-parsers?labelColor=202235&color=edb641&logo=python&logoColor=edb641)](https://badge.fury.io/py/litestar) ![PyPI - Support Python Versions](https://img.shields.io/pypi/pyversions/fast-query-parsers?labelColor=202235&color=edb641&logo=python&logoColor=edb641) ![PyPI - Downloads](https://img.shields.io/pypi/dm/fast-query-parsers?logo=python&label=fast-query-parsers%20downloads&labelColor=202235&color=edb641&logoColor=edb641) |
1616
| Community | | [![Reddit](https://img.shields.io/reddit/subreddit-subscribers/litestarapi?label=r%2FLitestar&logo=reddit&labelColor=202235&color=edb641&logoColor=edb641)](https://reddit.com/r/litestarapi) [![Discord](https://img.shields.io/discord/919193495116337154?labelColor=202235&color=edb641&label=chat%20on%20discord&logo=discord&logoColor=edb641)](https://discord.gg/X3FJqy8d2j) [![Matrix](https://img.shields.io/badge/chat%20on%20Matrix-bridged-202235?labelColor=202235&color=edb641&logo=matrix&logoColor=edb641)](https://matrix.to/#/#litestar:matrix.org) [![Medium](https://img.shields.io/badge/Medium-202235?labelColor=202235&color=edb641&logo=medium&logoColor=edb641)](https://blog.litestar.dev) [![Twitter](https://img.shields.io/twitter/follow/LitestarAPI?labelColor=202235&color=edb641&logo=twitter&logoColor=edb641&style=flat)](https://twitter.com/LitestarAPI) [![Blog](https://img.shields.io/badge/Blog-litestar.dev-202235?logo=blogger&labelColor=202235&color=edb641&logoColor=edb641)](https://blog.litestar.dev) |
17-
| Meta | | [![Litestar Project](https://img.shields.io/badge/Litestar%20Org-%E2%AD%90%20Fast%20Query%20Parsers-202235.svg?logo=python&labelColor=202235&color=edb641&logoColor=edb641)](https://github.com/litestar-org/fast-query-parsers) [![License - MIT](https://img.shields.io/badge/license-MIT-202235.svg?logo=python&labelColor=202235&color=edb641&logoColor=edb641)](https://spdx.org/licenses/) [![Litestar Sponsors](https://img.shields.io/badge/Sponsor-%E2%9D%A4-%23edb641.svg?&logo=github&logoColor=edb641&labelColor=202235)](https://github.com/sponsors/litestar-org) [![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json&labelColor=202235)](https://github.com/astral-sh/ruff) [![code style - Black](https://img.shields.io/badge/code%20style-black-000000.svg?logo=python&labelColor=202235&logoColor=edb641)](https://github.com/psf/black) |
17+
| Meta | | [![Litestar Project](https://img.shields.io/badge/Litestar%20Org-%E2%AD%90%20Fast%20Query%20Parsers-202235.svg?logo=python&labelColor=202235&color=edb641&logoColor=edb641)](https://github.com/litestar-org/fast-query-parsers) [![License - MIT](https://img.shields.io/badge/license-MIT-202235.svg?logo=python&labelColor=202235&color=edb641&logoColor=edb641)](https://spdx.org/licenses/) [![Litestar Sponsors](https://img.shields.io/badge/Sponsor-%E2%9D%A4-%23edb641.svg?&logo=github&logoColor=edb641&labelColor=202235)](https://github.com/sponsors/litestar-org) [![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json&labelColor=202235)](https://github.com/astral-sh/ruff)|
1818

1919
<!-- prettier-ignore-end -->
2020
</div>
@@ -122,13 +122,13 @@ To actually mimic the parsing done by `parse_url_encoded_dict` we will need a ut
122122
from collections import defaultdict
123123
from contextlib import suppress
124124
from json import loads, JSONDecodeError
125-
from typing import Any, DefaultDict, Dict, List
125+
from typing import Any
126126
from urllib.parse import parse_qsl
127127

128128

129-
def parse_url_encoded_form_data(encoded_data: bytes) -> Dict[str, Any]:
130-
"""Parse an url encoded form data into dict of parsed values"""
131-
decoded_dict: DefaultDict[str, List[Any]] = defaultdict(list)
129+
def parse_url_encoded_form_data(encoded_data: bytes) -> dict[str, Any]:
130+
"""Parse a url encoded form data into dict of parsed values"""
131+
decoded_dict: defaultdict[str, list[Any]] = defaultdict(list)
132132
for k, v in parse_qsl(encoded_data.decode(), keep_blank_values=True):
133133
with suppress(JSONDecodeError):
134134
v = loads(v) if isinstance(v, str) else v

benchmarks.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
from __future__ import annotations
2+
13
from collections import defaultdict
24
from contextlib import suppress
35
from json import JSONDecodeError, loads
4-
from typing import Any, DefaultDict, Dict, List
6+
from typing import Any
57
from urllib.parse import parse_qs as stdlib_parse_qs
68
from urllib.parse import parse_qsl as stdlib_parse_qsl
79
from urllib.parse import urlencode
@@ -19,11 +21,11 @@
1921
("calories", "122.53"),
2022
("healthy", True),
2123
("polluting", False),
22-
]
24+
],
2325
).encode()
2426

2527

26-
def parse_url_encoded_form_data(encoded_data: bytes) -> Dict[str, Any]:
28+
def parse_url_encoded_form_data(encoded_data: bytes) -> dict[str, Any]:
2729
"""Parse an url encoded form data dict.
2830
2931
Args:
@@ -35,15 +37,15 @@ def parse_url_encoded_form_data(encoded_data: bytes) -> Dict[str, Any]:
3537
A parsed dict.
3638
3739
"""
38-
decoded_dict: DefaultDict[str, List[Any]] = defaultdict(list)
40+
decoded_dict: defaultdict[str, list[Any]] = defaultdict(list)
3941
for k, v in stdlib_parse_qsl(encoded_data.decode(), keep_blank_values=True):
4042
with suppress(JSONDecodeError):
4143
v = loads(v) if isinstance(v, str) else v # noqa: PLW2901
4244
decoded_dict[k].append(v)
4345
return {k: v if len(v) > 1 else v[0] for k, v in decoded_dict.items()}
4446

4547

46-
def bench_qsl(runner: pyperf.Runner):
48+
def bench_qsl(runner: pyperf.Runner) -> None:
4749
runner.bench_func(
4850
"stdlib parse_qsl parsing query string",
4951
lambda: stdlib_parse_qsl(b"key=1&key=2&key=3&another=a&zorg=5=".decode(), keep_blank_values=True),
@@ -59,7 +61,7 @@ def bench_qsl(runner: pyperf.Runner):
5961
runner.bench_func("parse_query_string urlencoded query string", lambda: parse_query_string(url_encoded_query, "&"))
6062

6163

62-
def bench_qs(runner: pyperf.Runner):
64+
def bench_qs(runner: pyperf.Runner) -> None:
6365
runner.bench_func(
6466
"stdlib parse_qs parsing url-encoded values into dict",
6567
lambda: stdlib_parse_qs(url_encoded_query.decode()),
@@ -69,7 +71,8 @@ def bench_qs(runner: pyperf.Runner):
6971
lambda: parse_url_encoded_form_data(url_encoded_query),
7072
)
7173
runner.bench_func(
72-
"parse_url_encoded_dict parsing url-encoded values into dict", lambda: parse_url_encoded_dict(url_encoded_query)
74+
"parse_url_encoded_dict parsing url-encoded values into dict",
75+
lambda: parse_url_encoded_dict(url_encoded_query),
7376
)
7477

7578

build.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() {
2+
pyo3_build_config::use_pyo3_cfgs();
3+
}

pyproject.toml

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -58,41 +58,7 @@ line-length = 120
5858
target-version = "py38"
5959

6060
[tool.ruff.lint]
61-
select = [
62-
"A", # flake8-builtins
63-
"B", # flake8-bugbear
64-
"BLE", # flake8-blind-except
65-
"C4", # flake8-comprehensions
66-
"D", # pydocstyle
67-
"DJ", # flake8-django
68-
"DTZ", # flake8-datetimez
69-
"E", # pycodestyle errors
70-
"ERA", # eradicate
71-
"EXE", # flake8-executable
72-
"F", # pyflakes
73-
"G", # flake8-logging-format
74-
"I", # isort
75-
"ICN", # flake8-import-conventions
76-
"ISC", # flake8-implicit-str-concat
77-
"N", # pep8-naming
78-
"PIE", # flake8-pie
79-
"PLC", # pylint - convention
80-
"PLE", # pylint - error
81-
"PLW", # pylint - warning
82-
"PTH", # flake8-use-pathlib
83-
"Q", # flake8-quotes
84-
"RET", # flake8-return
85-
"RUF", # Ruff-specific rules
86-
"S", # flake8-bandit
87-
"SIM", # flake8-simplify
88-
"T10", # flake8-debugger
89-
"T20", # flake8-print
90-
"TC", # flake8-type-checking
91-
"TID", # flake8-tidy-imports
92-
"UP", # pyupgrade
93-
"W", # pycodestyle - warning
94-
"YTT", # flake8-2020
95-
]
61+
select = ["ALL"]
9662
ignore = [
9763
"A003", # flake8-builtins - class attribute {name} is shadowing a python builtin
9864
"B010", # flake8-bugbear - do not call setattr with a constant attribute value

src/lib.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,32 @@
11
mod query_string;
22

33
use pyo3::prelude::*;
4+
use pyo3::types::PyAny;
45
use pythonize::pythonize;
56

67
pub use query_string::{parse_query_string as parse_qs, parse_query_string_to_json};
78

89
// parse query string into a list of tuples.
910
#[pyfunction]
1011
#[pyo3(text_signature = "(qs, separator)")]
11-
fn parse_query_string(qs: &[u8], separator: char) -> PyResult<Vec<(String, String)>> {
12-
Ok(parse_qs(qs, separator))
12+
fn parse_query_string(qs: &[u8], separator: char) -> Vec<(String, String)> {
13+
parse_qs(qs, separator)
1314
}
1415

1516
// parse query string into a python object.
1617
#[pyfunction]
17-
#[pyo3(signature = (qs, parse_numbers=true),text_signature = "(qs, parse_numbers=true)")]
18-
fn parse_url_encoded_dict(py: Python, qs: &[u8], parse_numbers: bool) -> PyResult<PyObject> {
19-
Ok(pythonize(py, &parse_query_string_to_json(qs, parse_numbers)).unwrap())
18+
#[pyo3(signature = (qs, parse_numbers=true), text_signature = "(qs, parse_numbers=true)")]
19+
fn parse_url_encoded_dict(py: Python, qs: &[u8], parse_numbers: bool) -> Py<PyAny> {
20+
pythonize(py, &parse_query_string_to_json(qs, parse_numbers))
21+
.unwrap()
22+
.into()
2023
}
2124

22-
#[pymodule]
23-
fn fast_query_parsers(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
24-
m.add_function(wrap_pyfunction!(parse_query_string, m)?)?;
25-
m.add_function(wrap_pyfunction!(parse_url_encoded_dict, m)?)?;
25+
#[pyo3::prelude::pymodule(gil_used = false)]
26+
mod fast_query_parsers {
27+
#[pymodule_export]
28+
use super::parse_query_string;
2629

27-
Ok(())
30+
#[pymodule_export]
31+
use super::parse_url_encoded_dict;
2832
}

src/query_string.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::convert::Infallible;
33
use lazy_static::lazy_static;
44
use regex::Regex;
55
use rustc_hash::FxHashMap;
6-
use serde_json::{from_str, Value};
6+
use serde_json::{Value, from_str};
77
use urlencoding::decode;
88

99
lazy_static! {
@@ -93,7 +93,7 @@ pub fn parse_query_string_to_json(bs: &[u8], parse_numbers: bool) -> Value {
9393

9494
#[cfg(test)]
9595
mod tests {
96-
use serde_json::{json, to_string, Value};
96+
use serde_json::{Value, json, to_string};
9797

9898
use super::*;
9999

tests/test_parse_qs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818

1919
def test_parse_urlencoded_with_parse_numbers() -> None:
20-
result = parse_url_encoded_dict(encoded, True)
20+
result = parse_url_encoded_dict(encoded, parse_numbers=True)
2121
assert result == {
2222
"value": [10, 12],
2323
"veggies": ["tomato", "potato", "aubergine"],
@@ -30,7 +30,7 @@ def test_parse_urlencoded_with_parse_numbers() -> None:
3030

3131

3232
def test_parse_urlencoded_without_parse_numbers() -> None:
33-
result = parse_url_encoded_dict(encoded, False)
33+
result = parse_url_encoded_dict(encoded, parse_numbers=False)
3434
assert result == {
3535
"value": ["10", "12"],
3636
"veggies": ["tomato", "potato", "aubergine"],

tests/test_parse_qsl.py

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import List, Tuple
1+
from __future__ import annotations
2+
23
from urllib.parse import parse_qsl, urlencode
34

45
import pytest
@@ -7,7 +8,7 @@
78

89

910
@pytest.mark.parametrize(
10-
"qs, expected",
11+
("qs", "expected"),
1112
[
1213
("", []),
1314
("&", []),
@@ -19,64 +20,47 @@
1920
("&a=b", [("a", "b")]),
2021
("a=a+b&b=b+c", [("a", "a b"), ("b", "b c")]),
2122
("a=1&a=2", [("a", "1"), ("a", "2")]),
22-
("", []),
23-
("&", []),
24-
("&&", []),
25-
("=", [("", "")]),
26-
("=a", [("", "a")]),
27-
("a", [("a", "")]),
28-
("a=", [("a", "")]),
29-
("&a=b", [("a", "b")]),
30-
("a=a+b&b=b+c", [("a", "a b"), ("b", "b c")]),
31-
("a=1&a=2", [("a", "1"), ("a", "2")]),
32-
(";a=b", [(";a", "b")]),
33-
("a=a+b;b=b+c", [("a", "a b;b=b c")]),
3423
(";a=b", [(";a", "b")]),
3524
("a=a+b;b=b+c", [("a", "a b;b=b c")]),
3625
],
3726
)
38-
def test_parse_qsl_standard_separator(qs: str, expected: List[Tuple[str, str]]) -> None:
27+
def test_parse_qsl_standard_separator(qs: str, expected: list[tuple[str, str]]) -> None:
3928
result = parse_query_string(qs.encode(), "&")
4029
assert result == parse_qsl(qs, keep_blank_values=True) == expected
4130

4231

4332
@pytest.mark.parametrize(
44-
"qs, expected",
33+
("qs", "expected"),
4534
[
4635
(";", []),
4736
(";;", []),
4837
(";a=b", [("a", "b")]),
4938
("a=a+b;b=b+c", [("a", "a b"), ("b", "b c")]),
5039
("a=1;a=2", [("a", "1"), ("a", "2")]),
51-
(";", []),
52-
(";;", []),
53-
(";a=b", [("a", "b")]),
54-
("a=a+b;b=b+c", [("a", "a b"), ("b", "b c")]),
55-
("a=1;a=2", [("a", "1"), ("a", "2")]),
5640
],
5741
)
58-
def test_parse_qsl_semicolon_separator(qs: str, expected: List[Tuple[str, str]]) -> None:
42+
def test_parse_qsl_semicolon_separator(qs: str, expected: list[tuple[str, str]]) -> None:
5943
result = parse_query_string(qs.encode(), ";")
6044
assert result == parse_qsl(qs, separator=";", keep_blank_values=True) == expected
6145

6246

6347
@pytest.mark.parametrize(
6448
"values",
65-
(
49+
[
6650
(("first", "x@test.com"), ("second", "aaa")),
6751
(("first", "&@A.ac"), ("second", "aaa")),
6852
(("first", "a@A.ac&"), ("second", "aaa")),
6953
(("first", "a@A&.ac"), ("second", "aaa")),
70-
),
54+
],
7155
)
72-
def test_query_parsing_of_escaped_values(values: Tuple[Tuple[str, str], Tuple[str, str]]) -> None:
56+
def test_query_parsing_of_escaped_values(values: tuple[tuple[str, str], tuple[str, str]]) -> None:
7357
url_encoded = urlencode(values)
7458
assert parse_query_string(url_encoded.encode(), "&") == list(values)
7559

7660

7761
def test_parses_non_ascii_text() -> None:
7862
assert parse_query_string("arabic_text=اختبار اللغة العربية".encode(), "&") == [
79-
("arabic_text", "اختبار اللغة العربية")
63+
("arabic_text", "اختبار اللغة العربية"),
8064
]
8165

8266

0 commit comments

Comments
 (0)