add Request.not_grouped_args, deprecation warning Request.raw_args (#1476)
* add Request.not_grouped_args, deprecation warning Request.raw_args * add 1 more test for coverage * custom parser for Request.args and Request.query_args, some additional tests * add docs for custom queryset parsing * fix import sorting * docstrings for get_query_args and get_args methods * lost import
This commit is contained in:
parent
d5813152ab
commit
2a15583b87
|
@ -19,6 +19,8 @@ The following variables are accessible as properties on `Request` objects:
|
|||
URL that resembles `?key1=value1&key2=value2`. If that URL were to be parsed,
|
||||
the `args` dictionary would look like `{'key1': ['value1'], 'key2': ['value2']}`.
|
||||
The request's `query_string` variable holds the unparsed string value.
|
||||
Property is providing the default parsing strategy. If you would like to change it look to the section below
|
||||
(`Changing the default parsing rules of the queryset`).
|
||||
|
||||
```python
|
||||
from sanic.response import json
|
||||
|
@ -28,9 +30,54 @@ The following variables are accessible as properties on `Request` objects:
|
|||
return json({ "parsed": True, "args": request.args, "url": request.url, "query_string": request.query_string })
|
||||
```
|
||||
|
||||
- `raw_args` (dict) - On many cases you would need to access the url arguments in
|
||||
a less packed dictionary. For same previous URL `?key1=value1&key2=value2`, the
|
||||
`raw_args` dictionary would look like `{'key1': 'value1', 'key2': 'value2'}`.
|
||||
- `query_args` (list) - On many cases you would need to access the url arguments in
|
||||
a less packed form. `query_args` is the list of `(key, value)` tuples.
|
||||
Property is providing the default parsing strategy. If you would like to change it look to the section below
|
||||
(`Changing the default parsing rules of the queryset`).
|
||||
For the same previous URL queryset `?key1=value1&key2=value2`, the
|
||||
`query_args` list would look like `[('key1', 'value1'), ('key2', 'value2')]`.
|
||||
And in case of the multiple params with the same key like `?key1=value1&key2=value2&key1=value3`
|
||||
the `query_args` list would look like `[('key1', 'value1'), ('key2', 'value2'), ('key1', 'value3')]`.
|
||||
|
||||
The difference between Request.args and Request.query_args
|
||||
for the queryset `?key1=value1&key2=value2&key1=value3`
|
||||
|
||||
```python
|
||||
from sanic import Sanic
|
||||
from sanic.response import json
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
|
||||
@app.route("/test_request_args")
|
||||
async def test_request_args(request):
|
||||
return json({
|
||||
"parsed": True,
|
||||
"url": request.url,
|
||||
"query_string": request.query_string,
|
||||
"args": request.args,
|
||||
"raw_args": request.raw_args,
|
||||
"query_args": request.query_args,
|
||||
})
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
```
|
||||
|
||||
Output
|
||||
|
||||
```
|
||||
{
|
||||
"parsed":true,
|
||||
"url":"http:\/\/0.0.0.0:8000\/test_request_args?key1=value1&key2=value2&key1=value3",
|
||||
"query_string":"key1=value1&key2=value2&key1=value3",
|
||||
"args":{"key1":["value1","value3"],"key2":["value2"]},
|
||||
"raw_args":{"key1":"value1","key2":"value2"},
|
||||
"query_args":[["key1","value1"],["key2","value2"],["key1","value3"]]
|
||||
}
|
||||
```
|
||||
|
||||
`raw_args` contains only the first entry of `key1`. Will be deprecated in the future versions.
|
||||
|
||||
- `files` (dictionary of `File` objects) - List of files that have a name, body, and type
|
||||
|
||||
|
@ -106,6 +153,51 @@ The following variables are accessible as properties on `Request` objects:
|
|||
- `token`: The value of Authorization header: `Basic YWRtaW46YWRtaW4=`
|
||||
|
||||
|
||||
## Changing the default parsing rules of the queryset
|
||||
|
||||
The default parameters that are using internally in `args` and `query_args` properties to parse queryset:
|
||||
|
||||
- `keep_blank_values` (bool): `False` - flag indicating whether blank values in
|
||||
percent-encoded queries should be treated as blank strings.
|
||||
A true value indicates that blanks should be retained as blank
|
||||
strings. The default false value indicates that blank values
|
||||
are to be ignored and treated as if they were not included.
|
||||
- `strict_parsing` (bool): `False` - flag indicating what to do with parsing errors. If
|
||||
false (the default), errors are silently ignored. If true,
|
||||
errors raise a ValueError exception.
|
||||
- `encoding` and `errors` (str): 'utf-8' and 'replace' - specify how to decode percent-encoded sequences
|
||||
into Unicode characters, as accepted by the bytes.decode() method.
|
||||
|
||||
If you would like to change that default parameters you could call `get_args` and `get_query_args` methods
|
||||
with the new values.
|
||||
|
||||
For the queryset `/?test1=value1&test2=&test3=value3`:
|
||||
|
||||
```python
|
||||
from sanic.response import json
|
||||
|
||||
@app.route("/query_string")
|
||||
def query_string(request):
|
||||
args_with_blank_values = request.get_args(keep_blank_values=True)
|
||||
return json({
|
||||
"parsed": True,
|
||||
"url": request.url,
|
||||
"args_with_blank_values": args_with_blank_values,
|
||||
"query_string": request.query_string
|
||||
})
|
||||
```
|
||||
|
||||
The output will be:
|
||||
|
||||
```
|
||||
{
|
||||
"parsed": true,
|
||||
"url": "http:\/\/0.0.0.0:8000\/query_string?test1=value1&test2=&test3=value3",
|
||||
"args_with_blank_values": {"test1": ["value1""], "test2": "", "test3": ["value3"]},
|
||||
"query_string": "test1=value1&test2=&test3=value3"
|
||||
}
|
||||
```
|
||||
|
||||
## Accessing values using `get` and `getlist`
|
||||
|
||||
The request properties which return a dictionary actually return a subclass of
|
||||
|
|
123
sanic/request.py
123
sanic/request.py
|
@ -2,11 +2,12 @@ import asyncio
|
|||
import email.utils
|
||||
import json
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from cgi import parse_header
|
||||
from collections import namedtuple
|
||||
from collections import defaultdict, namedtuple
|
||||
from http.cookies import SimpleCookie
|
||||
from urllib.parse import parse_qs, unquote, urlunparse
|
||||
from urllib.parse import parse_qs, parse_qsl, unquote, urlunparse
|
||||
|
||||
from httptools import parse_url
|
||||
|
||||
|
@ -83,6 +84,7 @@ class Request(dict):
|
|||
"headers",
|
||||
"method",
|
||||
"parsed_args",
|
||||
"parsed_not_grouped_args",
|
||||
"parsed_files",
|
||||
"parsed_form",
|
||||
"parsed_json",
|
||||
|
@ -109,7 +111,8 @@ class Request(dict):
|
|||
self.parsed_json = None
|
||||
self.parsed_form = None
|
||||
self.parsed_files = None
|
||||
self.parsed_args = None
|
||||
self.parsed_args = defaultdict(RequestParameters)
|
||||
self.parsed_not_grouped_args = defaultdict(list)
|
||||
self.uri_template = None
|
||||
self._cookies = None
|
||||
self.stream = None
|
||||
|
@ -199,21 +202,117 @@ class Request(dict):
|
|||
|
||||
return self.parsed_files
|
||||
|
||||
@property
|
||||
def args(self):
|
||||
if self.parsed_args is None:
|
||||
def get_args(
|
||||
self,
|
||||
keep_blank_values: bool = False,
|
||||
strict_parsing: bool = False,
|
||||
encoding: str = "utf-8",
|
||||
errors: str = "replace",
|
||||
) -> RequestParameters:
|
||||
"""
|
||||
Method to parse `query_string` using `urllib.parse.parse_qs`.
|
||||
This methods is used by `args` property.
|
||||
Can be used directly if you need to change default parameters.
|
||||
:param keep_blank_values: flag indicating whether blank values in
|
||||
percent-encoded queries should be treated as blank strings.
|
||||
A true value indicates that blanks should be retained as blank
|
||||
strings. The default false value indicates that blank values
|
||||
are to be ignored and treated as if they were not included.
|
||||
:type keep_blank_values: bool
|
||||
:param strict_parsing: flag indicating what to do with parsing errors.
|
||||
If false (the default), errors are silently ignored. If true,
|
||||
errors raise a ValueError exception.
|
||||
:type strict_parsing: bool
|
||||
:param encoding: specify how to decode percent-encoded sequences
|
||||
into Unicode characters, as accepted by the bytes.decode() method.
|
||||
:type encoding: str
|
||||
:param errors: specify how to decode percent-encoded sequences
|
||||
into Unicode characters, as accepted by the bytes.decode() method.
|
||||
:type errors: str
|
||||
:return: RequestParameters
|
||||
"""
|
||||
if not self.parsed_args[
|
||||
(keep_blank_values, strict_parsing, encoding, errors)
|
||||
]:
|
||||
if self.query_string:
|
||||
self.parsed_args = RequestParameters(
|
||||
parse_qs(self.query_string)
|
||||
self.parsed_args[
|
||||
(keep_blank_values, strict_parsing, encoding, errors)
|
||||
] = RequestParameters(
|
||||
parse_qs(
|
||||
qs=self.query_string,
|
||||
keep_blank_values=keep_blank_values,
|
||||
strict_parsing=strict_parsing,
|
||||
encoding=encoding,
|
||||
errors=errors,
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.parsed_args = RequestParameters()
|
||||
return self.parsed_args
|
||||
|
||||
return self.parsed_args[
|
||||
(keep_blank_values, strict_parsing, encoding, errors)
|
||||
]
|
||||
|
||||
args = property(get_args)
|
||||
|
||||
@property
|
||||
def raw_args(self):
|
||||
def raw_args(self) -> dict:
|
||||
if self.app.debug: # pragma: no cover
|
||||
warnings.simplefilter("default")
|
||||
warnings.warn(
|
||||
"Use of raw_args will be deprecated in "
|
||||
"the future versions. Please use args or query_args "
|
||||
"properties instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return {k: v[0] for k, v in self.args.items()}
|
||||
|
||||
def get_query_args(
|
||||
self,
|
||||
keep_blank_values: bool = False,
|
||||
strict_parsing: bool = False,
|
||||
encoding: str = "utf-8",
|
||||
errors: str = "replace",
|
||||
) -> list:
|
||||
"""
|
||||
Method to parse `query_string` using `urllib.parse.parse_qsl`.
|
||||
This methods is used by `query_args` property.
|
||||
Can be used directly if you need to change default parameters.
|
||||
:param keep_blank_values: flag indicating whether blank values in
|
||||
percent-encoded queries should be treated as blank strings.
|
||||
A true value indicates that blanks should be retained as blank
|
||||
strings. The default false value indicates that blank values
|
||||
are to be ignored and treated as if they were not included.
|
||||
:type keep_blank_values: bool
|
||||
:param strict_parsing: flag indicating what to do with parsing errors.
|
||||
If false (the default), errors are silently ignored. If true,
|
||||
errors raise a ValueError exception.
|
||||
:type strict_parsing: bool
|
||||
:param encoding: specify how to decode percent-encoded sequences
|
||||
into Unicode characters, as accepted by the bytes.decode() method.
|
||||
:type encoding: str
|
||||
:param errors: specify how to decode percent-encoded sequences
|
||||
into Unicode characters, as accepted by the bytes.decode() method.
|
||||
:type errors: str
|
||||
:return: list
|
||||
"""
|
||||
if not self.parsed_not_grouped_args[
|
||||
(keep_blank_values, strict_parsing, encoding, errors)
|
||||
]:
|
||||
if self.query_string:
|
||||
self.parsed_not_grouped_args[
|
||||
(keep_blank_values, strict_parsing, encoding, errors)
|
||||
] = parse_qsl(
|
||||
qs=self.query_string,
|
||||
keep_blank_values=keep_blank_values,
|
||||
strict_parsing=strict_parsing,
|
||||
encoding=encoding,
|
||||
errors=errors,
|
||||
)
|
||||
return self.parsed_not_grouped_args[
|
||||
(keep_blank_values, strict_parsing, encoding, errors)
|
||||
]
|
||||
|
||||
query_args = property(get_query_args)
|
||||
|
||||
@property
|
||||
def cookies(self):
|
||||
if self._cookies is None:
|
||||
|
|
|
@ -10,7 +10,7 @@ import pytest
|
|||
from sanic import Sanic
|
||||
from sanic import Blueprint
|
||||
from sanic.exceptions import ServerError
|
||||
from sanic.request import DEFAULT_HTTP_CONTENT_TYPE
|
||||
from sanic.request import DEFAULT_HTTP_CONTENT_TYPE, RequestParameters
|
||||
from sanic.response import json, text
|
||||
from sanic.testing import HOST, PORT
|
||||
|
||||
|
@ -130,6 +130,9 @@ def test_query_string(app):
|
|||
|
||||
assert request.args.get("test1") == "1"
|
||||
assert request.args.get("test2") == "false"
|
||||
assert request.args.getlist("test2") == ["false", "true"]
|
||||
assert request.args.getlist("test1") == ["1"]
|
||||
assert request.args.get("test3", default="My value") == "My value"
|
||||
|
||||
|
||||
def test_uri_template(app):
|
||||
|
@ -646,6 +649,77 @@ def test_request_raw_args(app):
|
|||
assert request.raw_args == params
|
||||
|
||||
|
||||
def test_request_query_args(app):
|
||||
# test multiple params with the same key
|
||||
params = [('test', 'value1'), ('test', 'value2')]
|
||||
|
||||
@app.get("/")
|
||||
def handler(request):
|
||||
return text("pass")
|
||||
|
||||
request, response = app.test_client.get("/", params=params)
|
||||
|
||||
assert request.query_args == params
|
||||
|
||||
# test cached value
|
||||
assert request.parsed_not_grouped_args[(False, False, "utf-8", "replace")] == request.query_args
|
||||
|
||||
# test params directly in the url
|
||||
request, response = app.test_client.get("/?test=value1&test=value2")
|
||||
|
||||
assert request.query_args == params
|
||||
|
||||
# test unique params
|
||||
params = [('test1', 'value1'), ('test2', 'value2')]
|
||||
|
||||
request, response = app.test_client.get("/", params=params)
|
||||
|
||||
assert request.query_args == params
|
||||
|
||||
# test no params
|
||||
request, response = app.test_client.get("/")
|
||||
|
||||
assert not request.query_args
|
||||
|
||||
|
||||
def test_request_query_args_custom_parsing(app):
|
||||
@app.get("/")
|
||||
def handler(request):
|
||||
return text("pass")
|
||||
|
||||
request, response = app.test_client.get("/?test1=value1&test2=&test3=value3")
|
||||
|
||||
assert request.get_query_args(
|
||||
keep_blank_values=True
|
||||
) == [
|
||||
('test1', 'value1'), ('test2', ''), ('test3', 'value3')
|
||||
]
|
||||
assert request.query_args == [
|
||||
('test1', 'value1'), ('test3', 'value3')
|
||||
]
|
||||
assert request.get_query_args(
|
||||
keep_blank_values=False
|
||||
) == [
|
||||
('test1', 'value1'), ('test3', 'value3')
|
||||
]
|
||||
|
||||
assert request.get_args(
|
||||
keep_blank_values=True
|
||||
) == RequestParameters(
|
||||
{"test1": ["value1"], "test2": [""], "test3": ["value3"]}
|
||||
)
|
||||
|
||||
assert request.args == RequestParameters(
|
||||
{"test1": ["value1"], "test3": ["value3"]}
|
||||
)
|
||||
|
||||
assert request.get_args(
|
||||
keep_blank_values=False
|
||||
) == RequestParameters(
|
||||
{"test1": ["value1"], "test3": ["value3"]}
|
||||
)
|
||||
|
||||
|
||||
def test_request_cookies(app):
|
||||
|
||||
cookies = {"test": "OK"}
|
||||
|
|
Loading…
Reference in New Issue
Block a user