Skip to main content

Ultra-fast query string and url-encoded form-data parsers

Project description

Fast Query Parsers

Starlite logo

This library includes ultra-fast Rust based query string and urlencoded parsers. These parsers are used by Starlite, but are developed separately - and can of course be used separately.

Discord Matrix

Installation

pip install fast-query-parsers

Usage

The library exposes two function parse_query_string and parse_url_encoded_dict.

parse_query_string

This function is used to parse a query string into a list of key/value tuples.

from fast_query_parsers import parse_query_string

result = parse_query_string(b"value=1&value=2&type=dollar&country=US", "&")
# [("value", "1"), ("value", "2"), ("type", "dollar"), ("country", "US")]

The first argument to this function is a byte string that includes the query string to be parsed, the second argument is the separator used.

Benchmarks

Query string parsing is more than x5 times faster than the standard library:

stdlib parse_qsl parsing query string: Mean +- std dev: 2.86 us +- 0.03 us
.....................
parse_query_string parsing query string: Mean +- std dev: 916 ns +- 13 ns
.....................
stdlib parse_qsl parsing urlencoded query string: Mean +- std dev: 8.30 us +- 0.10 us
.....................
parse_query_string urlencoded query string: Mean +- std dev: 1.50 us +- 0.03 us

parse_url_encoded_dict

This function is used to parse a url-encoded form data dictionary and parse it into the python equivalent of JSON types.

from urllib.parse import urlencode

from fast_query_parsers import parse_url_encoded_dict

result = parse_url_encoded_dict(
    urlencode(
        [
            ("value", "10"),
            ("value", "12"),
            ("veggies", '["tomato", "potato", "aubergine"]'),
            ("nested", '{"some_key": "some_value"}'),
            ("calories", "122.53"),
            ("healthy", "true"),
            ("polluting", "false"),
            ("json", "null"),
        ]
    ).encode()
)

# result == {
#     "value": [10, 12],
#     "veggies": ["tomato", "potato", "aubergine"],
#     "nested": {"some_key": "some_value"},
#     "calories": 122.53,
#     "healthy": True,
#     "polluting": False,
#     "json": None,
# }

This function handles type conversions correctly - unlike the standard library function parse_qs. Additionally, it does not nest all values inside lists.

Benchmarks

Url Encoded parsing is more than x2 times faster than the standard library, without accounting for parsing of values:

stdlib parse_qs parsing url-encoded values into dict: Mean +- std dev: 8.99 us +- 0.09 us
.....................
parse_url_encoded_dict parse url-encoded values into dict: Mean +- std dev: 3.77 us +- 0.08 us

To actually mimick the parsing done by parse_url_encoded_dict we will need a utility along these lines:

from collections import defaultdict
from contextlib import suppress
from json import loads, JSONDecodeError
from typing import Any, DefaultDict, Dict, List
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)
    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
        decoded_dict[k].append(v)
    return {k: v if len(v) > 1 else v[0] for k, v in decoded_dict.items()}

With the above, the benchmarks looks like so:

python parse_url_encoded_form_data parsing url-encoded values into dict: Mean +- std dev: 19.7 us +- 0.1 us
.....................
parse_url_encoded_dict parsing url-encoded values into dict: Mean +- std dev: 3.69 us +- 0.03 us

Contributing

All contributions are of course welcome!

Repository Setup

  1. Run cargo install to setup the rust dependencies and poetry install to setup the python dependencies.
  2. Install the pre-commit hooks with pre-commit install (requires pre-commit).

Building

Run poetry run maturin develop --release --strip to install a release wheel (without debugging info). This wheel can be used in tests and benchmarks.

Benchmarking

There are basic benchmarks using pyperf in place. To run these execute poetry run python benchrmarks.py.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fast_query_parsers-0.3.0.tar.gz (23.2 kB view hashes)

Uploaded Source

Built Distributions

fast_query_parsers-0.3.0-cp38-abi3-win_amd64.whl (505.5 kB view hashes)

Uploaded CPython 3.8+ Windows x86-64

fast_query_parsers-0.3.0-cp38-abi3-win32.whl (458.3 kB view hashes)

Uploaded CPython 3.8+ Windows x86

fast_query_parsers-0.3.0-cp38-abi3-musllinux_1_2_x86_64.whl (770.7 kB view hashes)

Uploaded CPython 3.8+ musllinux: musl 1.2+ x86-64

fast_query_parsers-0.3.0-cp38-abi3-musllinux_1_2_i686.whl (758.2 kB view hashes)

Uploaded CPython 3.8+ musllinux: musl 1.2+ i686

fast_query_parsers-0.3.0-cp38-abi3-musllinux_1_2_armv7l.whl (772.4 kB view hashes)

Uploaded CPython 3.8+ musllinux: musl 1.2+ ARMv7l

fast_query_parsers-0.3.0-cp38-abi3-musllinux_1_2_aarch64.whl (726.6 kB view hashes)

Uploaded CPython 3.8+ musllinux: musl 1.2+ ARM64

fast_query_parsers-0.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (598.8 kB view hashes)

Uploaded CPython 3.8+ manylinux: glibc 2.17+ x86-64

fast_query_parsers-0.3.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl (684.6 kB view hashes)

Uploaded CPython 3.8+ manylinux: glibc 2.17+ s390x

fast_query_parsers-0.3.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl (764.2 kB view hashes)

Uploaded CPython 3.8+ manylinux: glibc 2.17+ ppc64le

fast_query_parsers-0.3.0-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl (798.7 kB view hashes)

Uploaded CPython 3.8+ manylinux: glibc 2.17+ ppc64

fast_query_parsers-0.3.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (509.0 kB view hashes)

Uploaded CPython 3.8+ manylinux: glibc 2.17+ ARMv7l

fast_query_parsers-0.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (545.7 kB view hashes)

Uploaded CPython 3.8+ manylinux: glibc 2.17+ ARM64

fast_query_parsers-0.3.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl (602.6 kB view hashes)

Uploaded CPython 3.8+ manylinux: glibc 2.5+ i686

fast_query_parsers-0.3.0-cp38-abi3-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl (1.1 MB view hashes)

Uploaded CPython 3.8+ macOS 10.9+ universal2 (ARM64, x86-64) macOS 10.9+ x86-64 macOS 11.0+ ARM64

fast_query_parsers-0.3.0-cp38-abi3-macosx_10_7_x86_64.whl (541.7 kB view hashes)

Uploaded CPython 3.8+ macOS 10.7+ x86-64

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page