Flask redirect from outside API functions - python

I have a helper method in a Flask app that is used by several endpoints to get a resource. To avoid having multiple redirect_url calls everywhere, I want to be able to redirect from just this helper method. Throwing a RequestRedirect exception as shown here correctly returns a 301 response, but doesn't set the Content-Location error. To get around this, I added a after_this_request hook that sets the url for that response.
This seems to work correctly, but I was wondering if theres a more elegant way to go about it.
Anonymized helper method:
def get_resource(resource_id):
try:
# Some logic
except:
#after_this_request
def add_header(response):
response.headers['Content-Location'] = url
return response
raise RequestRedirect(new_url='/')

If it is outside of your api, typically you would redirect to a 404 page. Here's a sample:
#app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404

Related

Hook for processing (not catching) exception in Flask

I have a Flask server.
Whenever the code inside my handler throws an exception, Flask catches it, and returns an HTML page to the client with a 5XX error.
The problem is that I don't notice this. I just got an email from someone using my API saying that they were getting 504 errors, and I didn't know about it until they told me.
In other non-Flask parts of my application I wrote a custom decorator to catch all exceptions, send an email to me, then re-throw. I would like something similar for my Flask app.
I want to find a way to have Flask call a function of mine every time my handler code throws an exception, before it returns a response to the client. I do not wish to modify the response that gets sent to the client. I don't want to change how Flask handles errors, or how it catches them. I just want some way of being notified, and then Flask can continue doing the default error handling behavior.
I suppose I could put a decorator over every single route handler to catch and rethrow exceptions before Flask sees them, but that's messy. I just know I'll forget one of them, especially when I add new ones in the future.
MWE
A buggy application:
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello():
assert False, "buggy code here"
return "hello"
def error_handler(exc_type, exc_val, exc_tb):
send_email(exc_type, exc_val, exc_tb)
# This is the part I don't know
# I want something along the lines of:
app.config['ERROR_HOOK'] = error_handler
from flask import Flask
app = Flask(__name__)
app.debug = False
app.config['PROPAGATE_EXCEPTIONS'] = True
#app.errorhandler(Exception)
def all_exception_handler(error):
print(str(error))
#app.errorhandler(404)
def page_not_found(error):
return 'This page does not exist', 404
you can define a function for each specific error you want to catch #app.my_custom_errorhandler(code_or_exception)
The argument to your error handler function will be an Exception.

Add custom handler on flask 404, 500 pages

I have a handler that wraps a function and does some logging and a few other things, almost like middleware. I want to wrap that around flask's default 404, 500 replies. How would this be done? For example:
#app.errorhandler(404)
#my_handler
def error404(*args, **kwargs):
return "404 - page not found", 404
# how to instead return the existing flask 404 call/page?
Basically, I just want to execute the error404 function but pass a handler around it. What should I return then?
Inspiring this example I tried this and it seems to work in the way you need.
#app.errorhandler(404)
#my_handler
def error404(e):
return e.get_response()

Flask-RESTplus CORS request not adding headers into the response

I have an issue trying to setup CORS for my REST API.
I'm currently using the Flask-Restplus package. Here's what my endpoints look like :
#api_ns.route('/some/endpoint')
#api_ns.response(code=400, description='Bad Request.')
class AEndpointResource(Resource):
#api_ns.param(**api_req_fields.POST_DOC)
#api_ns.expect(POST_REQUIRED_BODY)
#api_ns.marshal_with(code=201,
fields=my_api_models.MyEndpointResponse.get_serializer(),
description=my_api_models.MyEndpointResponse.description)
def post(self) -> Tuple[my_api_models.MyEndpointResponse, int]:
"""
The post body
"""
# Some logic here
return response, 200
If I code a small javascript snippet and I try to launch it in a browser, I will get an error because there's no CORS headers. I'm seeing the Flask-Restplus is already handling the OPTIONS request without me telling him anything. (This makes sense according to this link, mentioning that since Flask 0.6, the OPTIONS requests are handled automatically)
My problem is that even when I try to decorate my endpoint using :
from flask-restplus import cors # <--- Adding this import
...
class AnEndpointResource(Resource):
...
#my_other_decorators
...
#cors.crossdomain(origin='*') # <--- Adding this new decorator on my endpoint
def post(self) -> Tuple[my_api_models.MyEndpointResponse, int]:
...
Nothing changes and I still get the same result as before. I get an HTTP 200 from the OPTIONS request automatically handled as before, but I don't see my new headers (i.e. Access-Control-Allow-Origin) in the response.
Am I missing something ?
Using Flask-CORS, it works:
from flask import Flask, request
from flask_restplus import Resource, Api, fields
from flask_cors import CORS
# configuration
DEBUG = True
# instantiate the app
app = Flask(__name__)
api = Api(app)
app.config.from_object(__name__)
# enable CORS
CORS(app, resources={r'/*': {'origins': '*'}})
When doing local testing on my laptop and in my browser, I was able to solve the cors problem by adding the header in the response.
before: return state, 200
after: return state, 200, {'Access-Control-Allow-Origin': '*'}
I tested and the headers you are looking for are added to the response to the subsequent GET request. The decorator #cors.crossdomain has an option automatic_options which is set to be True by default. This means your OPTIONS request will still be handled automatically.
See this test to check how it should work.
The flask_restplus.cors module is not documented so not sure if you should use it.
I had a CORS problem as well and solved it this way:
from flask import Flask
from flask_restplus import Api
app = Flask('name')
api = Api(app)
// your api code here
#app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Origin', '*')
return response
The problem is that flask-restplus only assigns CORS headers to the GET request. When the browser makes an OPTIONS request, this method isn't handled by a method on the Resource class. Then you get a flickering CORS header: one request it works, the next it doesn't, etc
When a 404 is raised, this also skips the CORS decorator.
A workaround for these bugs is using something like:
def exception_to_response(func):
#wraps(func)
def _exc_to_resp_decorator(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except Exception as e:
return self.api.handle_error(e)
return _exc_to_resp_decorator
#api.route("/your-endpoint")
class SomeEndpoint(Resource):
#cors.crossdomain(origin='*')
def options(self):
# Make sure the OPTIONS request is also handled for CORS
# The "automatic_options" will handle this, no need to define a return here:
return
#api.expect(my_schema)
#api.response(200, "Success")
#cors.crossdomain(origin='*')
#exception_to_response
def get(self):
return {"json-fields": 123}, 200

redirect in before_app_request

mod = Blueprint('users', __name__, url_prefix='/users')
#mod.route('/welcome')
def www():
return '<h1>Hello</h1>'
#mod.before_app_request
def before_request():
return redirect(url_for('www'))
This code give me this error:
raise BuildError(endpoint, values, method)
BuildError: ('www', {}, None)
If I try this one, I will get an infinite loop redirect in the browser.
return redirect(url_for('users.www'))
My question is, how can I redirect the before_app_request to another action?
EDIT: log after changes, Now the redirect works, but the error still exists.
http://sharetext.org/dDje
With the way you set this up pretty much every request for this blueprint, including one to 'users.www' - hence the infinite loop redirect. You need some kind of conditions so that it would learn not to redirect and load the /welcome endpoint.
Also, url_for('www') will not work as you intend as you need . prefix to reference the current blueprint. Check the documentation for url_for
Maybe something like:
#mod.before_app_request
def before_request():
if request.endpoint != 'users.www':
return redirect(url_for('.www'))
Do note that you might want to print out the request.endpoint and grab the exact one, I can't remember how they are flask handles/names them internally for blueprints.
Reference: flask before request - add exception for specific route

My custom 404 page doesn't work (Pyramid framework)

I want to show my fancy 404 page in pyramid app, but can get it working. After reading various magic texts about the subject, I put something like this in my code:
cfg.add_view( "Page_not_found_view", renderer="page_404.mak",
context=HTTPNotFound )
But while my *Page_not_found_view* handler is invoked (I can see its' trace) I still get that poor "default" 404 page instead of *my own page_404.mak*. Any ideas?
Here's an example app that uses an exception view to catch the pyramid.httpexceptions.HTTPNotFound view raised by Pyramid when no view can be found that matches:
from waitress import serve
from pyramid.config import Configurator
from pyramid.response import Response
def hello_world(request):
return Response('<html><body>Hello world!</body></html>')
def notfound(request):
return Response('<html><body>Not found!</body></html>')
if __name__ == '__main__':
config = Configurator()
config.add_view(hello_world)
config.add_view(notfound, context='pyramid.httpexceptions.HTTPNotFound')
app = config.make_wsgi_app()
serve(app, host='0.0.0.0')
Visiting '/' will return "Hello world!", visiting "/abc" or "/def" (or anything else that isn't found) will return "Not found!".
What #chris-mcdonough wrote should work in most cases. However, if you are using a matchdict in your view callable and want to show your custom 404 page when nothing matches, make sure that you raise the HTTPNotFound exception instead of returning it. Otherwise, you will get the default 404 page.
Example:
from pyramid import httpexceptions
def my_page(self):
id = self.request.matchdict.get('id', None)
if not id:
raise httpexceptions.HTTPNotFound()
else:
# do whatever here

Categories