diff --git a/README.md b/README.md index 87e0dc0..fcabfcd 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ | 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) | | 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) | | 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) | -| 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) | +| 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)| @@ -122,13 +122,13 @@ To actually mimic the parsing done by `parse_url_encoded_dict` we will need a ut from collections import defaultdict from contextlib import suppress from json import loads, JSONDecodeError -from typing import Any, DefaultDict, Dict, List +from typing import Any from urllib.parse import parse_qsl -def parse_url_encoded_form_data(encoded_data: bytes) -> Dict[str, Any]: - """Parse an url encoded form data into dict of parsed values""" - decoded_dict: DefaultDict[str, List[Any]] = defaultdict(list) +def parse_url_encoded_form_data(encoded_data: bytes) -> dict[str, Any]: + """Parse a url encoded form data into dict of parsed values""" + decoded_dict: defaultdict[str, list[Any]] = defaultdict(list) for k, v in parse_qsl(encoded_data.decode(), keep_blank_values=True): with suppress(JSONDecodeError): v = loads(v) if isinstance(v, str) else v diff --git a/benchmarks.py b/benchmarks.py index 8f49150..8700604 100644 --- a/benchmarks.py +++ b/benchmarks.py @@ -1,7 +1,9 @@ +from __future__ import annotations + from collections import defaultdict from contextlib import suppress from json import JSONDecodeError, loads -from typing import Any, DefaultDict, Dict, List +from typing import Any from urllib.parse import parse_qs as stdlib_parse_qs from urllib.parse import parse_qsl as stdlib_parse_qsl from urllib.parse import urlencode @@ -19,11 +21,11 @@ ("calories", "122.53"), ("healthy", True), ("polluting", False), - ] + ], ).encode() -def parse_url_encoded_form_data(encoded_data: bytes) -> Dict[str, Any]: +def parse_url_encoded_form_data(encoded_data: bytes) -> dict[str, Any]: """Parse an url encoded form data dict. Args: @@ -35,7 +37,7 @@ def parse_url_encoded_form_data(encoded_data: bytes) -> Dict[str, Any]: A parsed dict. """ - decoded_dict: DefaultDict[str, List[Any]] = defaultdict(list) + decoded_dict: defaultdict[str, list[Any]] = defaultdict(list) for k, v in stdlib_parse_qsl(encoded_data.decode(), keep_blank_values=True): with suppress(JSONDecodeError): v = loads(v) if isinstance(v, str) else v # noqa: PLW2901 @@ -43,7 +45,7 @@ def parse_url_encoded_form_data(encoded_data: bytes) -> Dict[str, Any]: return {k: v if len(v) > 1 else v[0] for k, v in decoded_dict.items()} -def bench_qsl(runner: pyperf.Runner): +def bench_qsl(runner: pyperf.Runner) -> None: runner.bench_func( "stdlib parse_qsl parsing query string", 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): runner.bench_func("parse_query_string urlencoded query string", lambda: parse_query_string(url_encoded_query, "&")) -def bench_qs(runner: pyperf.Runner): +def bench_qs(runner: pyperf.Runner) -> None: runner.bench_func( "stdlib parse_qs parsing url-encoded values into dict", lambda: stdlib_parse_qs(url_encoded_query.decode()), @@ -69,7 +71,8 @@ def bench_qs(runner: pyperf.Runner): lambda: parse_url_encoded_form_data(url_encoded_query), ) runner.bench_func( - "parse_url_encoded_dict parsing url-encoded values into dict", lambda: parse_url_encoded_dict(url_encoded_query) + "parse_url_encoded_dict parsing url-encoded values into dict", + lambda: parse_url_encoded_dict(url_encoded_query), ) diff --git a/pyproject.toml b/pyproject.toml index ea6e3cf..728acc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,41 +58,7 @@ line-length = 120 target-version = "py38" [tool.ruff.lint] -select = [ - "A", # flake8-builtins - "B", # flake8-bugbear - "BLE", # flake8-blind-except - "C4", # flake8-comprehensions - "D", # pydocstyle - "DJ", # flake8-django - "DTZ", # flake8-datetimez - "E", # pycodestyle errors - "ERA", # eradicate - "EXE", # flake8-executable - "F", # pyflakes - "G", # flake8-logging-format - "I", # isort - "ICN", # flake8-import-conventions - "ISC", # flake8-implicit-str-concat - "N", # pep8-naming - "PIE", # flake8-pie - "PLC", # pylint - convention - "PLE", # pylint - error - "PLW", # pylint - warning - "PTH", # flake8-use-pathlib - "Q", # flake8-quotes - "RET", # flake8-return - "RUF", # Ruff-specific rules - "S", # flake8-bandit - "SIM", # flake8-simplify - "T10", # flake8-debugger - "T20", # flake8-print - "TC", # flake8-type-checking - "TID", # flake8-tidy-imports - "UP", # pyupgrade - "W", # pycodestyle - warning - "YTT", # flake8-2020 -] +select = ["ALL"] ignore = [ "A003", # flake8-builtins - class attribute {name} is shadowing a python builtin "B010", # flake8-bugbear - do not call setattr with a constant attribute value diff --git a/tests/test_parse_qs.py b/tests/test_parse_qs.py index 2f74d01..6c8621c 100644 --- a/tests/test_parse_qs.py +++ b/tests/test_parse_qs.py @@ -17,7 +17,7 @@ def test_parse_urlencoded_with_parse_numbers() -> None: - result = parse_url_encoded_dict(encoded, True) + result = parse_url_encoded_dict(encoded, parse_numbers=True) assert result == { "value": [10, 12], "veggies": ["tomato", "potato", "aubergine"], @@ -30,7 +30,7 @@ def test_parse_urlencoded_with_parse_numbers() -> None: def test_parse_urlencoded_without_parse_numbers() -> None: - result = parse_url_encoded_dict(encoded, False) + result = parse_url_encoded_dict(encoded, parse_numbers=False) assert result == { "value": ["10", "12"], "veggies": ["tomato", "potato", "aubergine"], diff --git a/tests/test_parse_qsl.py b/tests/test_parse_qsl.py index ea6e409..ddd8141 100644 --- a/tests/test_parse_qsl.py +++ b/tests/test_parse_qsl.py @@ -1,4 +1,5 @@ -from typing import List, Tuple +from __future__ import annotations + from urllib.parse import parse_qsl, urlencode import pytest @@ -7,7 +8,7 @@ @pytest.mark.parametrize( - "qs, expected", + ("qs", "expected"), [ ("", []), ("&", []), @@ -19,64 +20,47 @@ ("&a=b", [("a", "b")]), ("a=a+b&b=b+c", [("a", "a b"), ("b", "b c")]), ("a=1&a=2", [("a", "1"), ("a", "2")]), - ("", []), - ("&", []), - ("&&", []), - ("=", [("", "")]), - ("=a", [("", "a")]), - ("a", [("a", "")]), - ("a=", [("a", "")]), - ("&a=b", [("a", "b")]), - ("a=a+b&b=b+c", [("a", "a b"), ("b", "b c")]), - ("a=1&a=2", [("a", "1"), ("a", "2")]), - (";a=b", [(";a", "b")]), - ("a=a+b;b=b+c", [("a", "a b;b=b c")]), (";a=b", [(";a", "b")]), ("a=a+b;b=b+c", [("a", "a b;b=b c")]), ], ) -def test_parse_qsl_standard_separator(qs: str, expected: List[Tuple[str, str]]) -> None: +def test_parse_qsl_standard_separator(qs: str, expected: list[tuple[str, str]]) -> None: result = parse_query_string(qs.encode(), "&") assert result == parse_qsl(qs, keep_blank_values=True) == expected @pytest.mark.parametrize( - "qs, expected", + ("qs", "expected"), [ (";", []), (";;", []), (";a=b", [("a", "b")]), ("a=a+b;b=b+c", [("a", "a b"), ("b", "b c")]), ("a=1;a=2", [("a", "1"), ("a", "2")]), - (";", []), - (";;", []), - (";a=b", [("a", "b")]), - ("a=a+b;b=b+c", [("a", "a b"), ("b", "b c")]), - ("a=1;a=2", [("a", "1"), ("a", "2")]), ], ) -def test_parse_qsl_semicolon_separator(qs: str, expected: List[Tuple[str, str]]) -> None: +def test_parse_qsl_semicolon_separator(qs: str, expected: list[tuple[str, str]]) -> None: result = parse_query_string(qs.encode(), ";") assert result == parse_qsl(qs, separator=";", keep_blank_values=True) == expected @pytest.mark.parametrize( "values", - ( + [ (("first", "x@test.com"), ("second", "aaa")), (("first", "&@A.ac"), ("second", "aaa")), (("first", "a@A.ac&"), ("second", "aaa")), (("first", "a@A&.ac"), ("second", "aaa")), - ), + ], ) -def test_query_parsing_of_escaped_values(values: Tuple[Tuple[str, str], Tuple[str, str]]) -> None: +def test_query_parsing_of_escaped_values(values: tuple[tuple[str, str], tuple[str, str]]) -> None: url_encoded = urlencode(values) assert parse_query_string(url_encoded.encode(), "&") == list(values) def test_parses_non_ascii_text() -> None: assert parse_query_string("arabic_text=اختبار اللغة العربية".encode(), "&") == [ - ("arabic_text", "اختبار اللغة العربية") + ("arabic_text", "اختبار اللغة العربية"), ]