From 8376d4a75b1f8e72b0679bd0c54c170308a96379 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 19 Mar 2017 19:13:20 -0700 Subject: [PATCH] add json parameter to client request method #1726 --- aiohttp/client.py | 7 ++++++- aiohttp/client_reqrep.py | 5 ++++- aiohttp/payload.py | 14 +++++++++++++- docs/client_reference.rst | 13 +++++++++---- tests/test_client_functional.py | 22 ++++++++++++++++++++++ tests/test_client_request.py | 8 ++++++++ 6 files changed, 62 insertions(+), 7 deletions(-) diff --git a/aiohttp/client.py b/aiohttp/client.py index e48f9d8f7a5..7aa286295ca 100644 --- a/aiohttp/client.py +++ b/aiohttp/client.py @@ -133,6 +133,7 @@ def request(self, method, url, **kwargs): def _request(self, method, url, *, params=None, data=None, + json=None, headers=None, skip_auto_headers=None, auth=None, @@ -160,6 +161,10 @@ def _request(self, method, url, *, if self.closed: raise RuntimeError('Session is closed') + if data is not None and json is not None: + raise ValueError( + 'data and json parameters can not be used at the same time') + if not isinstance(chunked, bool) and chunked is not None: warnings.warn( 'Chunk size is deprecated #1615', DeprecationWarning) @@ -203,7 +208,7 @@ def _request(self, method, url, *, req = self._request_class( method, url, params=params, headers=headers, - skip_auto_headers=skip_headers, data=data, + skip_auto_headers=skip_headers, data=data, json=json, cookies=cookies, auth=auth, version=version, compress=compress, chunked=chunked, expect100=expect100, loop=self._loop, diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index 423347fa531..a67d49a8a56 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -54,7 +54,7 @@ class ClientRequest: def __init__(self, method, url, *, params=None, headers=None, skip_auto_headers=frozenset(), - data=None, cookies=None, + data=None, json=None, cookies=None, auth=None, version=http.HttpVersion11, compress=None, chunked=None, expect100=False, loop=None, response_class=None, @@ -84,6 +84,9 @@ def __init__(self, method, url, *, if loop.get_debug(): self._source_traceback = traceback.extract_stack(sys._getframe(1)) + if json is not None: + data = payload.JsonPayload(json) + self.update_version(version) self.update_host(url) self.update_headers(headers) diff --git a/aiohttp/payload.py b/aiohttp/payload.py index d4845f0aa2c..334dfb06905 100644 --- a/aiohttp/payload.py +++ b/aiohttp/payload.py @@ -1,5 +1,6 @@ import asyncio import io +import json import mimetypes import os from abc import ABC, abstractmethod @@ -14,7 +15,7 @@ __all__ = ('PAYLOAD_REGISTRY', 'get_payload', 'payload_type', 'Payload', 'BytesPayload', 'StringPayload', 'StreamReaderPayload', 'IOBasePayload', 'BytesIOPayload', 'BufferedReaderPayload', - 'TextIOPayload', 'StringIOPayload') + 'TextIOPayload', 'StringIOPayload', 'JsonPayload') class LookupError(Exception): @@ -284,6 +285,17 @@ def write(self, writer): break +class JsonPayload(BytesPayload): + + def __init__(self, value, + encoding='utf-8', content_type='application/json', + *args, **kwargs): + + super().__init__( + json.dumps(value).encode(encoding), + content_type=content_type, encoding=encoding, *args, **kwargs) + + PAYLOAD_REGISTRY = PayloadRegistry() PAYLOAD_REGISTRY.register(BytesPayload, (bytes, bytearray, memoryview)) PAYLOAD_REGISTRY.register(StringPayload, str) diff --git a/docs/client_reference.rst b/docs/client_reference.rst index 75fe7347416..b0cd3b6e2fa 100644 --- a/docs/client_reference.rst +++ b/docs/client_reference.rst @@ -146,7 +146,7 @@ The client session supports the context manager protocol for self closing. A read-only property. - .. comethod:: request(method, url, *, params=None, data=None,\ + .. comethod:: request(method, url, *, params=None, data=None, json=None,\ headers=None, skip_auto_headers=None, \ auth=None, allow_redirects=True,\ max_redirects=10, version=HttpVersion(major=1, minor=1),\ @@ -180,6 +180,9 @@ The client session supports the context manager protocol for self closing. :param data: Dictionary, bytes, or file-like object to send in the body of the request (optional) + :param json: Any json compatible python object (optional). `json` and `data` + parameters could not be used at the same time. + :param dict headers: HTTP Headers to send with the request (optional) @@ -484,7 +487,7 @@ keepaliving, cookies and complex connection stuff like properly configured SSL certification chaining. -.. coroutinefunction:: request(method, url, *, params=None, data=None, \ +.. coroutinefunction:: request(method, url, *, params=None, data=None, json=None,\ headers=None, cookies=None, auth=None, \ allow_redirects=True, max_redirects=10, \ encoding='utf-8', \ @@ -506,8 +509,10 @@ certification chaining. :param data: Dictionary, bytes, or file-like object to send in the body of the request (optional) - :param dict headers: HTTP Headers to send with - the request (optional) + :param json: Any json compatible python object (optional). `json` and `data` + parameters could not be used at the same time. + + :param dict headers: HTTP Headers to send with the request (optional) :param dict cookies: Cookies to send with the request (optional) diff --git a/tests/test_client_functional.py b/tests/test_client_functional.py index 9a53e202133..38970201512 100644 --- a/tests/test_client_functional.py +++ b/tests/test_client_functional.py @@ -1583,6 +1583,28 @@ def handler(request): resp.close() +@asyncio.coroutine +def test_json(loop, test_client): + @asyncio.coroutine + def handler(request): + assert request.content_type == 'application/json' + data = yield from request.json() + return web.Response(body=aiohttp.JsonPayload(data)) + + app = web.Application(loop=loop) + app.router.add_post('/', handler) + client = yield from test_client(app) + + resp = yield from client.post('/', json={'some': 'data'}) + assert 200 == resp.status + content = yield from resp.json() + assert content == {'some': 'data'} + resp.close() + + with pytest.raises(ValueError): + yield from client.post('/', data="some data", json={'some': 'data'}) + + @asyncio.coroutine def test_expect_continue(loop, test_client): expect_called = False diff --git a/tests/test_client_request.py b/tests/test_client_request.py index 6d0ba61f1df..04a8d575343 100644 --- a/tests/test_client_request.py +++ b/tests/test_client_request.py @@ -746,6 +746,14 @@ def test_chunked_length(loop, conn): headers={'CONTENT-LENGTH': '1000'}, chunked=True, loop=loop) +@asyncio.coroutine +def test_chunked_transfer_encoding(loop, conn): + with pytest.raises(ValueError): + ClientRequest( + 'get', URL('http://python.org/'), + headers={'TRANSFER-ENCODING': 'chunked'}, chunked=True, loop=loop) + + @asyncio.coroutine def test_file_upload_not_chunked(loop): here = os.path.dirname(__file__)