Overwrite django rest default validation errors handler - python-3.x

I am using django-rest for my back-end and want to overwrite default errors for fields.
My current code looks like this.
class DeckSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = (
"id",
"title",
"get_absolute_url",
"description",
"price",
"image",
"category_id",
"category",
"title"
)
extra_kwargs = {
'title': {"error_messages": {"required": "Title cannot be empty"}},
'image': {"error_messages": {"required": "Image cannot be empty"},}
}
After writing these 2 kwargs i realised i would just be repeating something that could be solved by code.
By default the serializer validation returns this when the field is missing {title:"This field is required"}.
Is there any way that i can overwrite the current message so it can display directly the name_of_the_field + my_message . Example {title: Title is required}
I am not looking on how to write custom error message for a single field , im looking on how to write generic costum messages for every field that for example is missing or null.

We can achieve it by writing a custom exception handler.
Here is how a custom response might look like:
{
"status_code": 400,
"type": "ValidationError",
"message": "Bad request syntax or unsupported method",
"errors": [
"username: This field may not be null.",
"email: This field may not be null.",
"ticket number: This field may not be null."
]
}
We have to create a file: exception_handler.py in our project directory with the code that follows; I use utils for this kind of purposes. You can also put this code anywhere you like, but I prefer to have it in a separated file dedicated for this purpose.
from http import HTTPStatus
from rest_framework import exceptions
from rest_framework.views import Response, exception_handler
def api_exception_handler(exception: Exception, context: dict) -> Response:
"""Custom API exception handler."""
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exception, context)
# Only alter the response when it's a validation error
if not isinstance(exception, exceptions.ValidationError):
return response
# It's a validation error, there should be a Serializer
view = context.get("view", None)
serializer = view.get_serializer_class()()
errors_list = []
for key, details in response.data.items():
if key in serializer.fields:
label = serializer.fields[key].label
help_text = serializer.fields[key].help_text
for message in details:
errors_list.append("{}: {}".format(label, message))
elif key == "non_field_errors":
for message in details:
errors_list.append(message)
else:
for message in details:
errors_list.append("{}: {}".format(key, message))
# Using the description's of the HTTPStatus class as error message.
http_code_to_message = {v.value: v.description for v in HTTPStatus}
error_payload = {
"status_code": 0,
"type": "ValidationError",
"message": "",
"errors": [],
}
# error = error_payload["error"]
status_code = response.status_code
error_payload["status_code"] = status_code
error_payload["message"] = http_code_to_message[status_code]
error_payload["errors"] = errors_list
# Overwrite default exception_handler response data
response.data = error_payload
return response
The main idea comes from here, but I changed it to my needs. change it as you see fit.
Don't forget to set it as your default exception handler in you settings.py file:
REST_FRAMEWORK["EXCEPTION_HANDLER"] = "utils.exception_handler.api_exception_handler";

Related

Using requests_auth_aws_sigv4 "Patch" showing the error

Here am using the AWSSigV4 library to request the API.
Am using the "Get" API getting response 200.
But using the "Patch" API getting response 400 and content showing as the b'{"message":"1 validation error detected: Value null at \'clientToken\' failed to satisfy constraint: Member must not be null"}'
and the header error is 'x-amzn-ErrorType': 'ValidationException'
The code I used is
import requests, json, os
from requests_auth_aws_sigv4 import AWSSigV4
path = "~/sfs.json"
full_path = os.path.expanduser(path)
with open(full_path, 'r') as j:
contents = json.loads(j.read())
accesskey = contents['credentials']['accessKeyId']
secretkey = contents['credentials']['secretAccessKey']
session_token = contents['credentials']['sessionToken']
data = {"fulfillmentStatus": "COMPLETE", "overrideItems": [{"itemId": "089f845c38-c3b3026f69", "unitDetails": [{"quantity": 1, "status": "SHIPPED", "trackingUpdate": {"trackingId": "trackingID23456789", "carrierCode": "usps"}}]}]}
value=json.dumps(data)
print(data)
print(value)
aws_auth =AWSSigV4('Service',
aws_access_key_id=accesskey,
aws_secret_access_key=secretkey,
aws_session_token=session_token
)
r = requests.request("Patch", "https://yyyyyyyyyy.execute-api.us-east-1.amazonaws.com/prod/fulfillmentManagers/xxxxxxxxxx/fulfillments/7Z1LUF9yFBaN/override", auth=aws_auth, data=value)

How can i fix AWS python event key problem

I have a problem with my AWS python code.
I am trying to send a post request to my code on AWS but I have key problems
My code on AWS
import json
import random
def lambda_handler(event, context):
name = surname = birthDate = favoriteFilm = password = ""
indexList = keysArray = []
setParams(event, name, surname, birthDate, favoriteFilm)
fillArray(keysArray, name, surname, birthDate, favoriteFilm)
arrayLength = len(keysArray)
while len(password)<6:
index = getRandomRangeIndex(arrayLength)
if index in indexList:
continue
password = password + keysArray[index]
indexList.append(index)
return {
'statusCode': 200,
'body': json.dumps(password)
}
def setParams(event, name, surname, birthDate, favoriteFilm):
name = event['first_name']
surname = event['last_name']
birthDate = event['d_o_b']
favoriteFilm = event['favorite_film']
def fillArray(keysArray, name, surname, birthDate, favoriteFilm):
for names in name.split():
keysArray.append(names)
keysArray.append(surname)
for dates in birthDate.split('-'):
keysArray.append(dates)
for films in favoriteFilm.split():
keysArray.append(films)
def getRandomRangeIndex(arrayLength):
return random.randint(0, arrayLength-1)
My Postman request header
{
"first_name": "John",
"last_name": "Smith",
"d_o_b": "1985-12-04",
"favorite_film": "Back to the Future"
}
My problem log
[ERROR] KeyError: 'first_name'
Traceback (most recent call last):
File "/var/task/password.py", line 7, in lambda_handler
setParams(event, name, surname, birthDate, favoriteFilm)
File "/var/task/password.py", line 24, in setParams
name = event['first_name']
I am not able to find any solution. How can I fix this problem? Thank you.
When you submit your json though api gateway, the event object that your function receives is different than what you submit. The format is shown in aws docs.
In your case the event will be something like this:
{
"resource": "/test",
"path": "/test",
"httpMethod": "POST",
#
# a banch of data removed for length
#
"body": "{\n \"first_name\": \"John\",\n \"last_name\": \"Smith\",\n \"d_o_b\": \"1985-12-04\",\n \"favorite_film\": \"Back to the Future\"\n}",
"isBase64Encoded": false
}
Thus, to get your actual data and you have to parse body you can use python's ast. You body is not json string, thus need to use ast:
import json
import random
import ast
def lambda_handler(event, context):
# overwrite event to keep using event in later parts of your code
event = ast.literal_eval(event['body'])
#....

How do i test except Errors using side_effect in python unittest mock?

I am a junior developer and trying to write some unittests for our API endpoints. Below is the class that i am testing and the actual test that runs without any issue. (But i am still hesitant that it's hitting my methods). My question is how can i improve my test and also make sure it covers exception(in this case ValueError, SystemError, Exception) by using side_effects(or any better suggestions) from python mock? I read python mock documentation but still can't figure out how to improve and importantly test exceptions.
we use flask microframework, python3.x,
--- Class that i am testing:
#USER_MOD.route('', methods=[HttpMethods.HTTP_POST])
#jwt_required
def create_user():
"""
Private end point for creating users of the system
:return: json
"""
response = ""
try:
logger_obj.info("Request : POST : Create User: {} ".format(request.base_url))
# validating input request
if ValidationUtils.validate_request(request, "CREATE_USER"):
logger_obj.debug("Request Validation Successful")
response = UserService().create_new_user(request)
logger_obj.debug("User Created: {} ".format(response))
return jsonify(response), HTTPStatus.OK
except ValueError as error:
logger_obj.error("ValueError create_user() : {}".format(error))
response = ApplicationUtils.create_api_response(status=ResponseConstants.MSG_ERROR, message=str(error))
return jsonify(response), HTTPStatus.BAD_REQUEST
except SystemError as error:
logger_obj.error("SystemError create_user() : {}".format(error))
response = ApplicationUtils.create_api_response(status=ResponseConstants.MSG_ERROR, message=str(error))
return jsonify(response), HTTPStatus.INTERNAL_SERVER_ERROR
except Exception as error:
logger_obj.error("Exception create_user() : {}".format(error))
response = ApplicationUtils.create_api_response(status=ResponseConstants.MSG_ERROR, message=str(error))
return jsonify(response), HTTPStatus.UNAUTHORIZED
finally:
logger_obj.info("Response : POST : Create User: {} : {}".format(request.base_url, response))
--- Test for above class:
class UserApiTests(DataTestCase): //(or i can use unittest.TestCase)
def setUp(self):
os.environ.update(self.config.to_env())
self.flask_app = make_flask_app()
self.client = self.flask_app.test_client()
self.flask_app.testing = True
#patch('usermgmt.app.services.user_service.UserService.create_new_user')
def test_create_user(self, mock_create_new_user):
# Given
mock_create_new_user.return_value.status_code = 200
mock_create_new_user.return_value = {
"status": "SUCCESS"
}
data_to_post = {
"name": "Test2",
"email": "new-user2#entity.com",
"entity_id": 1,
"is_active": True,
"product_roles": [
{"product_id": 1, "role_id": 4},
{"product_id": 2, "role_id": 4}
],
}
# When
response = self.client.post('/api/usermgmt/users', data=json.dumps(data_to_post), headers={
"Authorization": "Bearer {}".format(get_jwt(identity=self), "Content-Type: application/json")
})
data = response.data
json_data = json.loads(data)
# Then
self.assertEqual(response.status_code, 200)
self.assertEqual(json_data['status'], "SUCCESS")
During my practice I've found that approach quite good.
class TestCreateUser(TestCase):
def test_works_in_correct_case(self):
# equal to your test_create_user
#patch("usermgmt.app.services.user_service.UserService.create_new_user")
def test_handles_value_error_in_create_new_user(self, mock_create_new_user):
mock_create_new_user.side_effect = ValueError
# Your preparation flow here
response = self.client.post('/api/usermgmt/users') # simplified call here is just an example
self.assertEqual(response.status_code, 400)
# check if response body is what you've expected, etc.

How to mock internal function's side effect

HI I have a simple function. Which internally calls db but during local testing it can't connect DB so for that I have wrote specific exception and I want do side effect for same but it's not working.
Here is my code
def support_issue_contact(message, message_headers):
# Check if reporter_email_address is present in message
logger = message_headers.get('logger')
reporter_email = message.get('reporter_email_address', None)
if reporter_email is None:
return None
elif validators.email(reporter_email):
# look up message in our contact table to see if it exists
rule = {
"lookup_documents": [{
"document_type": "hub",
"hub_table": "hubs",
"entity": "contact",
"lookup_key_path": "reporter_email_address"
}]
}
try:
retrieve_documents(message, rule)
except StaleEventException:
return None
# Catch the retrieve_documents function if no 'Item' exists in the response it gets from Dynamo
# If no 'Item' exists then no contact exists so it should create a new contact
except KeyError as valid_exception:
if 'Item' in valid_exception.args:
# validate context to see if it is on 'Item'
contact_message = {
"business_key": reporter_email,
"source_system": 'gsac',
"source_system_id": reporter_email,
"source_sequence": message.get('source_sequence'),
"email": reporter_email,
"full_name": message.get('reporter_display_name', ''),
"customer_domain":
derive_customer_domain(reporter_email),
"status": "ACTIVE",
"id": reporter_email
}
return {
"payload": contact_message,
"schema": "it-bdm/contact-schema-v3.5.json",
"type": "avi:hydra-gsac:contact:upsert",
}
elif ('payload' in valid_exception.args) or ('satellite_name' in valid_exception.args):
# This means that the item exists within the hubs so we don't want to do anything
return None
else:
raise valid_exception
# All other exceptions should be raised
except Exception as e:
logger.error(e.__str__())
raise e
else:
return None
And I want that retrieve_documents function should raise CustomKeyError so I wrote these way and both are not working.
class SupportIssueContactTest(unittest.TestCase):
raw_event = parse_json_file(os.path.join(DIR_TEST_DATA, 'support-issue', 'support_issue.json'))
transformed_event = parse_json_file(os.path.join(DIR_TEST_DATA, 'support-issue', 'transformed_support_issue.json'))
def test_support_issue_contact_context(self):
with mock.patch('src.datavault_helper.retrieve_documents') as retrieve_documents_mock:
retrieve_documents_mock.side_effect = CustomKeyError()
assert self.transformed_event == support_issue_contact(message=self.raw_event, message_headers={'logger': config.logger})
#mock.patch('src.datavault_helper.retrieve_documents')
def test_support_issue_contact_decorator(self, retrieve_documents_mock):
retrieve_documents_mock.side_effect = CustomKeyError()
assert self.transformed_event == support_issue_contact(message=self.raw_event,
message_headers={'logger': config.logger})
I figured out the answer.
def test_support_issue_contact_context(self):
with mock.patch('path_of_function_where_it_is_called_for_mock') as retrieve_documents_mock:
retrieve_documents_mock.side_effect = CustomKeyError()
assert self.transformed_event == support_issue_contact(message=self.raw_event, message_headers={'logger': config.logger})

How do I serialise a nested dictionary with Marshmallow?

I'm new to marshmallow and flask etc.
I'm trying to learn by creating an API that consumes a jsonified python dictionary. The dictionary contains nested dictionaries like this. It also contains a few Null items.
{
"TITLE": "XXX",
"NAME": "Mongoose",
"TIME": "0430",
"USED": null,
"DESCRIPTION": "",
"WEAPONS": {
"HEAT": "Israel",
"RADAR": "Flat",
"CONV": {
"S": true,
"N": false,
"A": false
},
},
}
I simply want to consume this back into a dictionary type. Something like this on the POST action
fields_schema = FieldSchema(many=True)
field_schema = FieldSchema()
json_data = request.get_json(force=True)
if not json_data:
return {'message': 'No input data provided'}, 400
# Validate and deserialize input
try:
data = field_schema.load(json_data)
except ValidationError as e:
return e.messages, 422
Where data would simply be a nested dictionary.
It is defining the schema class that is causing me problems.
From what I can tell, when defining the schema, marshmallow doesnt have a JSON type and when I use fields.Dict I get the following error:
{
"meta": [
"Missing data for required field."
],
"TITLE": [
"Unknown field."
etc...
I'm not sure whether I should be looking at using a nested Schema or whether I am totally over complicating things.
My fields_schema currently looks like this:
class FieldSchema(ma.Schema):
id = fields.Integer()
meta = fields.Dict(required=True)
Any pointers would be greatly appreciated
If you're going to validate the nested object, you can use Marshmallow's fields.Nested functionality.
Using their example
from marshmallow import Schema, fields, pprint
class UserSchema(Schema):
name = fields.String()
email = fields.Email()
created_at = fields.DateTime()
class BlogSchema(Schema):
title = fields.String()
author = fields.Nested(UserSchema)
user = User(name="Monty", email="monty#python.org")
blog = Blog(title="Something Completely Different", author=user)
result = BlogSchema().dump(blog)
pprint(result)
# {'title': u'Something Completely Different',
# 'author': {'name': u'Monty',
# 'email': u'monty#python.org',
# 'created_at': '2014-08-17T14:58:57.600623+00:00'}}
Your need to define a schema from the root document though. Something like
class Widget(Schema):
TITLE = fields.String()
NAME = fields.String()
# ...
WEAPONS = fields.Nested(Weapon)
class Weapon(Schema):
HEAT = fields.String()
# ...
might get you going.

Resources