https://github.com/techouse/qs_codec
A query string encoding and decoding library for Python. Ported from qs for JavaScript.
https://github.com/techouse/qs_codec
python qs query-encoding query-parser query-string url-parsing url-query
Last synced: 2 months ago
JSON representation
A query string encoding and decoding library for Python. Ported from qs for JavaScript.
- Host: GitHub
- URL: https://github.com/techouse/qs_codec
- Owner: techouse
- License: bsd-3-clause
- Created: 2024-04-25T23:29:55.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-04-20T20:16:59.000Z (2 months ago)
- Last Synced: 2025-04-20T21:24:06.525Z (2 months ago)
- Topics: python, qs, query-encoding, query-parser, query-string, url-parsing, url-query
- Language: Python
- Homepage: https://techouse.github.io/qs_codec/
- Size: 438 KB
- Stars: 4
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.rst
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE
- Code of conduct: CODE-OF-CONDUCT.md
- Codeowners: .github/CODEOWNERS
- Security: SECURITY.md
Awesome Lists containing this project
README
qs-codec
========A query string encoding and decoding library for Python.
Ported from `qs `__ for JavaScript.
|PyPI - Version| |PyPI - Downloads| |PyPI - Status| |PyPI - Python Version| |PyPI - Format| |Black|
|Test| |CodeQL| |Publish| |Docs| |codecov| |Codacy| |Black| |flake8| |mypy| |pylint| |isort| |bandit|
|License| |Contributor Covenant| |GitHub Sponsors| |GitHub Repo stars|Usage
-----A simple usage example:
.. code:: python
import qs_codec as qs
# Encoding
assert qs.encode({'a': 'b'}) == 'a=b'# Decoding
assert qs.decode('a=b') == {'a': 'b'}Decoding
~~~~~~~~dictionaries
^^^^^^^^^^^^.. code:: python
import qs_codec as qs
import typing as tdef decode(
value: t.Optional[t.Union[str, t.Dict[str, t.Any]]],
options: qs.DecodeOptions = qs.DecodeOptions(),
) -> t.Dict[str, t.Any]:
"""Decodes a query string into a Dict[str, Any].
Providing custom DecodeOptions will override the default behavior."""
pass`decode `__ allows you to create nested ``dict``\ s within your query
strings, by surrounding the name of sub-keys with square brackets
``[]``. For example, the string ``'foo[bar]=baz'`` converts to:.. code:: python
import qs_codec as qs
assert qs.decode('foo[bar]=baz') == {'foo': {'bar': 'baz'}}
URI encoded strings work too:
.. code:: python
import qs_codec as qs
assert qs.decode('a%5Bb%5D=c') == {'a': {'b': 'c'}}
You can also nest your ``dict``\ s, like ``'foo[bar][baz]=foobarbaz'``:
.. code:: python
import qs_codec as qs
assert qs.decode('foo[bar][baz]=foobarbaz') == {'foo': {'bar': {'baz': 'foobarbaz'}}}
By default, when nesting ``dict``\ s qs will only decode up to 5
children deep. This means if you attempt to decode a string like
``'a[b][c][d][e][f][g][h][i]=j'`` your resulting ``dict`` will be:.. code:: python
import qs_codec as qs
assert qs.decode("a[b][c][d][e][f][g][h][i]=j") == {
"a": {"b": {"c": {"d": {"e": {"f": {"[g][h][i]": "j"}}}}}}
}This depth can be overridden by setting the `depth `_:
.. code:: python
import qs_codec as qs
assert qs.decode(
'a[b][c][d][e][f][g][h][i]=j',
qs.DecodeOptions(depth=1),
) == {'a': {'b': {'[c][d][e][f][g][h][i]': 'j'}}}You can configure `decode `__ to throw an error
when parsing nested input beyond this depth using `strict_depth `__ (defaults to ``False``):.. code:: python
import qs_codec as qs
try:
qs.decode(
'a[b][c][d][e][f][g][h][i]=j',
qs.DecodeOptions(depth=1, strict_depth=True),
)
except IndexError as e:
assert str(e) == 'Input depth exceeded depth option of 1 and strict_depth is True'The depth limit helps mitigate abuse when `decode `__ is used to parse user
input, and it is recommended to keep it a reasonably small number. `strict_depth `__
adds a layer of protection by throwing a ``IndexError`` when the limit is exceeded, allowing you to catch and handle such cases.For similar reasons, by default `decode `__ will only parse up to 1000 parameters. This can be overridden by passing a
`parameter_limit `__ option:.. code:: python
import qs_codec as qs
assert qs.decode(
'a=b&c=d',
qs.DecodeOptions(parameter_limit=1),
) == {'a': 'b'}To bypass the leading question mark, use `ignore_query_prefix `__:
.. code:: python
import qs_codec as qs
assert qs.decode(
'?a=b&c=d',
qs.DecodeOptions(ignore_query_prefix=True),
) == {'a': 'b', 'c': 'd'}An optional `delimiter `__ can also be passed:
.. code:: python
import qs_codec as qs
assert qs.decode(
'a=b;c=d',
qs.DecodeOptions(delimiter=';'),
) == {'a': 'b', 'c': 'd'}`delimiter `__ can be a regular expression too:
.. code:: python
import qs_codec as qs
import reassert qs.decode(
'a=b;c=d',
qs.DecodeOptions(delimiter=re.compile(r'[;,]')),
) == {'a': 'b', 'c': 'd'}Option `allow_dots `__
can be used to enable dot notation:.. code:: python
import qs_codec as qs
assert qs.decode(
'a.b=c',
qs.DecodeOptions(allow_dots=True),
) == {'a': {'b': 'c'}}Option `decode_dot_in_keys `__
can be used to decode dots in keys.**Note:** it implies `allow_dots `__, so
`decode `__ will error if you set `decode_dot_in_keys `__
to ``True``, and `allow_dots `__ to ``False``... code:: python
import qs_codec as qs
assert qs.decode(
'name%252Eobj.first=John&name%252Eobj.last=Doe',
qs.DecodeOptions(decode_dot_in_keys=True),
) == {'name.obj': {'first': 'John', 'last': 'Doe'}}Option `allow_empty_lists `__ can
be used to allowing empty ``list`` values in a ``dict``.. code:: python
import qs_codec as qs
assert qs.decode(
'foo[]&bar=baz',
qs.DecodeOptions(allow_empty_lists=True),
) == {'foo': [], 'bar': 'baz'}Option `duplicates `__ can be used to
change the behavior when duplicate keys are encountered.. code:: python
import qs_codec as qs
assert qs.decode('foo=bar&foo=baz') == {'foo': ['bar', 'baz']}
assert qs.decode(
'foo=bar&foo=baz',
qs.DecodeOptions(duplicates=qs.Duplicates.COMBINE),
) == {'foo': ['bar', 'baz']}assert qs.decode(
'foo=bar&foo=baz',
qs.DecodeOptions(duplicates=qs.Duplicates.FIRST),
) == {'foo': 'bar'}assert qs.decode(
'foo=bar&foo=baz',
qs.DecodeOptions(duplicates=qs.Duplicates.LAST),
) == {'foo': 'baz'}If you have to deal with legacy browsers or services, there’s also
support for decoding percent-encoded octets as `LATIN1 `__:.. code:: python
import qs_codec as qs
assert qs.decode(
'a=%A7',
qs.DecodeOptions(charset=qs.Charset.LATIN1),
) == {'a': '§'}Some services add an initial ``utf8=✓`` value to forms so that old
Internet Explorer versions are more likely to submit the form as utf-8.
Additionally, the server can check the value against wrong encodings of
the checkmark character and detect that a query string or
``application/x-www-form-urlencoded`` body was *not* sent as ``utf-8``,
e.g. if the form had an ``accept-charset`` parameter or the containing
page had a different character set.`decode `__ supports this mechanism via the
`charset_sentinel `__ option.
If specified, the ``utf8`` parameter will be omitted from the returned
``dict``. It will be used to switch to `LATIN1 `__ or
`UTF8 `__ mode depending on how the checkmark is encoded.**Important**: When you specify both the `charset `__
option and the `charset_sentinel `__ option, the
`charset `__ will be overridden when the request contains a
``utf8`` parameter from which the actual charset can be deduced. In that
sense the `charset `__ will behave as the default charset
rather than the authoritative charset... code:: python
import qs_codec as qs
assert qs.decode(
'utf8=%E2%9C%93&a=%C3%B8',
qs.DecodeOptions(
charset=qs.Charset.LATIN1,
charset_sentinel=True,
),
) == {'a': 'ø'}assert qs.decode(
'utf8=%26%2310003%3B&a=%F8',
qs.DecodeOptions(
charset=qs.Charset.UTF8,
charset_sentinel=True,
),
) == {'a': 'ø'}If you want to decode the `...; `__ syntax to the actual character, you can specify the
`interpret_numeric_entities `__
option as well:.. code:: python
import qs_codec qs qs
assert qs.decode(
'a=%26%239786%3B',
qs.DecodeOptions(
charset=qs.Charset.LATIN1,
interpret_numeric_entities=True,
),
) == {'a': '☺'}It also works when the charset has been detected in
`charset_sentinel `__ mode.lists
^^^^^`decode `__ can also decode ``list``\ s using a similar ``[]`` notation:
.. code:: python
import qs_codec as qs
assert qs.decode('a[]=b&a[]=c') == {'a': ['b', 'c']}
You may specify an index as well:
.. code:: python
import qs_codec as qs
assert qs.decode('a[1]=c&a[0]=b') == {'a': ['b', 'c']}
Note that the only difference between an index in a ``list`` and a key
in a ``dict`` is that the value between the brackets must be a number to
create a ``list``. When creating ``list``\ s with specific indices,
`decode `__ will compact a sparse ``list`` to
only the existing values preserving their order:.. code:: python
import qs_codec as qs
assert qs.decode('a[1]=b&a[15]=c') == {'a': ['b', 'c']}
Note that an empty ``str``\ing is also a value, and will be preserved:
.. code:: python
import qs_codec as qs
assert qs.decode('a[]=&a[]=b') == {'a': ['', 'b']}
assert qs.decode('a[0]=b&a[1]=&a[2]=c') == {'a': ['b', '', 'c']}
`decode `__ will also limit specifying indices
in a ``list`` to a maximum index of ``20``. Any ``list`` members with an
index of greater than ``20`` will instead be converted to a ``dict`` with
the index as the key. This is needed to handle cases when someone sent,
for example, ``a[999999999]`` and it will take significant time to iterate
over this huge ``list``... code:: python
import qs_codec as qs
assert qs.decode('a[100]=b') == {'a': {'100': 'b'}}
This limit can be overridden by passing an `list_limit `__
option:.. code:: python
import qs_codec as qs
assert qs.decode(
'a[1]=b',
qs.DecodeOptions(list_limit=0),
) == {'a': {'1': 'b'}}To disable ``list`` parsing entirely, set `parse_lists `__
to ``False``... code:: python
import qs_codec as qs
assert qs.decode(
'a[]=b',
qs.DecodeOptions(parse_lists=False),
) == {'a': {'0': 'b'}}If you mix notations, `decode `__ will merge the two items into a ``dict``:
.. code:: python
import qs_codec as qs
assert qs.decode('a[0]=b&a[b]=c') == {'a': {'0': 'b', 'b': 'c'}}
You can also create ``list``\ s of ``dict``\ s:
.. code:: python
import qs_codec as qs
assert qs.decode('a[][b]=c') == {'a': [{'b': 'c'}]}
(`decode `__ *cannot convert nested ``dict``\ s, such as ``'a={b:1},{c:d}'``*)
primitive values (``int``, ``bool``, ``None``, etc.)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^By default, all values are parsed as ``str``\ings.
.. code:: python
import qs_codec as qs
assert qs.decode(
'a=15&b=true&c=null',
) == {'a': '15', 'b': 'true', 'c': 'null'}Encoding
~~~~~~~~.. code:: python
import qs_codec as qs
import typing as tdef encode(
value: t.Any,
options: qs.EncodeOptions = qs.EncodeOptions()
) -> str:
"""Encodes an object into a query string.
Providing custom EncodeOptions will override the default behavior."""
passWhen encoding, `encode `__ by default URI encodes output. ``dict``\ s are
encoded as you would expect:.. code:: python
import qs_codec as qs
assert qs.encode({'a': 'b'}) == 'a=b'
assert qs.encode({'a': {'b': 'c'}}) == 'a%5Bb%5D=c'This encoding can be disabled by setting the `encode `__
option to ``False``:.. code:: python
import qs_codec as qs
assert qs.encode(
{'a': {'b': 'c'}},
qs.EncodeOptions(encode=False),
) == 'a[b]=c'Encoding can be disabled for keys by setting the
`encode_values_only `__ option to ``True``:.. code:: python
import qs_codec as qs
assert qs.encode(
{
'a': 'b',
'c': ['d', 'e=f'],
'f': [
['g'],
['h']
]
},
qs.EncodeOptions(encode_values_only=True)
) == 'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h'This encoding can also be replaced by a custom ``Callable`` in the
`encoder `__ option:.. code:: python
import qs_codec as qs
import typing as tdef custom_encoder(
value: str,
charset: t.Optional[qs.Charset],
format: t.Optional[qs.Format],
) -> str:
if value == 'č':
return 'c'
return valueassert qs.encode(
{'a': {'b': 'č'}},
qs.EncodeOptions(encoder=custom_encoder),
) == 'a[b]=c'(Note: the `encoder `__ option does not apply if
`encode `__ is ``False``).Similar to `encoder `__ there is a
`decoder `__ option for `decode `__
to override decoding of properties and values:.. code:: python
import qs_codec as qs,
typing as tdef custom_decoder(
value: t.Any,
charset: t.Optional[qs.Charset],
) -> t.Union[int, str]:
try:
return int(value)
except ValueError:
return valueassert qs.decode(
'foo=123',
qs.DecodeOptions(decoder=custom_decoder),
) == {'foo': 123}Examples beyond this point will be shown as though the output is not URI
encoded for clarity. Please note that the return values in these cases
*will* be URI encoded during real usage.When ``list``\s are encoded, they follow the
`list_format `__ option, which defaults to
`INDICES `__:.. code:: python
import qs_codec as qs
assert qs.encode(
{'a': ['b', 'c', 'd']},
qs.EncodeOptions(encode=False)
) == 'a[0]=b&a[1]=c&a[2]=d'You may override this by setting the `indices `__ option to
``False``, or to be more explicit, the `list_format `__
option to `REPEAT `__:.. code:: python
import qs_codec as qs
assert qs.encode(
{'a': ['b', 'c', 'd']},
qs.EncodeOptions(
encode=False,
indices=False,
),
) == 'a=b&a=c&a=d'You may use the `list_format `__ option to specify the
format of the output ``list``:.. code:: python
import qs_codec as qs
# ListFormat.INDICES
assert qs.encode(
{'a': ['b', 'c']},
qs.EncodeOptions(
encode=False,
list_format=qs.ListFormat.INDICES,
),
) == 'a[0]=b&a[1]=c'# ListFormat.BRACKETS
assert qs.encode(
{'a': ['b', 'c']},
qs.EncodeOptions(
encode=False,
list_format=qs.ListFormat.BRACKETS,
),
) == 'a[]=b&a[]=c'# ListFormat.REPEAT
assert qs.encode(
{'a': ['b', 'c']},
qs.EncodeOptions(
encode=False,
list_format=qs.ListFormat.REPEAT,
),
) == 'a=b&a=c'# ListFormat.COMMA
assert qs.encode(
{'a': ['b', 'c']},
qs.EncodeOptions(
encode=False,
list_format=qs.ListFormat.COMMA,
),
) == 'a=b,c'**Note:** When using `list_format `__ set to
`COMMA `_, you can also pass the
`comma_round_trip `__ option set to ``True`` or
``False``, to append ``[]`` on single-item ``list``\ s, so that they can round trip through a decoding.`BRACKETS `__ notation is used for encoding ``dict``\s by default:
.. code:: python
import qs_codec as qs
assert qs.encode(
{'a': {'b': {'c': 'd', 'e': 'f'}}},
qs.EncodeOptions(encode=False),
) == 'a[b][c]=d&a[b][e]=f'You may override this to use dot notation by setting the
`allow_dots `__ option to ``True``:.. code:: python
import qs_codec as qs
assert qs.encode(
{'a': {'b': {'c': 'd', 'e': 'f'}}},
qs.EncodeOptions(encode=False, allow_dots=True),
) == 'a.b.c=d&a.b.e=f'You may encode dots in keys of ``dict``\s by setting
`encode_dot_in_keys `__ to ``True``:.. code:: python
import qs_codec as qs
assert qs.encode(
{'name.obj': {'first': 'John', 'last': 'Doe'}},
qs.EncodeOptions(
allow_dots=True,
encode_dot_in_keys=True,
),
) == 'name%252Eobj.first=John&name%252Eobj.last=Doe'**Caveat:** When both `encode_values_only `__
and `encode_dot_in_keys `__ are set to
``True``, only dots in keys and nothing else will be encoded!You may allow empty ``list`` values by setting the
`allow_empty_lists `__ option to ``True``:.. code:: python
import qs_codec as qs
assert qs.encode(
{'foo': [], 'bar': 'baz', },
qs.EncodeOptions(
encode=False,
allow_empty_lists=True,
),
) == 'foo[]&bar=baz'Empty ``str``\ings and ``None`` values will be omitted, but the equals sign (``=``) remains in place:
.. code:: python
import qs_codec as qs
assert qs.encode({'a': ''}) == 'a='
Keys with no values (such as an empty ``dict`` or ``list``) will return nothing:
.. code:: python
import qs_codec as qs
assert qs.encode({'a': []}) == ''
assert qs.encode({'a': {}}) == ''
assert qs.encode({'a': [{}]}) == ''
assert qs.encode({'a': {'b': []}}) == ''
assert qs.encode({'a': {'b': {}}}) == ''
`Undefined `__ properties will be omitted entirely:
.. code:: python
import qs_codec as qs
assert qs.encode({'a': None, 'b': qs.Undefined()}) == 'a='
The query string may optionally be prepended with a question mark (``?``) by setting
`add_query_prefix `__ to ``True``:.. code:: python
import qs_codec as qs
assert qs.encode(
{'a': 'b', 'c': 'd'},
qs.EncodeOptions(add_query_prefix=True),
) == '?a=b&c=d'The `delimiter `__ may be overridden as well:
.. code:: python
import qs_codec as qs
assert qs.encode(
{'a': 'b', 'c': 'd', },
qs.EncodeOptions(delimiter=';')
) == 'a=b;c=d'If you only want to override the serialization of `datetime `__
objects, you can provide a ``Callable`` in the
`serialize_date `__ option:.. code:: python
import qs_codec as qs
import datetime
import sys# First case: encoding a datetime object to an ISO 8601 string
assert (
qs.encode(
{
"a": (
datetime.datetime.fromtimestamp(7, datetime.UTC)
if sys.version_info.major == 3 and sys.version_info.minor >= 11
else datetime.datetime.utcfromtimestamp(7)
)
},
qs.EncodeOptions(encode=False),
)
== "a=1970-01-01T00:00:07+00:00"
if sys.version_info.major == 3 and sys.version_info.minor >= 11
else "a=1970-01-01T00:00:07"
)# Second case: encoding a datetime object to a timestamp string
assert (
qs.encode(
{
"a": (
datetime.datetime.fromtimestamp(7, datetime.UTC)
if sys.version_info.major == 3 and sys.version_info.minor >= 11
else datetime.datetime.utcfromtimestamp(7)
)
},
qs.EncodeOptions(encode=False, serialize_date=lambda date: str(int(date.timestamp()))),
)
== "a=7"
)To affect the order of parameter keys, you can set a ``Callable`` in the
`sort `__ option:.. code:: python
import qs_codec as qs
assert qs.encode(
{'a': 'c', 'z': 'y', 'b': 'f'},
qs.EncodeOptions(
encode=False,
sort=lambda a, b: (a > b) - (a < b)
)
) == 'a=c&b=f&z=y'Finally, you can use the `filter `__ option to restrict
which keys will be included in the encoded output. If you pass a ``Callable``, it will be called for each key to obtain
the replacement value. Otherwise, if you pass a ``list``, it will be used to select properties and ``list`` indices to
be encoded:.. code:: python
import qs_codec as qs
import datetime
import sys# First case: using a Callable as filter
assert (
qs.encode(
{
"a": "b",
"c": "d",
"e": {
"f": (
datetime.datetime.fromtimestamp(123, datetime.UTC)
if sys.version_info.major == 3 and sys.version_info.minor >= 11
else datetime.datetime.utcfromtimestamp(123)
),
"g": [2],
},
},
qs.EncodeOptions(
encode=False,
filter=lambda prefix, value: {
"b": None,
"e[f]": int(value.timestamp()) if isinstance(value, datetime.datetime) else value,
"e[g][0]": value * 2 if isinstance(value, int) else value,
}.get(prefix, value),
),
)
== "a=b&c=d&e[f]=123&e[g][0]=4"
)# Second case: using a list as filter
assert qs.encode(
{'a': 'b', 'c': 'd', 'e': 'f'},
qs.EncodeOptions(
encode=False,
filter=['a', 'e']
)
) == 'a=b&e=f'# Third case: using a list as filter with indices
assert qs.encode(
{
'a': ['b', 'c', 'd'],
'e': 'f',
},
qs.EncodeOptions(
encode=False,
filter=['a', 0, 2]
)
) == 'a[0]=b&a[2]=d'Handling ``None`` values
~~~~~~~~~~~~~~~~~~~~~~~~~~~By default, ``None`` values are treated like empty ``str``\ings:
.. code:: python
import qs_codec as qs
assert qs.encode({'a': None, 'b': ''}) == 'a=&b='
To distinguish between ``None`` values and empty ``str``\s use the
`strict_null_handling `__ flag.
In the result string the ``None`` values have no ``=`` sign:.. code:: python
import qs_codec as qs
assert qs.encode(
{'a': None, 'b': ''},
qs.EncodeOptions(strict_null_handling=True),
) == 'a&b='To decode values without ``=`` back to ``None`` use the
`strict_null_handling `__ flag:.. code:: python
import qs_codec as qs
assert qs.decode(
'a&b=',
qs.DecodeOptions(strict_null_handling=True),
) == {'a': None, 'b': ''}To completely skip rendering keys with ``None`` values, use the
`skip_nulls `__ flag:.. code:: python
import qs_codec as qs
assert qs.encode(
{'a': 'b', 'c': None},
qs.EncodeOptions(skip_nulls=True),
) == 'a=b'If you’re communicating with legacy systems, you can switch to
`LATIN1 `__ using the
`charset `__ option:.. code:: python
import qs_codec as qs
assert qs.encode(
{'æ': 'æ'},
qs.EncodeOptions(charset=qs.Charset.LATIN1)
) == '%E6=%E6'Characters that don’t exist in `LATIN1 `__
will be converted to numeric entities, similar to what browsers do:.. code:: python
import qs_codec as qs
assert qs.encode(
{'a': '☺'},
qs.EncodeOptions(charset=qs.Charset.LATIN1)
) == 'a=%26%239786%3B'You can use the `charset_sentinel `__
option to announce the character by including an ``utf8=✓`` parameter with the proper
encoding of the checkmark, similar to what Ruby on Rails and others do when submitting forms... code:: python
import qs_codec as qs
assert qs.encode(
{'a': '☺'},
qs.EncodeOptions(charset_sentinel=True)
) == 'utf8=%E2%9C%93&a=%E2%98%BA'assert qs.encode(
{'a': 'æ'},
qs.EncodeOptions(charset=qs.Charset.LATIN1, charset_sentinel=True)
) == 'utf8=%26%2310003%3B&a=%E6'Dealing with special character sets
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~By default, the encoding and decoding of characters is done in
`UTF8 `__, and
`LATIN1 `__ support is also built in via
the `charset `__
and `charset `__ parameter,
respectively.If you wish to encode query strings to a different character set (i.e.
`Shift JIS `__).. code:: python
import qs_codec as qs
import codecs
import typing as tdef custom_encoder(
string: str,
charset: t.Optional[qs.Charset],
format: t.Optional[qs.Format],
) -> str:
if string:
buf: bytes = codecs.encode(string, 'shift_jis')
result: t.List[str] = ['{:02x}'.format(b) for b in buf]
return '%' + '%'.join(result)
return ''assert qs.encode(
{'a': 'こんにちは!'},
qs.EncodeOptions(encoder=custom_encoder)
) == '%61=%82%b1%82%f1%82%c9%82%bf%82%cd%81%49'This also works for decoding of query strings:
.. code:: python
import qs_codec as qs
import re
import codecs
import typing as tdef custom_decoder(
string: str,
charset: t.Optional[qs.Charset],
) -> t.Optional[str]:
if string:
result: t.List[int] = []
while string:
match: t.Optional[t.Match[str]] = re.search(r'%([0-9A-F]{2})', string, re.IGNORECASE)
if match:
result.append(int(match.group(1), 16))
string = string[match.end():]
else:
break
buf: bytes = bytes(result)
return codecs.decode(buf, 'shift_jis')
return Noneassert qs.decode(
'%61=%82%b1%82%f1%82%c9%82%bf%82%cd%81%49',
qs.DecodeOptions(decoder=custom_decoder)
) == {'a': 'こんにちは!'}RFC 3986 and RFC 1738 space encoding
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~The default `format `__ is
`RFC3986 `__ which encodes
``' '`` to ``%20`` which is backward compatible. You can also set the
`format `__ to
`RFC1738 `__ which encodes ``' '`` to ``+``... code:: python
import qs_codec as qs
assert qs.encode(
{'a': 'b c'},
qs.EncodeOptions(format=qs.Format.RFC3986)
) == 'a=b%20c'assert qs.encode(
{'a': 'b c'},
qs.EncodeOptions(format=qs.Format.RFC3986)
) == 'a=b%20c'assert qs.encode(
{'a': 'b c'},
qs.EncodeOptions(format=qs.Format.RFC1738)
) == 'a=b+c'--------------
Special thanks to the authors of
`qs `__ for JavaScript: - `Jordan
Harband `__ - `TJ
Holowaychuk `__.. |PyPI - Version| image:: https://img.shields.io/pypi/v/qs_codec
:target: https://pypi.org/project/qs-codec/
.. |PyPI - Downloads| image:: https://img.shields.io/pypi/dm/qs_codec
:target: https://pypistats.org/packages/qs-codec
.. |PyPI - Status| image:: https://img.shields.io/pypi/status/qs_codec
.. |PyPI - Python Version| image:: https://img.shields.io/pypi/pyversions/qs_codec
.. |PyPI - Format| image:: https://img.shields.io/pypi/format/qs_codec
.. |Test| image:: https://github.com/techouse/qs_codec/actions/workflows/test.yml/badge.svg
:target: https://github.com/techouse/qs_codec/actions/workflows/test.yml
.. |CodeQL| image:: https://github.com/techouse/qs_codec/actions/workflows/github-code-scanning/codeql/badge.svg
:target: https://github.com/techouse/qs_codec/actions/workflows/github-code-scanning/codeql
.. |Publish| image:: https://github.com/techouse/qs_codec/actions/workflows/publish.yml/badge.svg
:target: https://github.com/techouse/qs_codec/actions/workflows/publish.yml
.. |Docs| image:: https://github.com/techouse/qs_codec/actions/workflows/docs.yml/badge.svg
:target: https://github.com/techouse/qs_codec/actions/workflows/docs.yml
.. |Black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/psf/black
.. |codecov| image:: https://codecov.io/gh/techouse/qs_codec/graph/badge.svg?token=Vp0z05yj2l
:target: https://codecov.io/gh/techouse/qs_codec
.. |Codacy| image:: https://app.codacy.com/project/badge/Grade/7ead208221ae4f6785631043064647e4
:target: https://app.codacy.com/gh/techouse/qs_codec/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade
.. |License| image:: https://img.shields.io/github/license/techouse/qs_codec
:target: LICENSE
.. |GitHub Sponsors| image:: https://img.shields.io/github/sponsors/techouse
:target: https://github.com/sponsors/techouse
.. |GitHub Repo stars| image:: https://img.shields.io/github/stars/techouse/qs_codec
:target: https://github.com/techouse/qs_codec/stargazers
.. |Contributor Covenant| image:: https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg
:target: CODE-OF-CONDUCT.md
.. |flake8| image:: https://img.shields.io/badge/flake8-checked-blueviolet.svg
:target: https://flake8.pycqa.org/en/latest/
.. |mypy| image:: https://img.shields.io/badge/mypy-checked-blue.svg
:target: https://mypy.readthedocs.io/en/stable/
.. |pylint| image:: https://img.shields.io/badge/linting-pylint-yellowgreen.svg
:target: https://github.com/pylint-dev/pylint
.. |isort| image:: https://img.shields.io/badge/imports-isort-blue.svg
:target: https://pycqa.github.io/isort/
.. |bandit| image:: https://img.shields.io/badge/security-bandit-blue.svg
:target: https://github.com/PyCQA/bandit
:alt: Security Status