Merge pull request #3383 from StasDeep/feature/issue-3382

[MRG+1] Add ability to use FormRequest in contracts
This commit is contained in:
Daniel Graña 2018-09-05 11:21:26 -03:00 committed by GitHub
commit ae8a0dc77c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 40 additions and 7 deletions

View File

@ -86,8 +86,11 @@ override three methods:
.. method:: Contract.adjust_request_args(args)
This receives a ``dict`` as an argument containing default arguments
for :class:`~scrapy.http.Request` object. Must return the same or a
modified version of it.
for request object. :class:`~scrapy.http.Request` is used by default,
but this can be changed with the ``request_cls`` attribute.
If multiple contracts in chain have this attribute defined, the last one is used.
Must return the same or a modified version of it.
.. method:: Contract.pre_process(response)

View File

@ -49,16 +49,22 @@ class ContractsManager(object):
def from_method(self, method, results):
contracts = self.extract_contracts(method)
if contracts:
request_cls = Request
for contract in contracts:
if contract.request_cls is not None:
request_cls = contract.request_cls
# calculate request args
args, kwargs = get_spec(Request.__init__)
args, kwargs = get_spec(request_cls.__init__)
kwargs['callback'] = method
for contract in contracts:
kwargs = contract.adjust_request_args(kwargs)
# create and prepare request
args.remove('self')
# check if all positional arguments are defined in kwargs
if set(args).issubset(set(kwargs)):
request = Request(**kwargs)
request = request_cls(**kwargs)
# execute pre and post hooks in order
for contract in reversed(contracts):
@ -94,6 +100,7 @@ class ContractsManager(object):
class Contract(object):
""" Abstract class for contracts """
request_cls = None
def __init__(self, method, *args):
self.testcase_pre = _create_testcase(method, '@%s pre-hook' % self.name)

View File

@ -3,11 +3,12 @@ from unittest import TextTestResult
from twisted.python import failure
from twisted.trial import unittest
from scrapy import FormRequest
from scrapy.spidermiddlewares.httperror import HttpError
from scrapy.spiders import Spider
from scrapy.http import Request
from scrapy.item import Item, Field
from scrapy.contracts import ContractsManager
from scrapy.contracts import ContractsManager, Contract
from scrapy.contracts.default import (
UrlContract,
ReturnsContract,
@ -24,6 +25,15 @@ class ResponseMock(object):
url = 'http://scrapy.org'
class CustomFormContract(Contract):
name = 'custom_form'
request_cls = FormRequest
def adjust_request_args(self, args):
args['formdata'] = {'name': 'scrapy'}
return args
class TestSpider(Spider):
name = 'demo_spider'
@ -100,13 +110,20 @@ class TestSpider(Spider):
"""
pass
def custom_form(self, response):
"""
@url http://scrapy.org
@custom_form
"""
pass
class InheritsTestSpider(TestSpider):
name = 'inherits_demo_spider'
class ContractsManagerTest(unittest.TestCase):
contracts = [UrlContract, ReturnsContract, ScrapesContract]
contracts = [UrlContract, ReturnsContract, ScrapesContract, CustomFormContract]
def setUp(self):
self.conman = ContractsManager(self.contracts)
@ -207,6 +224,12 @@ class ContractsManagerTest(unittest.TestCase):
self.assertFalse(self.results.failures)
self.assertTrue(self.results.errors)
def test_form_contract(self):
spider = TestSpider()
request = self.conman.from_method(spider.custom_form, self.results)
self.assertEqual(request.method, 'POST')
self.assertIsInstance(request, FormRequest)
def test_inherited_contracts(self):
spider = InheritsTestSpider()