-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Robust Request Class for Penn APIs (#209)
* Base structure for r_request class * Lint * Finished r_request class * Added test cases * Resolve response content bug * Add TypeError
- Loading branch information
1 parent
ed9cd9d
commit b786ebe
Showing
3 changed files
with
169 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
from json.decoder import JSONDecodeError | ||
from unittest.mock import patch | ||
|
||
from django.test import TestCase | ||
|
||
from utils.r_request import RRequest | ||
|
||
|
||
def raise_decode_error(): | ||
raise JSONDecodeError("Invalid JSON data", "invalid_json", 0) | ||
|
||
|
||
class RRequestTestCase(TestCase): | ||
def setUp(self): | ||
self.url = "https://pennlabs.org" | ||
self.json = {"data": "data"} | ||
self.rrequest = RRequest() | ||
|
||
@patch("requests.request") | ||
def test_successful_request(self, mock_response): | ||
mock_response.return_value.status_code = 200 | ||
response = self.rrequest.request("get", self.url) | ||
self.assertEqual(200, response.status_code) | ||
|
||
@patch("requests.request") | ||
def test_unsuccessful_request(self, mock_response): | ||
mock_response.return_value.status_code = 400 | ||
mock_response.return_value.content = "Bad Error" | ||
response = self.rrequest.request("post", self.url, json=self.json) | ||
self.assertEqual(400, response.status_code) | ||
self.assertEqual("Bad Error", response.content) | ||
|
||
@patch("requests.request") | ||
def test_bad_json(self, mock_response): | ||
mock_response.return_value.status_code = 200 | ||
mock_response.return_value.json = raise_decode_error | ||
response = self.rrequest.delete(self.url, json=self.json) | ||
self.assertEqual(200, response.status_code) | ||
|
||
@patch("requests.request") | ||
def test_get_request(self, mock_response): | ||
mock_response.return_value.status_code = 200 | ||
response = self.rrequest.get(self.url) | ||
self.assertEqual(200, response.status_code) | ||
|
||
@patch("requests.request") | ||
def test_post_request(self, mock_response): | ||
mock_response.return_value.status_code = 200 | ||
response = self.rrequest.post(self.url, json=self.json) | ||
self.assertEqual(200, response.status_code) | ||
|
||
@patch("requests.request") | ||
def test_patch_request(self, mock_response): | ||
mock_response.return_value.status_code = 200 | ||
response = self.rrequest.patch(self.url) | ||
self.assertEqual(200, response.status_code) | ||
|
||
@patch("requests.request") | ||
def test_put_request(self, mock_response): | ||
mock_response.return_value.status_code = 200 | ||
response = self.rrequest.put(self.url, json=self.json) | ||
self.assertEqual(200, response.status_code) | ||
|
||
@patch("requests.request") | ||
def test_delete_request(self, mock_response): | ||
mock_response.return_value.status_code = 200 | ||
response = self.rrequest.delete(self.url, json=self.json) | ||
self.assertEqual(200, response.status_code) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
from enum import Enum | ||
from json.decoder import JSONDecodeError | ||
|
||
import requests | ||
|
||
|
||
class Method(str, Enum): | ||
POST = "post" | ||
GET = "get" | ||
PATCH = "patch" | ||
PUT = "put" | ||
DELETE = "delete" | ||
|
||
|
||
class RRequest: | ||
""" | ||
Robust wrapper around Python requests library | ||
Primary use case is to interact with unstable APIs where responses | ||
return one-off malformed data or failures | ||
""" | ||
|
||
NUM_RETRIES = 2 | ||
|
||
def __init__(self, num_retries=NUM_RETRIES): | ||
self.num_retries = num_retries | ||
|
||
def get(self, *args, **kwargs): | ||
return self.request(Method.GET, *args, **kwargs) | ||
|
||
def post(self, *args, **kwargs): | ||
return self.request(Method.POST, *args, **kwargs) | ||
|
||
def patch(self, *args, **kwargs): | ||
return self.request(Method.PATCH, *args, **kwargs) | ||
|
||
def put(self, *args, **kwargs): | ||
return self.request(Method.PUT, *args, **kwargs) | ||
|
||
def delete(self, *args, **kwargs): | ||
return self.request(Method.DELETE, *args, **kwargs) | ||
|
||
def request( | ||
self, | ||
method, | ||
url, | ||
params=None, | ||
data=None, | ||
headers=None, | ||
cookies=None, | ||
files=None, | ||
auth=None, | ||
timeout=None, | ||
allow_redirects=True, | ||
proxies=None, | ||
hooks=None, | ||
stream=None, | ||
verify=None, | ||
cert=None, | ||
json=None, | ||
): | ||
response = self.__default_response() | ||
|
||
for _ in range(self.num_retries): | ||
response = requests.request( | ||
method, | ||
url, | ||
params=params, | ||
data=data, | ||
headers=headers, | ||
cookies=cookies, | ||
files=files, | ||
auth=auth, | ||
timeout=timeout, | ||
allow_redirects=allow_redirects, | ||
proxies=proxies, | ||
hooks=hooks, | ||
stream=stream, | ||
verify=verify, | ||
cert=cert, | ||
json=json, | ||
) | ||
|
||
if response.status_code != 200: | ||
continue | ||
|
||
try: | ||
response.json() | ||
except (TypeError, JSONDecodeError): | ||
continue | ||
return response | ||
|
||
if not response.content: | ||
response.content = "RRequest: Default Error" | ||
|
||
return response | ||
|
||
def __default_response(self): | ||
response = requests.models.Response | ||
response.status_code = 400 | ||
return response |