diff --git a/scrapy/http/__init__.py b/scrapy/http/__init__.py index f04a9d3e5..4b2f7b33f 100644 --- a/scrapy/http/__init__.py +++ b/scrapy/http/__init__.py @@ -10,6 +10,7 @@ from scrapy.http.headers import Headers from scrapy.http.request import Request from scrapy.http.request.form import FormRequest from scrapy.http.request.rpc import XmlRpcRequest +from scrapy.http.request.json_request import JSONRequest from scrapy.http.response import Response from scrapy.http.response.html import HtmlResponse diff --git a/scrapy/http/request/json_request.py b/scrapy/http/request/json_request.py new file mode 100644 index 000000000..0fdd2ddf1 --- /dev/null +++ b/scrapy/http/request/json_request.py @@ -0,0 +1,28 @@ +""" +This module implements the JSONRequest class which is a more convenient class +(than Request) to generate JSON Requests. + +See documentation in docs/topics/request-response.rst +""" + +import json + +from scrapy.http.request import Request + + +class JSONRequest(Request): + def __init__(self, *args, **kwargs): + if 'method' not in kwargs: + kwargs['method'] = 'POST' + + data = kwargs.pop('data', {}) + kwargs['body'] = json.dumps(data) + super(JSONRequest, self).__init__(*args, **kwargs) + self.headers.setdefault(b'Content-Type', b'application/json') + + def replace(self, *args, **kwargs): + """ Create a new Request with the same attributes except for those + given new values. """ + + kwargs.pop('body', None) + return super(JSONRequest, self).replace(*args, **kwargs) diff --git a/tests/test_http_request.py b/tests/test_http_request.py index 58326a384..3f2e4f521 100644 --- a/tests/test_http_request.py +++ b/tests/test_http_request.py @@ -2,6 +2,7 @@ import cgi import unittest import re +import json import six from six.moves import xmlrpc_client as xmlrpclib @@ -9,7 +10,7 @@ from six.moves.urllib.parse import urlparse, parse_qs, unquote if six.PY3: from urllib.parse import unquote_to_bytes -from scrapy.http import Request, FormRequest, XmlRpcRequest, Headers, HtmlResponse +from scrapy.http import Request, FormRequest, XmlRpcRequest, JSONRequest, Headers, HtmlResponse from scrapy.utils.python import to_bytes, to_native_str @@ -1147,5 +1148,57 @@ class XmlRpcRequestTest(RequestTest): self._test_request(params=(u'pas£',), encoding='latin1') +class JSONRequestTest(RequestTest): + request_class = JSONRequest + default_method = 'POST' + default_headers = {b'Content-Type': [b'application/json']} + + def test_body(self): + r1 = self.request_class(url="http://www.example.com/") + self.assertEqual(r1.body, '{}') + + r2 = self.request_class(url="http://www.example.com/", body=b"") + self.assertEqual(r2.body, '{}') + + data = { + 'name': 'value', + } + r3 = self.request_class(url="http://www.example.com/", data=data) + self.assertEqual(r3.body, json.dumps(data)) + + r4 = self.request_class(url="http://www.example.com/", body='body1', data=data) + self.assertEqual(r3.body, json.dumps(data)) + + def test_replace(self): + """Test Request.replace() method""" + r1 = self.request_class("http://www.example.com") + hdrs = Headers(r1.headers) + hdrs[b'key'] = b'value' + r2 = r1.replace(body="New body", headers=hdrs) + + # body will not be replaced + self.assertEqual(r1.body, r2.body) + self.assertEqual(r1.url, r2.url) + self.assertEqual((r1.headers, r2.headers), (self.default_headers, hdrs)) + + # Empty attributes (which may fail if not compared properly) + r3 = self.request_class("http://www.example.com", meta={'a': 1}, dont_filter=True) + r4 = r3.replace(url="http://www.example.com/2", meta={}, dont_filter=False) + self.assertEqual(r4.url, "http://www.example.com/2") + self.assertEqual(r4.meta, {}) + assert r4.dont_filter is False + + data1 = { + 'name': 'value1', + } + data2 = { + 'name': 'value2', + } + r5 = self.request_class("http://www.example.com", data=data1) + r6 = r5.replace(url="http://www.example.com/2", data=data2) + self.assertNotEqual(r5.body, r6.body) + self.assertEqual((r5.body, r6.body), (json.dumps(data1), json.dumps(data2))) + + if __name__ == "__main__": unittest.main()