I want to understand how I can catch an HTTPException raised by flask.abort while using a test_request_context in a test for the calling method only.
# example.py
import flask
#api.route('/', methods=['POST'])
def do_stuff():
param_a = get_param()
return jsonify(a=param_a)
# check if request is json, return http error codes otherwise
def get_param():
if flask.request.is_json():
try:
data = flask.request.get_json()
a = data('param_a')
except(ValueError):
abort(400)
else:
abort(405)
# test_example.py
from app import app # where app = Flask(__name__)
from example import get_param
import flask
def test_get_param(app):
with app.test_request_context('/', data=flask.json.dumps(good_json), content_type='application/json'):
assert a == get_param()
In the get_param method above, I try to abort if the is_json() or the get_json() fail. To test this, I pass test_request_context without a content_type and, based on this blog and this answer, I tried adding a nested context manager like so:
# test_example.py
from app import app # where app = Flask(__name__)
from example import get_param
from werkzeug.exceptions import HTTPException
import flask
def test_get_param_aborts(app):
with app.test_request_context('/', data=flask.json.dumps('http://example', 'nope'), content_type=''):
with pytest.raises(HTTPException) as httperror:
get_param()
assert 405 == httperror
but I get a assert 405 == <ExceptionInfo for raises contextmanager> assertion error.
Can someone please explain this and suggest a way to test the abort in this get_param method?
Update:
Based on #tmt's answer, I modified the test. However, even though the test passes, while debugging I notice that the two assertions are never reached!
# test_example.py
from app import app # where app = Flask(__name__)
from example import get_param
from werkzeug.exceptions import HTTPException
import flask
def test_get_param_aborts(app):
with app.test_request_context('/', data=flask.json.dumps('http://example', 'nope'), content_type=''):
with pytest.raises(HTTPException) as httperror:
get_param() # <-- this line is reached
assert 405 == httperror.value.code
assert 1 ==2
httperror is an instance of ExceptionInfo which is pytest's own class that describes the exception. Once it happens, httperror would also contain value property which would be the instance of the HTTPException itself. If my memory is correct HTTPException contains code property that equals to the HTTP status code so you can use it to do the assertion:
# test_example.py
from app import app
from example import get_param
from werkzeug.exceptions import HTTPException
import flask
def test_get_param_aborts(app):
with app.test_request_context('/', data=flask.json.dumps(), content_type=''):
with pytest.raises(HTTPException) as httperror:
get_param()
assert 405 == httperror.value.code
Notes:
get_param() needs to be called within pytest.raises() context manager.
The assertion needs to be done outside of the context manager because once an exception is raised the context ends.
I don't know if pytest.raise is your typo or if it really existed in older versions of pytest. AFAIK it should be pytest.raises.
Related
I have developed a flask application that returns some text from OPEN-AI by giving some inputs.
But unfortunately the rest API in my application returns 404 error.
Here is the code:
from crypt import methods
from warnings import catch_warnings
from flask import Flask,request
from flask_cors import CORS
import flask
import openai
from flask_restful import Api,Resource
import base64
import json
#Init
app = Flask(__name__)
CORS(app)
api = Api(app)
app.run(host='0.0.0.0',port=8080)
#OPENAI CREDENTIALS
openai.api_key = ""
#Functions
class advert(Resource):
def post(self):
try:
request_body=request.json
A=request_body["data"]
adprompt = "Write an advertisement for " + A
response = openai.Completion.create(
engine="text-davinci-002",
prompt=adprompt,
temperature=0.7,
max_tokens=70,
top_p=1.0,
n=1
)
json_advert = json.loads(str(response))
advert_output = json_advert['choices'][0]['text']
to_return= json_advert = json.loads(str(advert_output))
return to_return,200
except:
return ({"ERROR":"Error Occured"}),500
#Mapping
api.add_resource(advert,'/data',methods=['POST'])
if __name__=='__main__':
app.run(debug=True)
This is the response i get:
192.168.1.21 - - [24/Nov/2022 11:52:59] "POST /data HTTP/1.1" 404 -
I've tried changing the port and endpoints, nothing helped .
How to solve this.
Your problem is at this line,
app.run(host='0.0.0.0',port=8080)
take it out, then add the parameters into the last line,
if __name__=='__main__':
app.run(debug=True, host='0.0.0.0',port=8080)
A bit more explanation, when the code hits app.run, it actually starts the server and not running the line api.add_resource. Hence, no path was configured.
I am new to flask and I want to return 401 error, I am not using authentication, I am simply using a parameter api_key to check whether the user is authenticated.
I've tried abort(401) and return false, but it returns error 400 'Bad Request', is this the default way it returns a 401. Or is there a different way to do this?
My Code:
import flask
from flask import jsonify, request, Response
from flask_cors import CORS, cross_origin
from GoogleNews import GoogleNews
googlenews = GoogleNews()
googlenews.set_lang('en')
googlenews.set_period('1d')
googlenews.set_encode('utf-8')
app=flask.Flask(__name__)
cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'
#cross_origin()
#app.route('/', methods=["GET"])
def return_news():
API_KEY = request.args['api_key']
if API_KEY == "xyz":
googlenews.get_news('TECHNOLOGY')
return jsonify(googlenews.result(True)[0:4])
else:
return Response(response="Unauthorized", status=401)
Thankyou
instead, try flask.Response object.
from flask import Flask, Response
app = Flask(__name__)
#app.route("/abort")
def abort():
return Response(response="Unauthorized", status=401)
app.run()
without the actual code it is hard to tell, but I think it is not using the flask abort() but the python internal abort() function.
Check the code below:
import flask
app = flask.Flask("aborter")
#app.route("/abort")
def abort():
return flask.abort(401)
if __name__ == '__main__':
app.run()
Happy Coding
I am trying to catch unhandled exceptions at global level. So somewhere in main.py file I have the below:
#app.exception_handler(Exception)
async def exception_callback(request: Request, exc: Exception):
logger.error(exc.detail)
But the above method is never executed. However, if I write a custom exception and try to catch it (as shown below), it works just fine.
class MyException(Exception):
#some code
#app.exception_handler(MyException)
async def exception_callback(request: Request, exc: MyException):
logger.error(exc.detail)
I have gone through Catch exception type of Exception and process body request #575. But this bug talks about accessing request body. After seeing this bug, I feel it should be possible to catch Exception.
FastAPI version I am using is: fastapi>=0.52.0.
Thanks in advance :)
Update
There are multiple answers, I am thankful to all the readers and authors here.
I was revisiting this solution in my application. Now I see that I needed to set debug=False, default it's False, but I had it set to True in
server = FastAPI(
title=app_settings.PROJECT_NAME,
version=app_settings.VERSION,
)
It seems that I missed it when #iedmrc commented on answer given by #Kavindu Dodanduwa.
In case you want to capture all unhandled exceptions (internal server error), there's a very simple way of doing it. Documentation
from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import Response
app = FastAPI()
async def catch_exceptions_middleware(request: Request, call_next):
try:
return await call_next(request)
except Exception:
# you probably want some kind of logging here
return Response("Internal server error", status_code=500)
app.middleware('http')(catch_exceptions_middleware)
Make sure you place this middleware before everything else.
You can do something like this. It should return a json object with your custom error message also works in debugger mode.
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
#app.exception_handler(Exception)
async def validation_exception_handler(request, err):
base_error_message = f"Failed to execute: {request.method}: {request.url}"
# Change here to LOGGER
return JSONResponse(status_code=400, content={"message": f"{base_error_message}. Detail: {err}"})
Adding a custom APIRoute can be also be used to handle global exceptions. The advantage of this approach is that if a http exception is raised from the custom route it will be handled by default Starlette's error handlers:
from typing import Callable
from fastapi import Request, Response, HTTPException, APIRouter, FastAPI
from fastapi.routing import APIRoute
from .logging import logger
class RouteErrorHandler(APIRoute):
"""Custom APIRoute that handles application errors and exceptions"""
def get_route_handler(self) -> Callable:
original_route_handler = super().get_route_handler()
async def custom_route_handler(request: Request) -> Response:
try:
return await original_route_handler(request)
except Exception as ex:
if isinstance(ex, HTTPException):
raise ex
logger.exception("uncaught error")
# wrap error into pretty 500 exception
raise HTTPException(status_code=500, detail=str(ex))
return custom_route_handler
router = APIRouter(route_class=RouteErrorHandler)
app = FastAPI()
app.include_router(router)
Worked for me with fastapi==0.68.1.
More on custom routes: https://fastapi.tiangolo.com/advanced/custom-request-and-route/
It is a known issue on the Fastapi and Starlette.
I am trying to capture the StarletteHTTPException globally by a following simple sample.
import uvicorn
from fastapi import FastAPI
from starlette.requests import Request
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.responses import JSONResponse
app = FastAPI()
#app.exception_handler(StarletteHTTPException)
async def exception_callback(request: Request, exc: Exception):
print("test")
return JSONResponse({"detail": "test_error"}, status_code=500)
if __name__ == "__main__":
uvicorn.run("test:app", host="0.0.0.0", port=1111, reload=True)
It works. I open the browser and call the endpoint / and try to access http://127.0.0.1:1111/ , it will return the json {"detail":"test_error"} with HTTP code "500 Internal Server Error" .
However, when I only changed StarletteHTTPException to Exception in the #app.exception_handler,
import uvicorn
from fastapi import FastAPI
from starlette.requests import Request
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.responses import JSONResponse
app = FastAPI()
#app.exception_handler(Exception)
async def exception_callback(request: Request, exc: Exception):
print("test")
return JSONResponse({"detail": "test_error"}, status_code=500)
if __name__ == "__main__":
uvicorn.run("test:app", host="0.0.0.0", port=1111, reload=True)
The method exception_callback could not capture the StarletteHTTPException when I accessed the http://127.0.0.1:1111/ . It reported 404 error.
The excepted behaviour should be: StarletteHTTPException error could be captured by the method exception_handler decorated by Exception because StarletteHTTPException is the child class of Exception.
However, it is a known issue reported in Fastapi and Starlette
https://github.com/tiangolo/fastapi/issues/2750
https://github.com/tiangolo/fastapi/issues/2683
https://github.com/encode/starlette/issues/1129
So we are not able to acheieve the goal currently.
First I invite to get familiar with exception base classes in python. You can read them in the document Built-in Exceptions
Secondly, read through fastApi default exception overriding behaviour Override the default exception handlers
What you must understand is that #app.exception_handler accepts any Exception or child classes derived from Exception. For example RequestValidationError is a subclass of python built in ValueError which itself a subclass of Exception.
So you must design your own exceptions or throw available exceptions with this background. I guess what went wrong is with your logger logger.error(exc.detail) by either not having a detail field or not having a proper logger configuration.
Sample code :
#app.get("/")
def read_root(response: Response):
raise ArithmeticError("Divide by zero")
#app.exception_handler(Exception)
async def validation_exception_handler(request, exc):
print(str(exc))
return PlainTextResponse("Something went wrong", status_code=400)
Output :
A stdout entry and a response with Something went wrong
I found a way to catch exceptions without the "Exception in ASGI application_" by using a middleware. Not sure if this has some other side effect but for me that works fine! #iedmrc
#app.middleware("http")
async def exception_handling(request: Request, call_next):
try:
return await call_next(request)
except Exception as exc:
log.error("Do some logging here")
return JSONResponse(status_code=500, content="some content")
I was searching for global handler for fast api for giving custome message for 429 status code i found and implemented, working fine for me
#app.exception_handler(429)
async def ratelimit_handler(request: Request, exc: Exception):
return JSONResponse({'message': "You have exceeded your request quota. Kindly try after some time.", 'status': 'failed'})
I want to build an api which accepts a parameter from the user which is a filepath and then process the file given in that path. The file to be processed is already in the server where the api will be running.
As of now, I have written an api where I have hardcoded the filepath in my code which runs the api. Now, I want to configure my api in such a way that accepts a filepath from the user. My api should accept the path as a parameter and process the file that has been given in the path.
The api code is as follows:
The convert function returns the category of the file.
import ectd
from ectd import convert
from flask import Flask, request
from flask_restful import Resource, Api
#from flask.views import MethodView
app = Flask(__name__)
api = Api(app)
#convert(r'D:\files\67cecf40-71cf-4fc4-82e1-696ca41a9fba.pdf')
class ectdtext(Resource):
def get(self, result):
return {'data': ectd.convert(result)}
#api.add_resource(ectdtext, '/ectd/<result>')
categories=convert(r'D:\files\6628cb99-a400-4821-8b13-aa4744bd1286.pdf')
#app.route('/')
def returnResult():
return categories
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000)
So, I want to make changes to this code to accept a parameter from the user which will be a filepath and the convert function will process that filepath. I want to know how to make my api accept a filepath parameter from the user.
Trial with requests.args.get:
import ectd
from ectd import convert
from flask import Flask, request
from flask_restful import Resource, Api
#from flask.views import MethodView
app = Flask(__name__)
api = Api(app)
#convert(r'D:\files\67cecf40-71cf-4fc4-82e1-696ca41a9fba.pdf')
class ectdtext(Resource):
def get(self, result):
return {'data': ectd.convert(result)}
#api.add_resource(ectdtext, '/ectd/<result>')
#app.route('/')
def returnResult():
categories=convert(r'D:\files\'.format(request.args.get('categories')))
return categories
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000)
results in error :
"RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem."
PRESENT SCENARIO:
I am able to post a filepath to the url. My question is now how do I use this posted url with filepath in my code to trigger my function that takes in the filepath and processes the file. Code to post the filepath:
import ectd
from ectd import convert
from flask import Flask, request
from flask_restful import Resource, Api
#from flask.views import MethodView
app = Flask(__name__)
api = Api(app)
class ectdtext(Resource):
def get(self, result):
return {'data': ectd.convert(result)}
#api.add_resource(ectdtext, '/ectd/<result>')
categories=convert('/home/brian/ajay/files/5ca21af9-5b67-45f8-969c-ae571431c665.pdf')
#app.route('/')
def returnResult():
return categories
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def get_dir(path):
return path
##app.route('/get_dir/<path>')
#def get_dir(path):
# return path
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000)
When I invoke socket.emit('someevent','blahblah') from server.py file, everything works as intended. But when I try to invoke the same method from bot.py, nothing happens.
Code:
server.py:
import eventlet
eventlet.monkey_patch()
import eventlet.wsgi
from flask import Flask, render_template, jsonify, request, abort
from flask_cors import CORS, cross_origin
import threading
from thread_manager import ThreadManager
from flask_socketio import SocketIO, emit, send
cho = Flask(__name__, static_folder="client/dist", template_folder="client/dist")
socketio = SocketIO(cho)
cors = CORS(cho)
threadmanager = ThreadManager() # Start the thread manager
import bot as bot_module
#cho.route('/api/start_bot', methods=['POST'])
#cross_origin()
def startBot():
"""
Begins the execution
:return:
"""
if request.method == 'POST':
request_json = request.get_json()
.... more code
bot = bot_module.Bot(some_args_from_request_above)
bot_thread = threading.Thread(target=bot.run)
bot_thread.start()
if threadmanager.check_thread_status(bot_name):
print('Thread is alive!')
return ok_res
else:
print('Thread seems inactive')
return bad_res
if __name__ == "__main__":
eventlet.wsgi.server(eventlet.listen(('0.0.0.0', 5000)), cho, debug=True)
bot.py
import server
class Bot:
.....
def run(self):
server.socketio.emit('someevent', 'w0w') # <-- nothing happens
I know I'm using the standard threading mechanism but it seems to not be related to threads whatsoever as I can create a random static method inside the Bot class, invoke it before creating a separate thread from the main file and nothing will happen. The thread_manager module contains nothing that would interfere, but I've even removed it completely from the picture and nothing changed. Any clues?
Turns out this was completely related to the circular import. Splitting the app declaration from the entrypoint worked, so that I'd have a third reference file which to import socketio from.