I want to define custom error handling for a Flask-restful API
The flask custom error(FlaskGenericException) is raised but is not handled by restplus errorhandler. it just throws a 500 Internal Server error.
Not sure why the dict defined inside exceptions.py is not thrown as api response.
Expected Result
{'user': 'Lionel Messi', 'company': 'Soccer Brazil','message': 'A generic error'}
Here are my files that I am using to test this:
app.py
import json,os
import logging
import sys
import requests
import config
from os import environ
from flask import Flask, request, jsonify, Response
from flask_jwt_extended import (JWTManager, jwt_required)
from flask_restplus import Api, Resource, reqparse, fields, inputs
from cal_Test import calTest
from exceptions import FlaskGenericException
# Setup Flask Server
app = Flask(__name__,root_path=os.path.join(os.getcwd(), './'))
app.config.from_object(config.Config)
api = Api(app=app,
version='0.1',
title="Test Cal API",
doc="/swagger/",
description='TestCal = Cal Test')
ns = api.namespace('/', description='Cal Test Api')
#api.errorhandler(FlaskGenericException)
def handle_error(error):
response = jsonify(error.to_dict())
response.status_code = error.status_code
return response
resource_parser = reqparse.RequestParser()
resource_parser.add_argument('test', type=str, required=True)
resource_flds = api.model('Resource', {
'test': fields.String(required = True),
})
#ns.route('/test', methods=['POST'])
class TestCal(Resource):
# #jwt_required
#ns.doc(body=resource_flds)
#ns.expect(resource_flds, validate=True)
def post(self):
request_data = resource_parser.parse_args()
caltst = calTest()
result = caltst.cal_test(request_data)
response = app.response_class(
response=json.dumps(result),
status=200,
mimetype='application/json'
)
return response
if __name__ == "__main__":
app.run(debug=True)
cal_Test.py
import pandas as pd
import pyodbc
import scipy.stats as st
import numpy as np
import pandas as pd
from scipy.stats import norm
from flask import Flask, request, jsonify
import os
from exceptions import FlaskGenericException
class calTest:
def __init__(self):
self.test_name = 'Hi! I am test'
def cal_test(self,reqdata):
if self.test_name == reqdata['test']:
return "Successfull"
else:
raise FlaskGenericException('A generic error', status_code=404, payload={'user': 'Lionel Messi', 'company': 'Soccer Brazil'})
exceptions.py
class FlaskGenericException(Exception):
status_code = 500 # default unless overridden
def __init__(self, message, status_code=None, payload=None):
Exception.__init__(self)
self.message = message
if status_code is not None:
self.status_code = status_code
self.payload = payload
def to_dict(self):
rv = dict(self.payload or ())
rv['message'] = self.message
return rv
Ran into the same issue, I ended up using the abort method from Flask. I don't know if it fits your use case, but give it a try.
from flask import abort
def cal_test(self,reqdata):
if self.test_name == reqdata['test']:
return "Successfull"
else:
abort(404, "A generic error")
Related
I am getting this error when I run my python script to make a call to my API:
{'message': 'The method is not allowed for the requested URL.'}
I cannot figure out why I am getting this error as I am using the exact code as the tutorial I am following.
Here is the call to my API:
import requests
BASE = "http://127.0.0.1:5000/"
response = requests.put(BASE + "video/1", {"likes": 10})
print(response.json())
And here is my API:
from flask import Flask
from flask_restful import Api, Resource, reqparse
app = Flask(__name__)
api = Api(app)
video_put_args = reqparse.RequestParser()
video_put_args.add_argument("name",type=str, help="t")
video_put_args.add_argument("views",type=int, help="t")
video_put_args.add_argument("likes",type=int, help="t")
videos = {}
class Video(Resource):
def get(self, video_id):
return videos[video_id]
def put(self, video_id):
args=video.args.parse_args()
return {videoid:args}
api.add_resource(Video, "/video/<int:video_id>")
if __name__ == "__main__":
app.run(debug=True)
Any help would be appreciated, thanks
This may be a newbie question. I am not able to override the greetings message in this simple 2 files FastAPI project. Could you please tell me what I might have done wrong? Thanks a lot for your help.
greetings_service.py
from fastapi import Depends
from fastapi_utils.cbv import cbv
from fastapi_utils.inferring_router import InferringRouter
router = InferringRouter()
def get_msg():
return "Original Message"
#cbv(router)
class GreetingsService:
#router.get("/")
async def greet(self, msg: str = Depends(get_msg)):
return f"Hello from FastAPI {msg}"
main.py
from fastapi import FastAPI
from starlette.testclient import TestClient
import greetings_service
app = FastAPI()
app.include_router(greetings_service.router)
def get_new_msg():
return "New Message"
//Tried this, doesn't work
#app.dependency_overrides["get_msg"] = get_new_msg()
//These 2 lines doesn't work too
app.dependency_overrides["get_msg"] = get_new_msg()
greetings_service.router.dependency_overrides_provider = app
client = TestClient(app)
res = client.get("/")
print(res.content) #"Hello from FastAPI Original Message" :(
The issue is with this:
app.dependency_overrides["get_msg"] = get_new_msg()
You are passing the dependency as string instead of the actual dependency.
Something like this would work:
from fastapi import FastAPI
from starlette.testclient import TestClient
import greetings_service
app = FastAPI()
app.include_router(greetings_service.router)
def get_new_msg():
return "New Message"
app.dependency_overrides[greetings_service.get_msg] = get_new_msg
client = TestClient(app)
res = client.get("/")
print(res.content)
The long story short is I am working on building a server that serves as something as a chat bot. The server uses google dialog flow. Right now I have an endpoint exposed that allows me to talk to my server, when I hit that endpoint, google auth, as well as google dialog flow gets called. I am attempting to mock the response of dialog flow while leaving the actual server to respond to the network call. As of now my test looks like this.
This is my base test file:
import unittest
import mock
class BaseTest(unittest.TestCase, object):
def __init__(self, *args, **kwargs):
super(BaseTest, self).__init__(*args, *kwargs)
def auto_patch(self, patch_target):
patcher = mock.patch(patch_target)
patched = patcher.start()
self.addCleanup(patcher.stop)
return patched
This is my test file:
import json
import uuid
from os import path
from tests.base_test import BaseTest
from agent.api_service import app
import requests_mock
import pytest
from hamcrest import assert_that, has_items, equal_to
CWD = path.dirname(path.realpath(__file__))
class TestAudio(BaseTest):
def test__interact__full_no_stt(self):
payload = json.load(open("tests/json_payloads/test__interact__full_audio.json"))
u_session_id = str(uuid.uuid1())
payload["session_id"] = u_session_id
#mock a 500 back from STT
with open("tests/json_payloads/stt_500.json", "r") as issues_file:
mock_response = issues_file.read()
with requests_mock.Mocker() as m:
m.register_uri('POST', 'https://speech.googleapis.com/v1/speech:recognize', text=mock_response)
request, response = app.test_client.post("/agent_service/interact", data=json.dumps(payload))
self.assertEqual(200, response.status)
This is my google stt file:
import json
import requests
from agent.exceptions import GoogleSTTException
from agent.integrations.google.google_auth_service import get_auth_token
from agent.integrations.google.google_stt_request import GoogleSTTRequest
from agent.integrations.google.google_stt_response import GoogleSTTResponse
def speech_to_text(audio_string):
try:
google_stt_request = GoogleSTTRequest(audio_string).to_payload()
request_headers = dict()
request_headers['Authorization'] = 'Bearer ' + get_auth_token()
request_headers['Content-Type'] = 'application/json'
url = 'https://speech.googleapis.com/v1/speech:recognize'
google_response = requests.post(url, data=json.dumps(google_stt_request), headers=request_headers)
response = GoogleSTTResponse(google_response.json())
return response
except Exception as e:
raise GoogleSTTException('Received an error invoking google stt {}'.format(e))
Does anyone have any ideas on how I can mock the response from the google stt call, without touching the google auth call or the server call itself? I have tried a handful of things and so far no luck. I either end up mocking nothing, or both the google stt and auth call.
So I ended up moving away from the original implementation, but this is what got me there.
#responses.activate
def test__interact__full_no_stt(self):
payload = json.load(open("tests/json_payloads/test__interact__full_audio.json"))
u_session_id = str(uuid.uuid1())
payload["session_id"] = u_session_id
#mock a 500 back from STT
responses.add(responses.POST,
'https://speech.googleapis.com/v1/speech:recognize',
json={'error': 'broken'}, status=500)
request, response = app.test_client.post("/agent_service/interact", data=json.dumps(payload))
self.assertEqual(200, response.status)
result = response.json
Responses makes this much easier, just be sure to include the annotation at the top of the test.
I am trying to generate a swagger documentation for the API I am building in flask however when I execute below code I get very staring error, What I am missing here ?
This is what I have tried :
try:
from flask import Flask, g, request
from flask_restful import Resource, Api
from flask_limiter.util import get_remote_address
from flask_limiter import Limiter
from flasgger import Swagger
from flasgger.utils import swag_from
from flask_restful_swagger import swagger
from flask_httpauth import HTTPTokenAuth
import os
import json
except Exception as e:
print("some modules are missing {}".format(e))
app = Flask(__name__)
api = Api(app)
limiter = Limiter(app, key_func=get_remote_address)
limiter.init_app(app)
api = swagger.docs(Api(app), apiVersion='0.1', api_spec_url='/doc')
auth = HTTPTokenAuth(scheme='Token')
tokens = {
'awserftggyjkk34)ghtyrjrhhye34nnmw': 'cadmin',
'bwsosjhee(dhj345gtyuioplsertbsjkl': 'dadmin',
'mnchthas(sjklertyusvfgmbshdls234h': 'eadmin'
}
#auth.verify_token
def verify_token(token):
if token in tokens:
g.current_user = tokens[token]
return True
return False
class defr(Resource):
decorators = [limiter.limit("100/day")]
#swagger.model
#swagger.operation(notes='my notes ntes')
#auth.login_required
def currentuser(self):
return "Hello, %s!" % g.current_user
api.add_resource(defr, '/user')
error :
File "C:\Users\codamd\AppData\Local\Continuum\anaconda3\lib\site-packages\flask_restful_swagger\swagger.py", line 294, in extract_operations
for method in [m.lower() for m in resource.methods]:
TypeError: 'NoneType' object is not iterable
Any help would be great
The problem here is that you've used a custom named method in your defr class. The Flask-RESTful documentation specifies defining HTTP methods in your Resource class e.g.
class defr(Resource):
decorators = [limiter.limit("100/day")]
#swagger.model
#swagger.operation(notes='my notes ntes')
#auth.login_required
def get(self):
return "Hello, %s!" % g.current_user
I'm new to Python and I try to implement REST API service on Flask. I faced with issue related to testing of my code. My Flask app looks something like that:
from flask import Flask, jsonify, make_response, request
from flask_httpauth import HTTPBasicAuth
import os
auth = HTTPBasicAuth()
#auth.get_password
def get_password(username):
if username == os.environ['SERVICE_KEY']:
return os.environ['SERVICE_PASS']
return None
#auth.error_handler
def unauthorized():
return make_response(jsonify({'error': 'Unauthorized access'}), 403)
app = Flask(__name__)
tweets = [
{
'id': 1,
'profileId': '1',
'message': 'My test tweet'
},
{
'id': 2,
'profileId': '1',
'message': 'Second tweet!'
}
]
#app.route('/api/v1/tweets', methods=['GET'])
#auth.login_required
def get_tweets():
return jsonify({'tweets': tweets}), 200
#app.errorhandler(404)
#auth.login_required
def not_found(error):
return make_response(jsonify({'error': 'Not found'}), 404)
if __name__ == '__main__':
app.run(debug=True)
And here is my test (currently it is only for not_found method):
import unittest
from app import app
class TestApp(unittest.TestCase):
def setUp(self):
self.app = app.test_client()
def test_404(self):
rv = self.app.get('/i-am-not-found')
self.assertEqual(rv.status_code, 404)
if __name__ == '__main__':
unittest.main()
But when I try to run test, it fails due to I get 'Unauthorized access' response:
>python test.py
F
======================================================================
FAIL: test_404 (__main__.TestApp)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test.py", line 25, in test_404
self.assertEqual(rv.status_code, 404)
AssertionError: 403 != 404
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)
Which approach for testing route-methods are more correct to handle authorization? And how can I fix that failed test?
You need to create a custom header that includes your auth details and send it along with your request. Something like this:
from base64 import b64encode
...
headers = {'Authorization': 'Basic ' + b64encode("{0}:{1}".format(username, password))}
rv = self.app.get('/i-am-not-found', headers=headers)
...
import unittest
from app import app
class TestApp(unittest.TestCase):
def setUp(self):
self.app = app.test_client()
def test_404(self):
headers = {
'Authorization': 'Basic ' + b64encode("username:password")
}
rv = self.app.get('/i-am-not-found', headers=headers)
self.assertEqual(rv.status_code, 404)
if __name__ == '__main__':
unittest.main()
Your username and password is sent in the form username:password but is base64 encoded. If expanding this there are ways to make this simpler such as extracting into a function to always pass the header and externalising username/password for testing.
EDIT: Additionally I think you should be returning a 401 code here. 401 is usually used when credentials are incorrect, 403 is usually used when you have successfully authenticated yourself but do not have access to a resource. A very simplified example being logged into Facebook but being restricted from accessing another person's photo that is marked as private.