From 0c8198ada1672ef8a52bad11ce6e7e2679d01f40 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Sat, 2 Jul 2016 18:44:13 +0200 Subject: [PATCH] implement PUT, PATCH and DELETE for webservice calls (#12416) --- tests/test_workflows.py | 33 +++++++++++++++++++++++++++++++++ tests/test_wscall.py | 36 +++++++++++++++++++++++++++++++++++- wcs/wf/wscall.py | 10 ++++++++-- wcs/wscalls.py | 27 ++++++++++++++++++--------- 4 files changed, 94 insertions(+), 12 deletions(-) diff --git a/tests/test_workflows.py b/tests/test_workflows.py index 81998a1..5343094 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -910,6 +910,39 @@ def test_webservice_call(pub): assert qs['evalme'] == [formdata.get_display_id()] assert qs['str'] == ['abcd'] + item = WebserviceCallStatusItem() + item.method = 'DELETE' + item.post = False + item.url = 'http://remote.example.net/json' + pub.substitutions.feed(formdata) + item.perform(formdata) + assert http_requests.get_last('method') == 'DELETE' + + item = WebserviceCallStatusItem() + item.url = 'http://remote.example.net' + item.method = 'PUT' + item.post = False + item.post_data = {'str': 'abcd', 'one': '=1', + 'evalme': '=form_number', 'error':'=1=3'} + pub.substitutions.feed(formdata) + item.perform(formdata) + assert http_requests.get_last('url') == 'http://remote.example.net' + assert http_requests.get_last('method') == 'PUT' + payload = json.loads(http_requests.get_last('body')) + assert payload == {'one': 1, 'str': 'abcd', 'evalme': formdata.get_display_id()} + + item = WebserviceCallStatusItem() + item.url = 'http://remote.example.net' + item.method = 'PATCH' + item.post = False + item.post_data = {'str': 'abcd', 'one': '=1', + 'evalme': '=form_number', 'error':'=1=3'} + pub.substitutions.feed(formdata) + item.perform(formdata) + assert http_requests.get_last('url') == 'http://remote.example.net' + assert http_requests.get_last('method') == 'PATCH' + payload = json.loads(http_requests.get_last('body')) + assert payload == {'one': 1, 'str': 'abcd', 'evalme': formdata.get_display_id()} def test_timeout(pub): workflow = Workflow(name='timeout') diff --git a/tests/test_wscall.py b/tests/test_wscall.py index 50d4632..fc693fd 100644 --- a/tests/test_wscall.py +++ b/tests/test_wscall.py @@ -1,5 +1,4 @@ import json -import mock import pytest from wcs.qommon.http_request import HTTPRequest @@ -17,6 +16,7 @@ def pub(): pub.load_site_options() return pub + def teardown_module(module): clean_temporary_pub() @@ -48,6 +48,7 @@ def test_named_wscall(pub): assert 'bye' in NamedWsCall.keys() assert not 'hello_1' in NamedWsCall.keys() + def test_webservice_substitution_variable(pub): NamedWsCall.wipe() @@ -61,6 +62,7 @@ def test_webservice_substitution_variable(pub): variables = pub.substitutions.get_context_variables() assert variables['webservice'].hello_world == {'foo': 'bar'} + def test_webservice_auto_sign(pub): NamedWsCall.wipe() @@ -98,3 +100,35 @@ def test_webservice_post_with_no_payload(pub): wscall.request = {'method': 'POST', 'url': 'http://remote.example.net/json'} wscall.call() assert http_requests.get_last('body') is None + + +def test_webservice_post_put_patch(pub): + NamedWsCall.wipe() + + for method in ('POST', 'PUT', 'PATCH'): + wscall = NamedWsCall() + wscall.name = 'Hello world' + wscall.request = {'method': 'PUT', 'post_data': {'toto': 'coin'}, + 'url': 'http://remote.example.net/json'} + try: + wscall.call() + except: + pass + assert http_requests.get_last('url') == wscall.request['url'] + assert http_requests.get_last('method') == wscall.request['method'] + assert json.loads(http_requests.get_last('body')) == wscall.request['post_data'] + + +def test_webservice_delete(pub): + NamedWsCall.wipe() + + wscall = NamedWsCall() + wscall.name = 'Hello world' + wscall.request = {'method': 'DELETE', 'post_data': {'toto': 'coin'}, + 'url': 'http://remote.example.net/json'} + try: + wscall.call() + except: + pass + assert http_requests.get_last('url') == wscall.request['url'] + assert http_requests.get_last('method') == 'DELETE' diff --git a/wcs/wf/wscall.py b/wcs/wf/wscall.py index 2e8228b..9bf60e0 100644 --- a/wcs/wf/wscall.py +++ b/wcs/wf/wscall.py @@ -114,7 +114,7 @@ class WebserviceCallStatusItem(WorkflowStatusItem): @property def method(self): - if self._method in ('GET', 'POST'): + if self._method in ('GET', 'POST', 'PUT', 'PATCH', 'DELETE'): return self._method if self.post or self.post_data: return 'POST' @@ -156,7 +156,13 @@ class WebserviceCallStatusItem(WorkflowStatusItem): value=self.qs_data or {}, element_value_type=ComputedExpressionWidget) methods = collections.OrderedDict( - [('GET', _('GET')), ('POST', _('POST (JSON)'))]) + [ + ('GET', _('GET')), + ('POST', _('POST (JSON)')) + ('PUT', _('PUT (JSON)')), + ('PATCH', _('PATCH (JSON)')), + ('DELETE', _('DELETE')), + ]) if 'method' in parameters: form.add(RadiobuttonsWidget, '%smethod' % prefix, title=_('Method'), diff --git a/wcs/wscalls.py b/wcs/wscalls.py index 7f5593d..52adc64 100644 --- a/wcs/wscalls.py +++ b/wcs/wscalls.py @@ -23,8 +23,8 @@ import xml.etree.ElementTree as ET from quixote import get_publisher -from qommon.misc import (simplify, http_get_page, http_post_request, - get_variadic_url, JSONEncoder, json_loads) +import qommon.misc +from qommon.misc import (simplify, http_get_page, get_variadic_url, JSONEncoder, json_loads) from qommon.xml_storage import XmlStorableObject from qommon.form import (CompositeWidget, StringWidget, WidgetDict, ComputedExpressionWidget, RadiobuttonsWidget, CheckboxWidget) @@ -78,7 +78,7 @@ def call_webservice(url, qs_data=None, request_signature_key=None, payload = None # if post_data exists, payload is a dict built from it - if method == 'POST' and post_data: + if method in ('PATCH', 'PUT', 'POST') and post_data: payload = {} for (key, value) in post_data.items(): try: @@ -88,14 +88,14 @@ def call_webservice(url, qs_data=None, request_signature_key=None, # if formdata has to be sent, it's the payload. If post_data exists, # it's added in formdata['extra'] - if method == 'POST' and post_formdata: + if method in ('PATCH', 'PUT', 'POST') and post_formdata: if formdata: formdata_dict = formdata.get_json_export_dict() if payload is not None: formdata_dict['extra'] = payload payload = formdata_dict - if method == 'POST': + if method in ('PATCH', 'PUT', 'POST'): timeout = TIMEOUT if payload: payload = json.dumps(payload, cls=JSONEncoder, @@ -103,11 +103,14 @@ def call_webservice(url, qs_data=None, request_signature_key=None, # increase timeout for huge loads, one second every 65536 # bytes, to match a country 512kbps DSL line. timeout += len(payload) / 65536 - response, status, data, auth_header = http_post_request( - url, payload, headers=headers, timeout=timeout) + response, status, data, auth_header = qommon.misc._http_request( + url, method=method, body=payload, headers=headers, timeout=timeout) + elif method == 'DELETE': + response, status, data, auth_header = qommon.misc._http_request( + url, method='DELETE', headers=headers, timeout=TIMEOUT) else: response, status, data, auth_header = http_get_page( - url, headers=headers, timeout=TIMEOUT) + url, headers=headers, timeout=TIMEOUT) return (response, status, data) @@ -216,7 +219,13 @@ class WsCallRequestWidget(CompositeWidget): value=value.get('qs_data') or {}, element_value_type=ComputedExpressionWidget) methods = collections.OrderedDict( - [('GET', _('GET')), ('POST', _('POST (JSON)'))]) + [ + ('GET', _('GET')), + ('POST', _('POST (JSON)')) + ('PUT', _('PUT (JSON)')), + ('PATCH', _('PATCH (JSON)')), + ('DELETE', _('DELETE')), + ]) self.add(RadiobuttonsWidget, 'method', title=_('Method'), options=methods.items(), -- 2.1.4