qstion


Nameqstion JSON
Version 1.0.1 PyPI version JSON
download
home_page
SummarySimple package for parsing querystrings into nested dictionaries and vice versa.
upload_time2024-03-06 10:18:51
maintainer
docs_urlNone
author
requires_python>=3.11
licenseBSD-3-Clause
keywords querystring query string url parse serialize deserialize nested dict dictionary qs qstion
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # qstion

A querystring parsing and stringifying library with some added security. 
Library was based on [this](https://www.npmjs.com/package/qs?activeTab=readme) js library.

## Usage 

```python
import qstion as qs

x = qs.parse('a=c')
assert x == {'a': 'c'}

x_str = qs.stringify(x)
assert x_str == 'a=c'
```

### Parsing strings

**qstion** (as well as **qs** in js) allows you to parse string into nested objects.
    
```python
import qstion as qs

assert qs.parse('a[b][c]=1') == {'a': {'b': {'c': '1'}}}
```
There is no support for plain object options or prototype pollution.

Uri encoded strings are supported
    
```python
import qstion as qs

assert qs.parse('a%5Bb%5D=c') == {'a': { 'b': 'c' }}
```

Following options are supported:
1. `from_url` : **bool** - if `True` then `parse` will parse querystring from url using `urlparse` from `urllib.parse` module, default: `False`
```python
import qstion as qs

assert qs.parse('http://localhost:8080/?a=c', from_url=True) == {'a': 'c'}

assert qs.parse('a=c', from_url=False) == {'a': 'c'}
```
2. `delimiter` : **str**|**re.Pattern** - delimiter for parsing, default: `&`
```python
import qstion as qs

assert qs.parse('a=c&b=d', delimiter='&') == {'a': 'c', 'b': 'd'}

assert qs.parse('a=c;b=d,c=d', delimiter='[;,]') == {'a': 'c', 'b': 'd', 'c': 'd'}
```
3. `depth` : **int** - maximum depth for parsing, default: `5`
```python
import qstion as qs

assert qs.parse('a[b][c][d][e][f][g][h][i]=j', depth=5) == {'a': {'b': {'c': {'d': {'e': {'f': {'[g][h][i]': 'j'}}}}}}}

assert qs.parse('a[b][c][d][e][f][g][h][i]=j', depth=1) == {'a': {'b': {'[c][d][e][f][g][h][i]': 'j'}}}
```

4. `parameter_limit` : **int** - maximum number of parameters to parse, default: `1000`
```python
import qstion as qs

assert qs.parse('a=b&c=d&e=f&g=h&i=j&k=l&m=n&o=p&q=r&s=t&u=v&w=x&y=z', parameter_limit=5) == {'a': 'b', 'c': 'd', 'e': 'f', 'g': 'h', 'i': 'j'}
```

5. `allow_dots` : **bool** - if `True` then dots in keys will be parsed as nested objects, default: `False`
```python
import qstion as qs

assert qs.parse('a.b=c', allow_dots=True) == {'a': {'b': 'c'}}
```
6. `parse_arrays`: **bool** - if `True` then arrays will be parsed, default: `False`
```python
import qstion as qs

assert qs.parse('a[]=b&a[]=c', parse_arrays=True) == {'a': {0: 'b', 1: 'c'}}

assert qs.parse('a[0]=b&a[1]=c', parse_arrays=False) == {'a': {'0': 'b', '1': 'c'}}
```

7. `array_limit` : **int** - maximum number of elements in array to keep array notation (only used with combination of argument `parse_arrays`), default: `20`
```python
import qstion as qs

assert qs.parse('a[]=b&a[]=c&', parse_arrays=True) == {'a': {0: 'b', 1: 'c'}}

assert qs.parse('a[0]=b&a[1]=c&', array_limit=1, parse_arrays=True) == {'a': {'0': 'b', '1': 'c'}}
```

8. `allow_empty` : **bool** - if `True` then empty values and keys are accepted, default: `False`
```python
import qstion as qs

assert qs.parse('a=&b=', allow_empty=True) == {'a': '', 'b': ''}

assert qs.parse('a[]=&b[]=', allow_empty=True) == {'a': {'': ''}, 'b': {'': ''}}
```

9. `charset` : **str** - charset to use when decoding uri encoded strings, default: `utf-8`
```python
import qstion as qs

assert qs.parse('a=%A7', charset='iso-8859-1') == {'a': '§'}

assert qs.parse('a=%C2%A7', charset='utf-8') == {'a': '§'}
```

10. `charset_sentinel` : **bool** - if `True` then, if `utf8=✓` arg is included in querystring, then charset will be deduced based on encoding of '✓' character (recognizes only `utf8` and `iso-8859-1`), default: `False`
```python
import qstion as qs

assert qs.parse('a=%C2%A7&utf8=%E2%9C%93',charset='iso-8859-1', charset_sentinel=True) == {'a': '§'}

assert qs.parse('a=%A7&utf8=%26%2310003', charset='utf-8', charset_sentinel=True) == {'a': '§'}
```

11. `interpret_numeric_entities` : **bool** - if `True` then numeric entities will be interpreted as unicode characters, default: `False`
```python
import qstion as qs

assert qs.parse('a=%26%2310003',charset='iso-8859-1', interpret_numeric_entities=True) == {'a': '✓'}
```

12. `parse_primitive`: **bool** - if `True` then primitive values will be parsed in their appearing types, default: `False`
```python
import qstion as qs

assert qs.parse('a=1&b=2&c=3', parse_primitive=True) == {'a': 1, 'b': 2, 'c': 3}

assert qs.parse('a=true&b=false&c=null', parse_primitive=True) == {'a': True, 'b': False, 'c': None}
```

13. `primitive_strict`: **bool** - if `True` then primitive values of `bool` and `NoneType` will be parsed to reserved strict keywords (used only if `parse_primitive` is `True`), default: `True`
```python
import qstion as qs

assert qs.parse('a=true&b=false&c=null&d=None', parse_primitive=True, primitive_strict=True) == {'a': True, 'b': False, 'c': None, 'd': 'None'}

assert qs.parse('a=True&b=False&c=NULL', parse_primitive=True, primitive_strict=True) == {'a': 'True', 'b': 'False', 'c': 'NULL'}

assert qs.parse('a=True&b=False&c=NULL', parse_primitive=True, primitive_strict=False) == {'a': True, 'b': False, 'c': None}
```

14. `comma`: **bool** - if `True`, then coma separated values will be parsed as multiple separate values instead of string, default: `False`
```python
import qstion as qs

assert qs.parse('a=1,2,3', comma=True, parse_primitive=True) == {'a': [1, 2, 3]}
```

### Stringifying objects

**qstion** (as well as **qs** in js) allows you to stringify objects into querystring.
    
```python
import qstion as qs

assert qs.stringify({'a': 'b'}) == 'a=b'
```

Following options are supported:
1. `allow_dots` : **bool** - if `True` then nested keys will be stringified using dot notation instead of brackets, default: `False`
```python
import qstion as qs

assert qs.stringify({'a': {'b': 'c'}}, allow_dots=True) == 'a.b=c'
```

2. `encode` : **bool** - if `True` then keys and values will be uri encoded (with default `charset`), default: `True`
```python
import qstion as qs

assert qs.stringify({'a[b]': 'b'}, encode=False) == 'a[b]=b'

assert qs.stringify({'a[b]': 'b'}, encode=True) == 'a%5Bb%5D=b'
```

3. `charset` : **str** - charset to use when encoding uri strings, default: `utf-8` (if `encode` is `True`), note that un-encodable characters will be encoded using their xml numeric entities
```python
import qstion as qs

assert qs.stringify({'a': '§'}, charset='iso-8859-1') == 'a=%A7'

assert qs.stringify({'a': '☺'}, charset='iso-8859-1') == 'a=%26%2312850'
```

4. `charset_sentinel` : **bool** - if `True` then, `utf8=✓` will be added to querystring to indicate that charset based on encoding of '✓' character (recognizes only `utf8` and `iso-8859-1`), default: `False`
```python
import qstion as qs

assert qs.stringify({'a': '§'}, charset='iso-8859-1', charset_sentinel=True) == 'a=%A7&utf8=%E2%9C%93'
```


5. `delimiter` : **str** - delimiter for stringifying, default: `&`
```python
import qstion as qs

assert qs.stringify({'a': 'b', 'c': 'd'}, delimiter='&') == 'a=b&c=d'

assert qs.stringify({'a': 'b', 'c': 'd'}, delimiter=';') == 'a=b;c=d'
``` 

6. `encode_values_only` : **bool** - if `True` then only values will be encoded, default: `False` (this option is overridden when `encode` is `True`)
```python

import qstion as qs

assert qs.stringify({'a': {'b': '☺'}}, encode_values_only=True, charset='iso-8859-1') == 'a[b]=%26%2312850'
```

7. `array_format` : **str** - format for array notation, options: 'brackets','indices', 'repeat', 'comma', default: 'indices'
```python
import qstion as qs

assert qs.stringify({'a': {1: 'b', 2: 'c'}}, array_format='brackets') == 'a[]=b&a[]=c'

assert qs.stringify({'a': {1: 'b', 2: 'c'}}, array_format='indices') == 'a[1]=b&a[2]=c'

assert qs.stringify({'a': {1: 'b', 2: 'c'}}, array_format='repeat') == 'a=b&a=c'

assert qs.stringify({'a': {1: 'b', 2: 'c'}}, array_format='comma') == 'a=b,c'
```

8. `sort` : **bool** - if `True` then keys will be sorted alphabetically, default: `False`
```python
import qstion as qs

assert qs.stringify({'x': 'y', 'a': 'b'}, sort=True) == 'a=b&x=y'
```

9. `sort_reverse` : **bool** - if `True` then keys will be sorted (if `sort` is `True`) in reverse order, default: `False`
```python
import qstion as qs

assert qs.stringify({'x': 'y', 'a': 'b'}, sort=True, sort_reverse=True) == 'x=y&a=b'
```

10. `filter` : **list[str]** - list of keys to filter, default: `None`
```python
import qstion as qs

assert qs.stringify({'a': 'b', 'c': 'd'}, filter=['a']) == 'a=b'
```

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "qstion",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.11",
    "maintainer_email": "",
    "keywords": "querystring,query,string,url,parse,serialize,deserialize,nested,dict,dictionary,qs,qstion",
    "author": "",
    "author_email": "Marek Nemeth <99m.nemeth@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/ec/8e/19a9bfd458613851fbd14ee2c044f300c8498b7a5da4411d3753e74554f0/qstion-1.0.1.tar.gz",
    "platform": null,
    "description": "# qstion\n\nA querystring parsing and stringifying library with some added security. \nLibrary was based on [this](https://www.npmjs.com/package/qs?activeTab=readme) js library.\n\n## Usage \n\n```python\nimport qstion as qs\n\nx = qs.parse('a=c')\nassert x == {'a': 'c'}\n\nx_str = qs.stringify(x)\nassert x_str == 'a=c'\n```\n\n### Parsing strings\n\n**qstion** (as well as **qs** in js) allows you to parse string into nested objects.\n    \n```python\nimport qstion as qs\n\nassert qs.parse('a[b][c]=1') == {'a': {'b': {'c': '1'}}}\n```\nThere is no support for plain object options or prototype pollution.\n\nUri encoded strings are supported\n    \n```python\nimport qstion as qs\n\nassert qs.parse('a%5Bb%5D=c') == {'a': { 'b': 'c' }}\n```\n\nFollowing options are supported:\n1. `from_url` : **bool** - if `True` then `parse` will parse querystring from url using `urlparse` from `urllib.parse` module, default: `False`\n```python\nimport qstion as qs\n\nassert qs.parse('http://localhost:8080/?a=c', from_url=True) == {'a': 'c'}\n\nassert qs.parse('a=c', from_url=False) == {'a': 'c'}\n```\n2. `delimiter` : **str**|**re.Pattern** - delimiter for parsing, default: `&`\n```python\nimport qstion as qs\n\nassert qs.parse('a=c&b=d', delimiter='&') == {'a': 'c', 'b': 'd'}\n\nassert qs.parse('a=c;b=d,c=d', delimiter='[;,]') == {'a': 'c', 'b': 'd', 'c': 'd'}\n```\n3. `depth` : **int** - maximum depth for parsing, default: `5`\n```python\nimport qstion as qs\n\nassert qs.parse('a[b][c][d][e][f][g][h][i]=j', depth=5) == {'a': {'b': {'c': {'d': {'e': {'f': {'[g][h][i]': 'j'}}}}}}}\n\nassert qs.parse('a[b][c][d][e][f][g][h][i]=j', depth=1) == {'a': {'b': {'[c][d][e][f][g][h][i]': 'j'}}}\n```\n\n4. `parameter_limit` : **int** - maximum number of parameters to parse, default: `1000`\n```python\nimport qstion as qs\n\nassert qs.parse('a=b&c=d&e=f&g=h&i=j&k=l&m=n&o=p&q=r&s=t&u=v&w=x&y=z', parameter_limit=5) == {'a': 'b', 'c': 'd', 'e': 'f', 'g': 'h', 'i': 'j'}\n```\n\n5. `allow_dots` : **bool** - if `True` then dots in keys will be parsed as nested objects, default: `False`\n```python\nimport qstion as qs\n\nassert qs.parse('a.b=c', allow_dots=True) == {'a': {'b': 'c'}}\n```\n6. `parse_arrays`: **bool** - if `True` then arrays will be parsed, default: `False`\n```python\nimport qstion as qs\n\nassert qs.parse('a[]=b&a[]=c', parse_arrays=True) == {'a': {0: 'b', 1: 'c'}}\n\nassert qs.parse('a[0]=b&a[1]=c', parse_arrays=False) == {'a': {'0': 'b', '1': 'c'}}\n```\n\n7. `array_limit` : **int** - maximum number of elements in array to keep array notation (only used with combination of argument `parse_arrays`), default: `20`\n```python\nimport qstion as qs\n\nassert qs.parse('a[]=b&a[]=c&', parse_arrays=True) == {'a': {0: 'b', 1: 'c'}}\n\nassert qs.parse('a[0]=b&a[1]=c&', array_limit=1, parse_arrays=True) == {'a': {'0': 'b', '1': 'c'}}\n```\n\n8. `allow_empty` : **bool** - if `True` then empty values and keys are accepted, default: `False`\n```python\nimport qstion as qs\n\nassert qs.parse('a=&b=', allow_empty=True) == {'a': '', 'b': ''}\n\nassert qs.parse('a[]=&b[]=', allow_empty=True) == {'a': {'': ''}, 'b': {'': ''}}\n```\n\n9. `charset` : **str** - charset to use when decoding uri encoded strings, default: `utf-8`\n```python\nimport qstion as qs\n\nassert qs.parse('a=%A7', charset='iso-8859-1') == {'a': '\u00a7'}\n\nassert qs.parse('a=%C2%A7', charset='utf-8') == {'a': '\u00a7'}\n```\n\n10. `charset_sentinel` : **bool** - if `True` then, if `utf8=\u2713` arg is included in querystring, then charset will be deduced based on encoding of '\u2713' character (recognizes only `utf8` and `iso-8859-1`), default: `False`\n```python\nimport qstion as qs\n\nassert qs.parse('a=%C2%A7&utf8=%E2%9C%93',charset='iso-8859-1', charset_sentinel=True) == {'a': '\u00a7'}\n\nassert qs.parse('a=%A7&utf8=%26%2310003', charset='utf-8', charset_sentinel=True) == {'a': '\u00a7'}\n```\n\n11. `interpret_numeric_entities` : **bool** - if `True` then numeric entities will be interpreted as unicode characters, default: `False`\n```python\nimport qstion as qs\n\nassert qs.parse('a=%26%2310003',charset='iso-8859-1', interpret_numeric_entities=True) == {'a': '\u2713'}\n```\n\n12. `parse_primitive`: **bool** - if `True` then primitive values will be parsed in their appearing types, default: `False`\n```python\nimport qstion as qs\n\nassert qs.parse('a=1&b=2&c=3', parse_primitive=True) == {'a': 1, 'b': 2, 'c': 3}\n\nassert qs.parse('a=true&b=false&c=null', parse_primitive=True) == {'a': True, 'b': False, 'c': None}\n```\n\n13. `primitive_strict`: **bool** - if `True` then primitive values of `bool` and `NoneType` will be parsed to reserved strict keywords (used only if `parse_primitive` is `True`), default: `True`\n```python\nimport qstion as qs\n\nassert qs.parse('a=true&b=false&c=null&d=None', parse_primitive=True, primitive_strict=True) == {'a': True, 'b': False, 'c': None, 'd': 'None'}\n\nassert qs.parse('a=True&b=False&c=NULL', parse_primitive=True, primitive_strict=True) == {'a': 'True', 'b': 'False', 'c': 'NULL'}\n\nassert qs.parse('a=True&b=False&c=NULL', parse_primitive=True, primitive_strict=False) == {'a': True, 'b': False, 'c': None}\n```\n\n14. `comma`: **bool** - if `True`, then coma separated values will be parsed as multiple separate values instead of string, default: `False`\n```python\nimport qstion as qs\n\nassert qs.parse('a=1,2,3', comma=True, parse_primitive=True) == {'a': [1, 2, 3]}\n```\n\n### Stringifying objects\n\n**qstion** (as well as **qs** in js) allows you to stringify objects into querystring.\n    \n```python\nimport qstion as qs\n\nassert qs.stringify({'a': 'b'}) == 'a=b'\n```\n\nFollowing options are supported:\n1. `allow_dots` : **bool** - if `True` then nested keys will be stringified using dot notation instead of brackets, default: `False`\n```python\nimport qstion as qs\n\nassert qs.stringify({'a': {'b': 'c'}}, allow_dots=True) == 'a.b=c'\n```\n\n2. `encode` : **bool** - if `True` then keys and values will be uri encoded (with default `charset`), default: `True`\n```python\nimport qstion as qs\n\nassert qs.stringify({'a[b]': 'b'}, encode=False) == 'a[b]=b'\n\nassert qs.stringify({'a[b]': 'b'}, encode=True) == 'a%5Bb%5D=b'\n```\n\n3. `charset` : **str** - charset to use when encoding uri strings, default: `utf-8` (if `encode` is `True`), note that un-encodable characters will be encoded using their xml numeric entities\n```python\nimport qstion as qs\n\nassert qs.stringify({'a': '\u00a7'}, charset='iso-8859-1') == 'a=%A7'\n\nassert qs.stringify({'a': '\u263a'}, charset='iso-8859-1') == 'a=%26%2312850'\n```\n\n4. `charset_sentinel` : **bool** - if `True` then, `utf8=\u2713` will be added to querystring to indicate that charset based on encoding of '\u2713' character (recognizes only `utf8` and `iso-8859-1`), default: `False`\n```python\nimport qstion as qs\n\nassert qs.stringify({'a': '\u00a7'}, charset='iso-8859-1', charset_sentinel=True) == 'a=%A7&utf8=%E2%9C%93'\n```\n\n\n5. `delimiter` : **str** - delimiter for stringifying, default: `&`\n```python\nimport qstion as qs\n\nassert qs.stringify({'a': 'b', 'c': 'd'}, delimiter='&') == 'a=b&c=d'\n\nassert qs.stringify({'a': 'b', 'c': 'd'}, delimiter=';') == 'a=b;c=d'\n``` \n\n6. `encode_values_only` : **bool** - if `True` then only values will be encoded, default: `False` (this option is overridden when `encode` is `True`)\n```python\n\nimport qstion as qs\n\nassert qs.stringify({'a': {'b': '\u263a'}}, encode_values_only=True, charset='iso-8859-1') == 'a[b]=%26%2312850'\n```\n\n7. `array_format` : **str** - format for array notation, options: 'brackets','indices', 'repeat', 'comma', default: 'indices'\n```python\nimport qstion as qs\n\nassert qs.stringify({'a': {1: 'b', 2: 'c'}}, array_format='brackets') == 'a[]=b&a[]=c'\n\nassert qs.stringify({'a': {1: 'b', 2: 'c'}}, array_format='indices') == 'a[1]=b&a[2]=c'\n\nassert qs.stringify({'a': {1: 'b', 2: 'c'}}, array_format='repeat') == 'a=b&a=c'\n\nassert qs.stringify({'a': {1: 'b', 2: 'c'}}, array_format='comma') == 'a=b,c'\n```\n\n8. `sort` : **bool** - if `True` then keys will be sorted alphabetically, default: `False`\n```python\nimport qstion as qs\n\nassert qs.stringify({'x': 'y', 'a': 'b'}, sort=True) == 'a=b&x=y'\n```\n\n9. `sort_reverse` : **bool** - if `True` then keys will be sorted (if `sort` is `True`) in reverse order, default: `False`\n```python\nimport qstion as qs\n\nassert qs.stringify({'x': 'y', 'a': 'b'}, sort=True, sort_reverse=True) == 'x=y&a=b'\n```\n\n10. `filter` : **list[str]** - list of keys to filter, default: `None`\n```python\nimport qstion as qs\n\nassert qs.stringify({'a': 'b', 'c': 'd'}, filter=['a']) == 'a=b'\n```\n",
    "bugtrack_url": null,
    "license": "BSD-3-Clause",
    "summary": "Simple package for parsing querystrings into nested dictionaries and vice versa.",
    "version": "1.0.1",
    "project_urls": {
        "Bug Tracker": "https://github.com/kajotgames/qstion/issues",
        "Documentation": "https://github.com/kajotgames/qstion/blob/main/README.md",
        "Homepage": "https://github.com/kajotgames/qstion"
    },
    "split_keywords": [
        "querystring",
        "query",
        "string",
        "url",
        "parse",
        "serialize",
        "deserialize",
        "nested",
        "dict",
        "dictionary",
        "qs",
        "qstion"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "4752f3f94c517ab12f462b2640fe5a96ae494278dbda98a2d7b31e9a5dbab558",
                "md5": "9e319db6ec39ca94acb5d7ed1485539a",
                "sha256": "5a1a8a0f45993683e1544424a3ab6707b666050b5f30fea44ee893c5c283682f"
            },
            "downloads": -1,
            "filename": "qstion-1.0.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "9e319db6ec39ca94acb5d7ed1485539a",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.11",
            "size": 13383,
            "upload_time": "2024-03-06T10:18:49",
            "upload_time_iso_8601": "2024-03-06T10:18:49.897512Z",
            "url": "https://files.pythonhosted.org/packages/47/52/f3f94c517ab12f462b2640fe5a96ae494278dbda98a2d7b31e9a5dbab558/qstion-1.0.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ec8e19a9bfd458613851fbd14ee2c044f300c8498b7a5da4411d3753e74554f0",
                "md5": "95d67fb981dfd98650efbf639971f3bd",
                "sha256": "0ee3ebe62a370e8009ac0f94e32a9692ee3a9a5a34e9e7d7f301e6bc887412de"
            },
            "downloads": -1,
            "filename": "qstion-1.0.1.tar.gz",
            "has_sig": false,
            "md5_digest": "95d67fb981dfd98650efbf639971f3bd",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.11",
            "size": 18418,
            "upload_time": "2024-03-06T10:18:51",
            "upload_time_iso_8601": "2024-03-06T10:18:51.305768Z",
            "url": "https://files.pythonhosted.org/packages/ec/8e/19a9bfd458613851fbd14ee2c044f300c8498b7a5da4411d3753e74554f0/qstion-1.0.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-03-06 10:18:51",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "kajotgames",
    "github_project": "qstion",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "requirements": [],
    "lcname": "qstion"
}
        
Elapsed time: 0.26649s