Skip to content

Commit 73fe31e

Browse files
authored
Merge pull request #1 from trustpilot/fix-path-params
Fix path params
2 parents 225a0ef + 0a6368f commit 73fe31e

File tree

7 files changed

+121
-25
lines changed

7 files changed

+121
-25
lines changed

.travis.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ install:
99
- pip install pypandoc
1010
- pip install .[test]
1111
script:
12-
- py.test
12+
- prospector -M
13+
- pytest
1314
branches:
1415
only: master
1516
after_success:

HISTORY.rst

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
History
22
=======
33

4-
0.0.1 (2017-08-09)
4+
0.0.1 (2017-01-09)
55
------------------
66

7-
* git init
7+
* git init
8+
9+
1.0.0(2017-01-12)
10+
------------------
11+
12+
* added test suite
13+
* added path_param type casting

README.md

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,56 @@
11
[![Build Status](https://travis-ci.org/trustpilot/python-sanicargs.svg?branch=master)](https://travis-ci.org/trustpilot/python-sanicargs) [![Latest Version](https://img.shields.io/pypi/v/sanicargs.svg)](https://pypi.python.org/pypi/sanicargs) [![Python Support](https://img.shields.io/pypi/pyversions/sanicargs.svg)](https://pypi.python.org/pypi/sanicargs)
22

33
# Sanicargs
4-
Parses query args in sanic using type annotations
4+
Parses query args in [Sanic](https://github.com/channelcat/sanic) using type annotations.
5+
6+
## Install
7+
Install with pip
8+
```
9+
$ pip install sanicargs
10+
```
511

612
## Usage
713

8-
Use with [Sanic framework](https://github.com/channelcat/sanic)
14+
Use the `parse_query_args` decorator to parse query args and type cast query args and path params with [Sanic](https://github.com/channelcat/sanic)'s routes or blueprints like in the [example](https://github.com/trustpilot/python-sanicargs/tree/master/examples/simple.py) below:
15+
16+
```python
17+
import datetime
18+
from sanic import Sanic, response
19+
from sanicargs import parse_query_args
20+
21+
app = Sanic("test_sanic_app")
22+
23+
@app.route("/me/<id>/birthdate", methods=['GET'])
24+
@parse_query_args
25+
async def test_datetime(request, id: str, birthdate: datetime.datetime):
26+
return response.json({
27+
'id': id,
28+
'birthdate': birthdate.isoformat()
29+
})
30+
31+
if __name__ == "__main__":
32+
app.run(host="0.0.0.0", port=8080, access_log=False, debug=False)
33+
```
34+
35+
Test it running with
36+
```bash
37+
$ curl 'http://0.0.0.0:8080/me/123/birthdate?birthdate=2017-10-30'
938
```
10-
@app.route("/datetime", methods=['GET'])
11-
@parse_query_args
12-
async def test_datetime(request, test: datetime.datetime):
13-
return response.json({'test': test.isoformat()})
14-
```
39+
40+
### Fields
41+
42+
* **str** : `ex: ?message=hello world`
43+
* **int** : `ex: ?age=100`
44+
* **datetime.datetime** : `ex: ?currentdate=2017-10-30T10:10:30 or 2017-10-30`
45+
* **datetime.date** : `ex: ?birthdate=2017-10-30`
46+
* **List[str]** : `ex: ?words=you,me,them,we`
47+
48+
### Important notice about decorators
49+
50+
The sequence of decorators is, as usual, important in Python.
51+
52+
You need to apply the `parse_query_args` decorator as the first one executed which means closest to the `def`.
53+
54+
### `request` is mandatory!
55+
56+
You should always have request as the first argument in your function in order to use `parse_query_args`

examples/simple.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import datetime
2+
from sanic import Sanic, response
3+
from sanicargs import parse_query_args
4+
5+
app = Sanic("test_sanic_app")
6+
7+
@app.route("/me/<id>/birthdate", methods=['GET'])
8+
@parse_query_args
9+
async def test_datetime(request, id: str, birthdate: datetime.datetime):
10+
return response.json({
11+
'id': id,
12+
'birthdate': birthdate.isoformat()
13+
})
14+
if __name__ == "__main__":
15+
app.run(host="0.0.0.0", port=8080, access_log=False, debug=False)

sanicargs/__init__.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,25 @@
88

99
from logging import getLogger
1010

11-
logger = getLogger('sonicargs')
11+
__logger = getLogger('sanicargs')
1212

1313

14-
def parse_datetime(str):
14+
def __parse_datetime(str):
1515
# attempt full date time, but tolerate just a date
1616
try:
1717
return datetime.datetime.strptime(str, '%Y-%m-%dT%H:%M:%S')
1818
except:
1919
pass
2020
return datetime.datetime.strptime(str, '%Y-%m-%d')
2121

22-
def parse_date(str):
22+
def __parse_date(str):
2323
return datetime.datetime.strptime(str, '%Y-%m-%d').date()
2424

25-
type_deserializers = {
25+
__type_deserializers = {
2626
int: int,
2727
str: str,
28-
datetime.datetime: parse_datetime,
29-
datetime.date: parse_date,
28+
datetime.datetime: __parse_datetime,
29+
datetime.date: __parse_date,
3030
List[str]: lambda s: s.split(',')
3131
}
3232

@@ -54,26 +54,27 @@ async def inner(request, *old_args, **route_parameters):
5454
name = None
5555
try:
5656
for name, arg_type, default in parameters:
57+
raw_value = request.args.get(name, None)
58+
5759
# provided in route
5860
if name in route_parameters or name=="request":
59-
continue
61+
if name=="request":
62+
continue
63+
raw_value = route_parameters[name]
6064

6165
# no value
62-
if name not in request.args:
66+
elif name not in request.args:
6367
if default != inspect._empty:
6468
# TODO clone?
6569
kwargs[name] = default
6670
continue
6771
else:
6872
raise KeyError("Missing required argument %s" % name)
6973

70-
raw_value = request.args[name][0]
71-
parsed_value = type_deserializers[arg_type](raw_value)
74+
parsed_value = __type_deserializers[arg_type](raw_value)
7275
kwargs[name] = parsed_value
73-
74-
kwargs.update(route_parameters)
7576
except Exception as err:
76-
logger.warning({
77+
__logger.warning({
7778
"message": "Request args not validated",
7879
"stacktrace": str(err)
7980
})

sanicargs/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.0.3'
1+
__version__ = '1.0.0'

tests/test_sanicargs.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ async def test_all(
5454
'e': e
5555
})
5656

57+
@app.route("/optional", methods=['GET'])
58+
@parse_query_args
59+
async def test_optional(request, test: str = 'helloworld'):
60+
return response.json({'test': test})
61+
62+
@app.route("/with/<path_param>/path_params", methods=['GET'])
63+
@parse_query_args
64+
async def test_path_params(request, path_param: int, test: str, test_2: int=35):
65+
return response.json({'path_param': path_param, 'test': test, 'test_2': test_2})
66+
5767
yield app
5868

5969
@pytest.fixture
@@ -65,6 +75,7 @@ def test_cli(loop, app, test_client):
6575
# Tests #
6676
#########
6777

78+
6879
async def test_parse_int_success(test_cli):
6980
resp = await test_cli.get('/int?test=10')
7081
assert resp.status == 200
@@ -132,6 +143,7 @@ async def test_parse_list_also_works_with_singular(test_cli):
132143
'not a datetime'
133144
]}
134145

146+
135147
async def test_all_at_once(test_cli):
136148
resp = await test_cli.get('/all?a=10&b=test&c=2017-10-10T10:10:10&d=2017-10-10&e=a,b,c,d,e')
137149
assert resp.status == 200
@@ -144,4 +156,23 @@ async def test_all_at_once(test_cli):
144156
e=[
145157
'a', 'b', 'c', 'd', 'e'
146158
]
147-
)
159+
)
160+
161+
162+
async def test_optional(test_cli):
163+
resp = await test_cli.get('/optional')
164+
assert resp.status == 200
165+
resp_json = await resp.json()
166+
assert resp_json == {'test': 'helloworld'}
167+
168+
169+
async def test_mandatory(test_cli):
170+
resp = await test_cli.get('/str')
171+
assert resp.status == 400
172+
173+
174+
async def test_with_path_params(test_cli):
175+
resp = await test_cli.get('/with/123/path_params?test=hello')
176+
assert resp.status == 200
177+
resp_json = await resp.json()
178+
assert resp_json == {'path_param': 123, 'test': 'hello', 'test_2': 35}

0 commit comments

Comments
 (0)