Skip to content

Commit

Permalink
Robust Request Class for Penn APIs (#209)
Browse files Browse the repository at this point in the history
* Base structure for r_request class

* Lint

* Finished r_request class

* Added test cases

* Resolve response content bug

* Add TypeError
  • Loading branch information
judtinzhang authored Oct 3, 2023
1 parent ed9cd9d commit b786ebe
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 0 deletions.
Empty file added backend/tests/utils/__init__.py
Empty file.
68 changes: 68 additions & 0 deletions backend/tests/utils/test_r_request.py
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)
101 changes: 101 additions & 0 deletions backend/utils/r_request.py
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

0 comments on commit b786ebe

Please sign in to comment.