Skip to content

Commit 8c8ae15

Browse files
committed
Add support resource (add_resource) based decorator.
1 parent 9111329 commit 8c8ae15

File tree

3 files changed

+94
-2
lines changed

3 files changed

+94
-2
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ async def images(req, resp, fn):
5959
# Send picture. Filename - in just a parameter
6060
await resp.send_file('images/{}'.format(fn))
6161

62+
# Some REST API endpoint
63+
@app.resource('/user/<id>')
64+
def user(data, id):
65+
return {'id': id, 'name': 'Foo', 'status': 'online'}
66+
67+
6268
if __name__ == '__main__':
6369
app.run(host='0.0.0.0', port=80)
6470
```
@@ -110,6 +116,22 @@ Main tinyweb app class.
110116
`**kwargs` are optional and will be passed to handler directly.
111117
**Note**: only `GET`, `POST`, `PUT` and `DELETE` methods are supported. Check [restapi full example](https://github.com/belyalov/tinyweb/blob/master/examples/rest_api.py) as well.
112118

119+
* `@resource` - the same idea as for `route` but for resource:
120+
```python
121+
# Regular version
122+
@app.resource('/user/<id>')
123+
def user(data, id):
124+
return {'id': id, 'name': 'foo'}
125+
126+
# Generator based / different HTTP method
127+
@app.resource('/user/<id>', method='POST')
128+
async def user(data, id):
129+
yield '{'
130+
yield '"id": "{}",'.format(id)
131+
yield '"name": "test",'
132+
yield '}'
133+
```
134+
113135
* `run(self, host="127.0.0.1", port=8081, loop_forever=True, backlog=10)` - run web server. Since *tinyweb* is fully async server by default it is blocking call assuming that you've added other tasks before.
114136
* `host` - host to listen on
115137
* `port` - port to listen on

test/test_server.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ def testUrlFinderNegative(self):
254254
srv.add_route('/duppp', 1)
255255

256256

257-
# We want to test decorator @server.route as well
257+
# We want to test decorators as well
258258
server_for_decorators = webserver()
259259

260260

@@ -265,6 +265,16 @@ async def route_for_decorator(req, resp, user_id):
265265
await resp.send('YO, {}'.format(user_id))
266266

267267

268+
@server_for_decorators.resource('/rest1/<user_id>')
269+
def resource_for_decorator1(data, user_id):
270+
return {'name': user_id}
271+
272+
273+
@server_for_decorators.resource('/rest2/<user_id>')
274+
async def resource_for_decorator2(data, user_id):
275+
yield '{"name": user_id}'
276+
277+
268278
class ServerFull(unittest.TestCase):
269279

270280
def setUp(self):
@@ -279,7 +289,7 @@ def setUp(self):
279289
self.srv = webserver()
280290
self.srv.conns[id(1)] = None
281291

282-
def testDecorator(self):
292+
def testRouteDecorator1(self):
283293
"""Test @.route() decorator"""
284294
# First decorator
285295
rdr = mockReader(['GET /uid/man1 HTTP/1.1\r\n',
@@ -294,6 +304,7 @@ def testDecorator(self):
294304
self.assertEqual(wrt.history, expected)
295305
self.assertTrue(wrt.closed)
296306

307+
def testRouteDecorator2(self):
297308
# Second decorator
298309
rdr = mockReader(['GET /uid2/man2 HTTP/1.1\r\n',
299310
HDRE])
@@ -309,6 +320,42 @@ def testDecorator(self):
309320
self.assertEqual(wrt.history, expected)
310321
self.assertTrue(wrt.closed)
311322

323+
def testResourceDecorator1(self):
324+
"""Test @.resource() decorator"""
325+
rdr = mockReader(['GET /rest1/man1 HTTP/1.1\r\n',
326+
HDRE])
327+
wrt = mockWriter()
328+
run_coro(server_for_decorators._handler(rdr, wrt))
329+
expected = ['HTTP/1.0 200 MSG\r\n'
330+
'Access-Control-Allow-Origin: *\r\n' +
331+
'Access-Control-Allow-Headers: *\r\n' +
332+
'Content-Length: 16\r\n' +
333+
'Access-Control-Allow-Methods: GET\r\n' +
334+
'Content-Type: application/json\r\n\r\n',
335+
'{"name": "man1"}']
336+
self.assertEqual(wrt.history, expected)
337+
self.assertTrue(wrt.closed)
338+
339+
def testResourceDecorator2(self):
340+
rdr = mockReader(['GET /rest2/man2 HTTP/1.1\r\n',
341+
HDRE])
342+
wrt = mockWriter()
343+
run_coro(server_for_decorators._handler(rdr, wrt))
344+
expected = ['HTTP/1.1 200 MSG\r\n' +
345+
'Access-Control-Allow-Methods: GET\r\n' +
346+
'Connection: close\r\n' +
347+
'Access-Control-Allow-Headers: *\r\n' +
348+
'Content-Type: application/json\r\n' +
349+
'Transfer-Encoding: chunked\r\n' +
350+
'Access-Control-Allow-Origin: *\r\n\r\n',
351+
'11\r\n',
352+
'{"name": user_id}',
353+
'\r\n',
354+
'0\r\n\r\n'
355+
]
356+
self.assertEqual(wrt.history, expected)
357+
self.assertTrue(wrt.closed)
358+
312359
async def dummy_handler(self, req, resp):
313360
"""Dummy URL handler. It just records the fact - it has been called"""
314361
self.dummy_req = req

tinyweb/server.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,29 @@ def _route(f):
577577
return f
578578
return _route
579579

580+
def resource(self, url, method='GET', **kwargs):
581+
"""Decorator for add_resource() method
582+
583+
Examples:
584+
@app.resource('/users')
585+
def users(data):
586+
return {'a': 1}
587+
588+
@app.resource('/messages/<topic_id>')
589+
async def index(data, topic_id):
590+
yield '{'
591+
yield '"topic_id": "{}",'.format(topic_id)
592+
yield '"message": "test",'
593+
yield '}'
594+
"""
595+
def _resource(f):
596+
self.add_route(url, restful_resource_handler,
597+
methods=[method],
598+
save_headers=['Content-Length', 'Content-Type'],
599+
_callmap={method.encode(): (f, kwargs)})
600+
return f
601+
return _resource
602+
580603
async def _tcp_server(self, host, port, backlog):
581604
"""TCP Server implementation.
582605
Opens socket for accepting connection and

0 commit comments

Comments
 (0)