How to return customized JSON response for an error in graphene / django-graphene? - graphene-python

I want to add status field to error response, so instead of this:
{
"errors": [
{
"message": "Authentication credentials were not provided",
"locations": [
{
"line": 2,
"column": 3
}
]
}
],
"data": {
"viewer": null
}
}
It should be like this:
{
"errors": [
{
"status": 401, # or 400 or 403 or whatever error status suits
"message": "Authentication credentials were not provided",
"locations": [
{
"line": 2,
"column": 3
}
]
}
],
"data": {
"viewer": null
}
}
I found out that I only can change message by raising Exception inside resolver: raise Error('custom error message'), but how to add field?
Code example:
class Query(UsersQuery, graphene.ObjectType):
me = graphene.Field(SelfUserNode)
def resolve_me(self, info: ResolveInfo):
user = info.context.user
if not user.is_authenticated:
# but status attr doesn't exist...
raise GraphQLError('Authentication credentials were not provided', status=401)
return user

Update the default GraphQLView with the following:
from graphene_django.views import GraphQLView as BaseGraphQLView
class GraphQLView(BaseGraphQLView):
#staticmethod
def format_error(error):
formatted_error = super(GraphQLView, GraphQLView).format_error(error)
try:
formatted_error['context'] = error.original_error.context
except AttributeError:
pass
return formatted_error
urlpatterns = [
path('api', GraphQLView.as_view()),
]
This will look for the context attribute in any exceptions raised. If it exists, it'll populate the error with this data.
Now you can create exceptions for different use cases that populate the context attribute. In this case you want to add the status code to errors, here's an example of how you'd do that:
class APIException(Exception):
def __init__(self, message, status=None):
self.context = {}
if status:
self.context['status'] = status
super().__init__(message)
You'd use it like this:
raise APIException('Something went wrong', status=400)

I didn't found a way to solve your problem int the way that you propose, otherwise i extend the LoginRequiredMixin class like this:
class LoginRequiredMixin:
def dispatch(self, info, *args, **kwargs):
if not info.user.is_authenticated:
e = HttpError(HttpResponse(status=401, content_type='application/json'), 'Please log in first')
response = e.response
response.content = self.json_encode(info, [{'errors': [self.format_error(e)]}])
return response
return super().dispatch(info, *args, **kwargs)
class PrivateGraphQLView(LoginRequiredMixin, GraphQLView):
schema=schema
and in your url:
from django.views.decorators.csrf import csrf_exempt
from educor.schema import PrivateGraphQLView
url(r'^graphql', csrf_exempt(PrivateGraphQLView.as_view(batch=True)))
you can't see the status with the graphiql but in your client you can get it in the headers or you could modify this line to add in the response response.content = self.json_encode(info, [{'errors': [self.format_error(e)]}])
. Hope it helps anyway i'll leave you another possible solution https://github.com/graphql-python/graphene-django/issues/252

Related

How to change priority of WTForms validators?

tl;dr: I need to change the order in which WTForms validators validate the user input. How do I do that?
Details:
Flask code:
class SampleForm(Form):
user_email = user_email_field
...
#api.route('/sample-route')
class ClassName(Resource):
#api.expect(sample_payload)
#api.marshal_with(sample_response)
def post(self):
form = SampleForm(formdata=MultiDict(api.payload))
if not form.validate():
return {"form_errors": form.errors}, 400
...
WTForms validation field:
user_email_field = EmailField('Email Address',[
validators.InputRequired(Errors.REQUIRED_FIELD),
validators.Length(min=5, max=256),
validators.Email(Errors.INVALID_EMAIL_ADDRESS),
])
Problem is, user_email is checked by validators in the wrong order. I send a request with the following body:
{
"user_email": ""
}
I get this response:
{
"form_errors": {
"user_email": [
"'' is too short"
]
}
}
As you see, despite being 2nd in the list of validators, validators.Length() kicks in before everything else.
If I comment it out in the validation field like that:
user_email_field = EmailField('Email Address',[
validators.InputRequired(Errors.REQUIRED_FIELD),
# validators.Length(min=5, max=256),
validators.Email(Errors.INVALID_EMAIL_ADDRESS),
])
then the exact same request will yield a desired response:
{
"errors": null,
"success": null,
"form_errors": {
"user_email": [
"REQUIRED_FIELD"
]
}
}
However, this is not a working solution because, of course, then eMail won't be checked for its length.
Question:
How do I change the priority of these validators? How do I make WTForms always check the user input with validators.InputRequired() FIRST and with validators.Length() SECOND and not the other way around?
As per your question:
You can use custom validators for that purpose. Then you can change the order of validation by if, elif, else logic within.
class Test(Form):
test_field = StringField('Test_Field')
submit = SubmitField()
def validate_test_field(self, test_field):
try:
if len(test_field.data) == 0:
raise wtforms.validators.ValidationError('Input is required for this field!')
elif len(test_field.data) not in range(5, 257):
raise wtforms.validators.ValidationError(f'Error in input length.')
except TypeError as e:
raise wtforms.validators.ValidationError('Input is required for this field!')
Check the documentation for custom validators here

Custom format for serializer ValidationError

Iv'e got a custom object-level validator in one of my serializers:
def validate(self, data):
# some checks on token
# set token to True or False
if not token:
raise serializers.ValidationError(
{
"status": "failed",
"message": _("token is not valid"),
}
)
return data
What I expect to get as an output is this:
{
"status": "failed",
"message": "token is not valid"
}
However, what I'm actually getting is:
{
"status": [
"failed"
],
"message": [
"token is not valid"
]
}
Is there anyway to achieve what I'm looking for?
Create a custom ValidatorError class:
from rest_framework import serializers, status
from rest_framework.exceptions import APIException
class PlainValidationError(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = _("Invalid input.")
default_code = "invalid"
def __init__(self, detail=None, code=None):
if not isinstance(detail, dict):
raise serializers.ValidationError("Invalid Input")
self.detail = detail
Instead of using serializers.ValidationError use your custom ValidationError class:
def validate(self, data):
# some checks on token
# set token to True or False
if not token:
raise PlainValidationError(
{
"status": "failed",
"message": _("token is not valid"),
}
)
return data
It's not perfect but it does the job for me.

pytest json.decoder.JSONDecodeError

List item
Hi,
I need to pytest this function
def lambda_handler(event, context):
message = json.loads(event['Records'][0]['Sns']['Message'])
But it failed by json error
def test_lambda_handler():
event = {
"Records": [
{
"Sns" : { "Message" : "test" }
}
]
}
response = fw_init.lambda_handler( event,"")
JSONDecodeError("Expecting value", s, err.value) from None
E json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
Function loads() deserializes JSON from string. You trying to decode "Message" field value as JSON.
lambda_handler function's first argument is a JSON-formatted string, according to AWS documentation.
You need to pass an serialized data to lambda_handler function:
response = fw_init.lambda_handler(json.dumps(event) ,"")
In function lambda_handler() you need to deserialize data first, and then get field value:
def lambda_handler(event, context):
data = json.loads(event)
message = data['Records'][0]['Sns']['Message']

Passing diffrent time slots in same field Django Rest framework

I want to send 3 diffrent time slots in a field ,But do not getting the diffrent slots,but getting error-->"message": "JSON parse error - Expecting property name enclosed in double quotes: line 3 column 9 (char 27)",
models.py
class Slot(models.Model):
time=models.TimeField(auto_now=False, auto_now_add=False,null=True,blank=True)
def __str__(self):
return str(self.time)
views.py
class Available(viewsets.ViewSet):
def create(self, request):
try:
data=request.data
timings=data.get('schedule')
for i in range(3):
time_obj=Slot()
time_obj.time=timings[i]['time']
time_obj.save()
return Response({"message":"Data delivered","success": True}, status=status.HTTP_200_OK)
except Exception as error:
traceback.print_exc()
return Response({"message": str(error), "success": False}, status=status.HTTP_200_OK)
postman response
{
"schedule":{
{
"time":"12:00"
},
{
"time":"12:10"
},
{
"time":"15:00"
}
}
}
looks like the data sent is not valid JSON it should be something like
{
"schedule":[
{
"time":"12:00"
},
{
"time":"12:10"
},
{
"time":"15:00"
}
]
}

How I can return my custom json file instead of default json file that generates spring boot?

I have a rest controller for authorization:
#RestController
class AuthController {
#PostMapping("/sign-up")
fun signUp(#RequestBody signUpRequest: SignUpRequest): ResponseEntity<String> {
some code here..
}
}
The signUp method gets SignUpRequest model as a request body. SignUpRequest model is:
enum class Role {
#JsonProperty("Student")
STUDENT,
#JsonProperty("Tutor")
TUTOR
}
data class SignUpRequest(
val role: Role,
val email: String,
val password: String
)
When I make /sign-up post request with JSON:
{
"role": "asdf",
"email": "",
"password": ""
}
It returns me an answer that were generated by spring boot:
{
"timestamp": "2020-02-12T05:45:42.387+0000",
"status": 400,
"error": "Bad Request",
"message": "JSON parse error: Cannot deserialize value of type `foo.bar.xyz.model.Role` from String \"asdf\": not one of the values accepted for Enum class: [Student, Tutor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `foo.bar.xyz.model.Role` from String \"asdf\": not one of the values accepted for Enum class: [Student, Tutor]\n at [Source: (PushbackInputStream); line: 3, column: 10] (through reference chain: foo.bar.xyz.model.SignUpRequest[\"role\"])",
"path": "/sign-up"
}
Question is: How I can return my custom JSON instead of that default generated JSON?
I want to return my custom JSON, like:
{
"result": "Invalid user data are given",
"errors": [
{
"fieldName": "ROLE",
"text": "Given role does not exist"
},
{
"fieldName": "EMAIL",
"text": "EMAIL is empty"
}
]
}
I suggest you to create ErrorContrller that generates custom json map as response. Then when you will catch an error in sign-up method, call ErrorContrllers method.
You can find info from this link
Finally I found out a solution. You should create a class that annotates #ControllerAdvice, and make a method that annotates #ExceptionHandler.
#ControllerAdvice
class HttpMessageNotReadableExceptionController {
#ExceptionHandler(HttpMessageNotReadableException::class)
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
fun handleException(
exception: HttpMessageNotReadableException
): PostSignUpResponseError {
val errors = mutableListOf<PostSignUpResponseErrorItem>()
errors.add(
PostSignUpResponseErrorItem(
fieldNamePost = "Role",
text = "Given role does not exist"
)
)
return PostSignUpResponseError(
result = "Invalid user data are given",
errors = errors
)
}
}
where PostSignUpResponseErrorItem and PostSignUpResponseError are:
data class PostSignUpResponseError(
val result: String,
val errors: List<PostSignUpResponseErrorItem>
)
class PostSignUpResponseErrorItem(
val fieldNamePost: PostSignUpRequestFieldName,
val text: String
)
Anyway, I still don't know how to attach this thing to a certain PostMapping method.

Resources