I am only beginner in flask. Trying to integrate marshmallow and webargs. It perfectly works in flask-restful Resource class. But when I use a simple flask route it does not work
routes.py
class UserAPI(Resource):
#use_args(UserSchema())
def post(self, *args):
print(args)
return 'success', 201
def get(self):
return '<h1>Hello</h1>'
#bp.route('/test/', methods=['POST'])
#use_kwargs(UserSchema())
def test2(*args, **kwargs):
print(args)
print(kwargs)
return 'success', 201
api.add_resource(UserAPI, '/', endpoint='user')
I've added error handler which is necessary when using use_args
from webargs.flaskparser import parser, abort
from webargs import core
#parser.error_handler
def webargs_validation_handler(error, req, schema, *, error_status_code, error_headers):
status_code = error_status_code or core.DEFAULT_VALIDATION_STATUS
abort(
400,
exc=error,
messages=error.messages,
)
That's what I'm getting when I make request to Resource endpoint what is normal
And that's what I'm getting when I make request to a simple flask route what is not normal
I want to be able to use both ways
Found answer in webargs docs :)
https://webargs.readthedocs.io/en/latest/framework_support.html#error-handling
from flask import jsonify
# Return validation errors as JSON
#app.errorhandler(422)
#app.errorhandler(400)
def handle_error(err):
headers = err.data.get("headers", None)
messages = err.data.get("messages", ["Invalid request."])
if headers:
return jsonify({"errors": messages}), err.code, headers
else:
return jsonify({"errors": messages}), err.code
Related
I'm trying to use the django rest framework to identify when an API Request is coming from my own website (in order to send an error to these requests).
views.py
from django.shortcuts import render
from django.http import JsonResponse
from rest_framework.request import Request as RESTRequest
def is_rest_request(request):
return isinstance(request, RESTRequest)
def payment(request, *args, **kwargs):
if is_rest_request(request):
return JsonResponse({"result": 502})
else:
return JsonResponse({"result": 209})
However, when I make the following request from an online python compiler:
import requests
x = requests.get('https://url.com/api/payment')
print(x.text)
I get this output: {"result": 209}, when I should be getting {"result": 502}
Any reasons why?
Title might be a little confusing.
Say I have an APIView with a post method. Inside the post method, I introduced a class that has its own method. In this case, it's a class that deals with uploading to S3, which is something I want to skip when running unittest.
class SomeView(APIView):
def post(self):
# do something here
input1 = some_process(payload_arg1)
input2 = some_other_process(payload_arg2)
uploader = S3Uploader()
s3_response = uploader.upload_with_aux_fxn(input1, input2)
if s3_response['status_code'] == 200:
# do something else
return Response('Good job I did it!', status_code=200)
else:
return Response('noooo you're horrible!', status_code=400)
Real code has different function calls and responses, obviously.
Now I need to mock that uploader and uploader.upload_with_aux_fxn so I don't actually call S3. How do I mock it?
I tried in my test script
from some_place import S3Uploader
class SomeViewTestCase(TestCase):
def setUp(self):
self.client = APIClient()
uploader_mock = S3Uploader()
uploader_mock.upload_support_doc = MagicMock(return_value={'status_code': 200, 'message': 'asdasdad'}
response = self.client.post(url, payload, format='multipart')
But I still triggered S3 upload (as file shows up in S3). How do I correctly mock this?
EDIT1:
My attempt to patch
def setUp(self):
self.factory = APIRequestFactory()
self.view = ViewToTest.as_view()
self.url = reverse('some_url')
#patch('some_place.S3Uploader', FakeUploader)
def test_uplaod(self):
payload = {'some': 'data', 'other': 'stuff'}
request = self.factory.post(self.url, payload, format='json')
force_authenticate(request, user=self.user)
response = self.view(request)
where the FakeUplaoder is
class FakeUplaoder(object):
def __init__(self):
pass
def upload_something(self, data, arg1, arg2, arg3):
return {'status_code': 200, 'message': 'unit test', 's3_path':
'unit/test/path.pdf'}
def downlaod_something(self, s3_path):
return {'status_code': 200, 'message': '', 'body': 'some base64
stuff'}
unfortunately this is not successful. I still hit the actual class
EDIT 2:
I'm using Django 1.11 and Python 2.7, in case people need this info
I guess the correct approach to it would be save the file within a model with FileField, and then connect Boto to handle upload in production scenario.
Take a good look at:
https://docs.djangoproject.com/en/2.2/ref/models/fields/#filefield
and
https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#model
this approach would preserve Django default behavior, making things more testable with Django's default test client.
Take a look at vcrpy. It records request to external API once and then replays answer every time you run your tests. No need to manually mock anything.
Here's an example of how I would mock that S3Uploader in an APITestCase.
from rest_framework import status
from unittest import mock
from unittest.mock import MagicMock
class SomeViewTestCase(APITestCase):
#mock.patch("path.to.view_file.S3Uploader")
def test_upload(self, s3_uploader_mock):
"""Test with mocked S3Uploader"""
concrete_uploader_mock = MagicMock(**{
"upload_with_aux_fxn__return_value": {"status_code": 200}
})
s3_uploader_mock.return_value = concrete_uploader_mock
response = self.client.post(url, payload, format='multipart')
self.assertEqual(response.status_code, status.HTTP_200_OK)
s3_uploader_mock.assert_called_once()
concrete_uploader_mock.upload_with_aux_fx.assert_called_once()
Try using MagicMock like below
from unittest import mock
from storages.backends.s3boto3 import S3Boto3Storage
class SomeTestCase(TestCase):
def setUp(self):
self.factory = APIRequestFactory()
self.view = ViewToTest.as_view()
self.url = reverse('some_url')
#mock.patch.object(S3Boto3Storage, '_save', MagicMock(return_value='/tmp/somefile.png'))
def test_uplaod(self):
payload = {'some': 'data', 'other': 'stuff'}
request = self.factory.post(self.url, payload, format='json')
force_authenticate(request, user=self.user)
response = self.view(request)
I am creating a simple rest api using django REST framework. I have successfully got the response by sending GET request to the api but since I want to send POST request, the django rest framework doesn't allow POST request by default.
As in image(below) only GET,HEAD, OPTIONS are allowed but not the POST request
The GET and POST methods inside of views.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from profiles_api import serializers
from rest_framework import status
# Create your views here.
class HelloApiView(APIView):
"""Test APIView"""
#Here we are telling django that the serializer class for this apiViewClass is serializer.HelloSerializer class
serializer_class = serializers.HelloSerializer
def get(self, request, format=None):
"""Retruns a list of APIViews features."""
an_apiview = [
'Uses HTTP methods as fucntion (get, post, patch, put, delete)',
'It is similar to a traditional Django view',
'Gives you the most of the control over your logic',
'Is mapped manually to URLs'
]
#The response must be as dictionary which will be shown in json as response
return Response({'message': 'Hello!', 'an_apiview': an_apiview})
def post(self,request):
"""Create a hello message with our name"""
serializer = serializer.HelloSerializer(data=request.data)
if serializer.is_valid():
name = serializer.data.get('name')
message = 'Hello! {0}'.format(name)
return Response({'message':message})
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
How to allow POST requests in django REST framework?
The problem with the code was, you have added the def post() after the return statement.
To solve, just correct your indentation level as below,
class HelloApiView(APIView):
def get(self, request, format=None):
return Response()
def post(self, request):
return Response()
I am trying to write unit test cases for flas api server.
Can someeone please suggest ow to get rid of auth.login_required.
Tried mocking auth, but of no use.
with test_client its not hitting code block too.
api.py
from flask import Flask
from flask.ext.httpauth import HTTPBasicAuth
app = Flask(__name__)
auth = HTTPBasicAuth()
#app.route('/')
#auth.login_required
def index():
print "In index"
response.status_code = 200
return response
Tried following http://flask.pocoo.org/docs/0.12/testing/
from src.api import app
from unittest import TestCase
class TestIntegrations(TestCase):
def setUp(self):
self.app = app.test_client()
def test_thing(self):
response = self.app.get('/')
Can someone please help ??
There are two ways to do so - first is to disable authorization in tests:
// in your test module
from api import app, auth
import unittest
#auth.verify_password
def verify_password(user, password):
"""Overwrite password check to always pass.
This works even if we send no auth data."""
return True
Another approach is to actually send the auth headers from tests (this way you can also test your authorization system):
from api import app
from base64 import b64encode
import unittest
class ApiClient:
"""Performs API requests."""
def __init__(self, app):
self.client = app.test_client()
def get(self, url, **kwargs):
"""Sends GET request and returns the response."""
return self.client.get(url, headers=self.request_headers(), **kwargs)
def request_headers(self):
"""Returns API request headers."""
auth = '{0}:{1}'.format('user', 'secret')
return {
'Accept': 'application/json',
'Authorization': 'Basic {encoded_login}'.format(
encoded_login=b64encode(auth.encode('utf-8')).decode('utf-8')
)
}
class TestIntegrations(unittest.TestCase):
def setUp(self):
self.app = ApiClient(app)
def test_thing(self):
response = self.app.get('/')
print(response.data)
The ApiClient helper can also define post, delete methods which will be similar to get.
The full source code with examples is here.
How to send customised response if the unauthorised credentials were provided in django rest.
class StockList(APIView):
permission_classes = [IsAuthenticated]
def get(self,request):
stocks = Stock.objects.all()
serializer = StockSerializer(stocks,many=True)
return Response({'user': serializer.data,'post': serializer.data})
def post(self):
pass
Here when I Hit url by invalid credentials i get 401 error on development server.
But i want to send customised response on client using json.
any suggestions are welcomed.
Thank you.
You can use a custom exception handler to customized response of api exception.
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
# Now add the HTTP status code to the response.
if response is not None:
response.data['status_code'] = response.status_code
if response.status_code == 401:
response.data['some_message'] = "Some custom message"
return response
Then in setting.py you have to add your custom error handler
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.path.to.custom_exception_handler'
}
Refs: docs