From 8f17741849edb5e7eba0356f90cb17e43c938a2f Mon Sep 17 00:00:00 2001 From: Carol Willing Date: Thu, 17 Jul 2014 12:34:31 -0700 Subject: [PATCH 1/5] Adds json parameter for POST requests --- requests/api.py | 6 ++++-- requests/models.py | 22 ++++++++++++++++------ requests/sessions.py | 10 ++++++++-- test_requests.py | 8 ++++++++ 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/requests/api.py b/requests/api.py index 01d853d5..88db7dc7 100644 --- a/requests/api.py +++ b/requests/api.py @@ -22,6 +22,7 @@ def request(method, url, **kwargs): :param url: URL for the new :class:`Request` object. :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. :param files: (optional) Dictionary of 'name': file-like-objects (or {'name': ('filename', fileobj)}) for multipart encoding upload. @@ -77,15 +78,16 @@ def head(url, **kwargs): return request('head', url, **kwargs) -def post(url, data=None, **kwargs): +def post(url, data=None, json=None, **kwargs): """Sends a POST request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. """ - return request('post', url, data=data, **kwargs) + return request('post', url, data=data, json=json, **kwargs) def put(url, data=None, **kwargs): diff --git a/requests/models.py b/requests/models.py index 03ff627a..b6ef9190 100644 --- a/requests/models.py +++ b/requests/models.py @@ -190,6 +190,7 @@ class Request(RequestHooksMixin): :param headers: dictionary of headers to send. :param files: dictionary of {filename: fileobject} files to multipart upload. :param data: the body to attach the request. If a dictionary is provided, form-encoding will take place. + :param json: json for the body to attach the request. :param params: dictionary of URL parameters to append to the URL. :param auth: Auth handler or (user, pass) tuple. :param cookies: dictionary or CookieJar of cookies to attach to this request. @@ -209,6 +210,7 @@ class Request(RequestHooksMixin): headers=None, files=None, data=None, + json=None, params=None, auth=None, cookies=None, @@ -216,6 +218,7 @@ class Request(RequestHooksMixin): # Default empty dicts for dict params. data = [] if data is None else data + json = [] if json is None else json files = [] if files is None else files headers = {} if headers is None else headers params = {} if params is None else params @@ -230,6 +233,7 @@ class Request(RequestHooksMixin): self.headers = headers self.files = files self.data = data + self.json = json self.params = params self.auth = auth self.cookies = cookies @@ -246,6 +250,7 @@ class Request(RequestHooksMixin): headers=self.headers, files=self.files, data=self.data, + json=self.json, params=self.params, auth=self.auth, cookies=self.cookies, @@ -289,7 +294,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): self.hooks = default_hooks() def prepare(self, method=None, url=None, headers=None, files=None, - data=None, params=None, auth=None, cookies=None, hooks=None): + data=None, json=None, params=None, auth=None, cookies=None, hooks=None): """Prepares the entire request with the given parameters.""" self.prepare_method(method) @@ -397,7 +402,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): else: self.headers = CaseInsensitiveDict() - def prepare_body(self, data, files): + def prepare_body(self, data, files, _json=None): """Prepares the given HTTP body data.""" # Check if file, fo, generator, iterator. @@ -408,6 +413,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): content_type = None length = None + if _json is not None: + content_type = 'application/json' + data = json.dumps(_json) + is_stream = all([ hasattr(data, '__iter__'), not isinstance(data, (basestring, list, tuple, dict)) @@ -435,10 +444,11 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): else: if data: body = self._encode_params(data) - if isinstance(data, basestring) or hasattr(data, 'read'): - content_type = None - else: - content_type = 'application/x-www-form-urlencoded' + if not _json: + if isinstance(data, basestring) or hasattr(data, 'read'): + content_type = None + else: + content_type = 'application/x-www-form-urlencoded' self.prepare_content_length(body) diff --git a/requests/sessions.py b/requests/sessions.py index 508b0ef2..7942447f 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -365,6 +365,7 @@ class Session(SessionRedirectMixin): url=request.url, files=request.files, data=request.data, + json=request.json, headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict), params=merge_setting(request.params, self.params), auth=merge_setting(auth, self.auth), @@ -376,6 +377,7 @@ class Session(SessionRedirectMixin): def request(self, method, url, params=None, data=None, + json=None, headers=None, cookies=None, files=None, @@ -396,6 +398,8 @@ class Session(SessionRedirectMixin): string for the :class:`Request`. :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the + :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. :param cookies: (optional) Dict or CookieJar object to send with the @@ -426,6 +430,7 @@ class Session(SessionRedirectMixin): headers = headers, files = files, data = data or {}, + json = json or {}, params = params or {}, auth = auth, cookies = cookies, @@ -479,15 +484,16 @@ class Session(SessionRedirectMixin): kwargs.setdefault('allow_redirects', False) return self.request('HEAD', url, **kwargs) - def post(self, url, data=None, **kwargs): + def post(self, url, data=None, json=None, **kwargs): """Sends a POST request. Returns :class:`Response` object. :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. """ - return self.request('POST', url, data=data, **kwargs) + return self.request('POST', url, data=data, json=json, **kwargs) def put(self, url, data=None, **kwargs): """Sends a PUT request. Returns :class:`Response` object. diff --git a/test_requests.py b/test_requests.py index 716c0dcf..2e98cb91 100755 --- a/test_requests.py +++ b/test_requests.py @@ -986,6 +986,14 @@ class RequestsTestCase(unittest.TestCase): assert item.history == total[0:i] i=i+1 + def test_json_param_post_content_type_works(self): + r = requests.post( + httpbin('post'), + json={'life': 42} + ) + assert r.status_code == 200 + assert 'application/json' in r.headers['Content-Type'] + class TestContentEncodingDetection(unittest.TestCase): From b34a496649667b9324caa634cb17e726e0cce1a5 Mon Sep 17 00:00:00 2001 From: Carol Willing Date: Thu, 28 Aug 2014 16:45:24 -0700 Subject: [PATCH 2/5] Adds review changes --- requests/models.py | 4 ++-- test_requests.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/requests/models.py b/requests/models.py index b6ef9190..1b110b49 100644 --- a/requests/models.py +++ b/requests/models.py @@ -301,7 +301,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): self.prepare_url(url, params) self.prepare_headers(headers) self.prepare_cookies(cookies) - self.prepare_body(data, files) + self.prepare_body(data, files, json) self.prepare_auth(auth, url) # Note that prepare_auth must be last to enable authentication schemes # such as OAuth to work on a fully prepared request. @@ -442,7 +442,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if files: (body, content_type) = self._encode_files(files, data) else: - if data: + if data and not _json: body = self._encode_params(data) if not _json: if isinstance(data, basestring) or hasattr(data, 'read'): diff --git a/test_requests.py b/test_requests.py index 2e98cb91..af272355 100755 --- a/test_requests.py +++ b/test_requests.py @@ -992,7 +992,8 @@ class RequestsTestCase(unittest.TestCase): json={'life': 42} ) assert r.status_code == 200 - assert 'application/json' in r.headers['Content-Type'] + assert 'application/json' in r.request.headers['Content-Type'] + #assert {'life': 42} == r.json()['json'] class TestContentEncodingDetection(unittest.TestCase): From 402f3b4993fe8d0626fcffcbfc30caf671b65df3 Mon Sep 17 00:00:00 2001 From: Carol Willing Date: Thu, 28 Aug 2014 19:27:45 -0700 Subject: [PATCH 3/5] Changes check on base and json. Fails on tests. --- requests/models.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/requests/models.py b/requests/models.py index 1b110b49..a96fc4e6 100644 --- a/requests/models.py +++ b/requests/models.py @@ -442,13 +442,12 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if files: (body, content_type) = self._encode_files(files, data) else: - if data and not _json: + if data and _json is None: body = self._encode_params(data) - if not _json: - if isinstance(data, basestring) or hasattr(data, 'read'): - content_type = None - else: - content_type = 'application/x-www-form-urlencoded' + if isinstance(data, basestring) or hasattr(data, 'read'): + content_type = None + else: + content_type = 'application/x-www-form-urlencoded' self.prepare_content_length(body) From 0713e09526d3ed2d2fe2516fa6d35727c557024f Mon Sep 17 00:00:00 2001 From: Carol Willing Date: Thu, 28 Aug 2014 19:42:21 -0700 Subject: [PATCH 4/5] Fixes typo in test --- test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index af272355..294ba684 100755 --- a/test_requests.py +++ b/test_requests.py @@ -993,7 +993,7 @@ class RequestsTestCase(unittest.TestCase): ) assert r.status_code == 200 assert 'application/json' in r.request.headers['Content-Type'] - #assert {'life': 42} == r.json()['json'] + assert {'life': 42} == r.json()['json'] class TestContentEncodingDetection(unittest.TestCase): From 8ed941fa6991355753e698f6b253096d57f6c7e4 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 30 Sep 2014 16:03:31 -0500 Subject: [PATCH 5/5] Fix a couple of issues I noticed - Don't _ prefix json in prepare_body - Don't initialize json to [] - Don't initialize json to {} - Reorder parameters to PreparedRequest.prepare - Remove extra parentheses - Update docstring --- requests/models.py | 20 +++++++++++--------- requests/sessions.py | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/requests/models.py b/requests/models.py index a96fc4e6..c1f7f561 100644 --- a/requests/models.py +++ b/requests/models.py @@ -46,6 +46,8 @@ DEFAULT_REDIRECT_LIMIT = 30 CONTENT_CHUNK_SIZE = 10 * 1024 ITER_CHUNK_SIZE = 512 +json_dumps = json.dumps + class RequestEncodingMixin(object): @property @@ -189,8 +191,8 @@ class Request(RequestHooksMixin): :param url: URL to send. :param headers: dictionary of headers to send. :param files: dictionary of {filename: fileobject} files to multipart upload. - :param data: the body to attach the request. If a dictionary is provided, form-encoding will take place. - :param json: json for the body to attach the request. + :param data: the body to attach to the request. If a dictionary is provided, form-encoding will take place. + :param json: json for the body to attach to the request (if data is not specified). :param params: dictionary of URL parameters to append to the URL. :param auth: Auth handler or (user, pass) tuple. :param cookies: dictionary or CookieJar of cookies to attach to this request. @@ -218,7 +220,6 @@ class Request(RequestHooksMixin): # Default empty dicts for dict params. data = [] if data is None else data - json = [] if json is None else json files = [] if files is None else files headers = {} if headers is None else headers params = {} if params is None else params @@ -294,7 +295,8 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): self.hooks = default_hooks() def prepare(self, method=None, url=None, headers=None, files=None, - data=None, json=None, params=None, auth=None, cookies=None, hooks=None): + data=None, params=None, auth=None, cookies=None, hooks=None, + json=None): """Prepares the entire request with the given parameters.""" self.prepare_method(method) @@ -402,7 +404,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): else: self.headers = CaseInsensitiveDict() - def prepare_body(self, data, files, _json=None): + def prepare_body(self, data, files, json=None): """Prepares the given HTTP body data.""" # Check if file, fo, generator, iterator. @@ -413,9 +415,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): content_type = None length = None - if _json is not None: + if json is not None: content_type = 'application/json' - data = json.dumps(_json) + body = json_dumps(json) is_stream = all([ hasattr(data, '__iter__'), @@ -442,7 +444,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if files: (body, content_type) = self._encode_files(files, data) else: - if data and _json is None: + if data and json is None: body = self._encode_params(data) if isinstance(data, basestring) or hasattr(data, 'read'): content_type = None @@ -452,7 +454,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): self.prepare_content_length(body) # Add content-type if it wasn't explicitly provided. - if (content_type) and (not 'content-type' in self.headers): + if content_type and ('content-type' not in self.headers): self.headers['Content-Type'] = content_type self.body = body diff --git a/requests/sessions.py b/requests/sessions.py index 7942447f..c5ad0060 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -430,7 +430,7 @@ class Session(SessionRedirectMixin): headers = headers, files = files, data = data or {}, - json = json or {}, + json = json, params = params or {}, auth = auth, cookies = cookies,