Skip to content

Commit

Permalink
Merge pull request #40 from hardcoretech/feat/dj-payload
Browse files Browse the repository at this point in the history
feat(decorator): support application/json
  • Loading branch information
CJHwong authored Jun 20, 2023
2 parents bddf7fa + 461361b commit 3324a2e
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 9 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Changelog
Changes:

- DEPRECATIONS: The decorators support to Django version 1.x and 2.x is dropped. The minimum supported version is Django 3.0.

- [feature] `dsv` decorator support loading payload from Django WSGIRequest for Content-Type: application/json

3.2.0
-----
Expand Down
2 changes: 1 addition & 1 deletion data_spec_validator/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '3.2.0'
__version__ = '3.3.0'
12 changes: 11 additions & 1 deletion data_spec_validator/decorator/decorators.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
from functools import wraps
from typing import Dict, List, Union

Expand Down Expand Up @@ -102,8 +103,17 @@ def _collect_data(method, req_qp, req_data) -> Dict:
# TODO: Don't care about the query_params if it's not a dict or the payload is in list.
return req_data

def _get_dj_payload(request):
content_type = request.headers.get('Content-Type')
if content_type == 'application/json':
try:
return request.body and json.loads(request.body) or {}
except Exception:
raise ParseError('Unable to parse request body as JSON')
return request.POST

if is_wsgi_request or is_asgi_request:
data = _collect_data(req.method, req.GET, req.POST)
data = _collect_data(req.method, req.GET, _get_dj_payload(req))
else:
data = _collect_data(req.method, req.query_params, req.data)

Expand Down
28 changes: 25 additions & 3 deletions test/test_decorator_dj.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import itertools
import json
import unittest
from unittest.mock import patch

from parameterized import parameterized, parameterized_class

from data_spec_validator.decorator import dsv, dsv_request_meta
from data_spec_validator.decorator.decorators import ParseError
from data_spec_validator.spec import DIGIT_STR, LIST_OF, ONE_OF, STR, Checker, dsv_feature

from .utils import is_django_installed, make_request
Expand Down Expand Up @@ -112,13 +114,15 @@ def decorated_func(self, req, *_args, **_kwargs):
view = _View(request=fake_request)
view.decorated_func(fake_request, **kwargs)

@parameterized.expand(['PUT', 'PATCH', 'DELETE'])
def test_query_params_with_data(self, method):
@parameterized.expand(itertools.product(['POST', 'PUT', 'PATCH', 'DELETE'], [True, False]))
def test_query_params_with_data(self, method, is_json):
# arrange
qs = 'q_a=3&q_b=true&d.o.t=dot&array[]=a1&array[]=a2&array[]=a3'
payload = {'test_a': 'TEST A', 'test_f[]': [1, 2, 3]}

fake_request = make_request(self.request_class, method=method, data=payload, qs=qs)
if is_json:
payload = json.dumps(payload).encode('utf-8')
fake_request = make_request(self.request_class, method=method, data=payload, qs=qs, is_json=is_json)

kwargs = {'test_b': 'TEST_B', 'test_c.d.e': 'TEST C.D.E'}

Expand All @@ -141,6 +145,24 @@ def decorated_func(self, req, *_args, **_kwargs):
view = _View(request=fake_request)
assert view.decorated_func(fake_request, **kwargs)

@parameterized.expand(['POST', 'PUT', 'PATCH', 'DELETE'])
def test_query_params_with_data_in_invalid_json_format(self, method):
payload = 'invalid json data'

fake_request = make_request(self.request_class, method=method, data=payload, is_json=True)

class _ViewSpec:
pass

class _View(View):
@dsv(_ViewSpec)
def decorated_func(self, req, *_args, **_kwargs):
return True

view = _View(request=fake_request)
with self.assertRaises(ParseError):
assert view.decorated_func(fake_request)

def test_req_list_data_with_no_multirow_set(self):
# arrange
payload = [{'test_a': 'TEST A1'}, {'test_a': 'TEST A2'}, {'test_a': 'TEST A3'}]
Expand Down
10 changes: 7 additions & 3 deletions test/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def is_drf_installed():
return True


def make_request(cls, path='/', method='GET', user=None, headers=None, data=None, qs=None):
def make_request(cls, path='/', method='GET', user=None, headers=None, data=None, qs=None, is_json=False):
assert is_django_installed()

from django.core.handlers.asgi import ASGIRequest
Expand Down Expand Up @@ -62,11 +62,15 @@ def make_request(cls, path='/', method='GET', user=None, headers=None, data=None
req.read() # trigger RawPostDataException and force DRF to load data from req.POST
req.META.update(
{
'CONTENT_TYPE': 'application/x-www-form-urlencoded',
'CONTENT_TYPE': 'application/json' if is_json else 'application/x-www-form-urlencoded',
'CONTENT_LENGTH': len(str(data)),
}
)
req.POST = data
if is_json:
req._body = data
req.POST = {}
else:
req.POST = data

if is_drf_installed() and cls is not WSGIRequest and cls is not ASGIRequest:
from rest_framework.parsers import FormParser
Expand Down

0 comments on commit 3324a2e

Please sign in to comment.