Is there a way to inject a Flask request object into a different Flask app. This is what I'm trying to do:
app = flask.Flask(__name__)
#app.route('/foo/<id>')
def do_something(id):
return _process_request(id)
def say_hello(request):
# request is an instance of flask.Request.
# I want to inject it into 'app'
I'm trying this with Google Cloud Functions, where say_hello() is a function that is invoked by the cloud runtime. It receives a flask.Request as the argument, which I want to then process through my own set of routes.
I tried the following, which doesn't work:
def say_hello(request):
with app.request_context(request.environ):
return app.full_dispatch_request()
This responds with 404 errors for all requests.
Edit:
The simple way to implement say_hello() is as follows:
def say_hello(request):
if request.method == 'GET' and request.path.startswith('/foo/'):
return do_something(_get_id(request.path))
flask.abort(404)
This essentially requires me to write the route matching logic myself. I'm wondering if there's a way to avoid doing that, and instead use Flask's built-in decorators and routing capabilities.
Edit 2:
Interestingly, dispatching across apps work locally:
app = flask.Flask(__name__)
# Add app.routes here
functions = flask.Flask('functions')
#functions.route('/', defaults={'path': ''})
#functions.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
def catch_all(path):
with app.request_context(flask.request.environ):
return app.full_dispatch_request()
if __name__ == '__main__':
functions.run()
But the same technique doesn't seem to work on GCF.
I wouldn't recommend this method, but this is technically possible by abusing the request stack and rewriting the current request and re-dispatching it.
However, you'll still need to do some type of custom "routing" to properly set the url_rule, as the incoming request from GCF won't have it (unless you explicitly provide it via the request):
from flask import Flask, _request_ctx_stack
from werkzeug.routing import Rule
app = Flask(__name__)
#app.route('/hi')
def hi(*args, **kwargs):
return 'Hi!'
def say_hello(request):
ctx = _request_ctx_stack.top
request = ctx.request
request.url_rule = Rule('/hi', endpoint='hi')
ctx.request = request
_request_ctx_stack.push(ctx)
return app.dispatch_request()
Related
There is a catch-all URL example for Flask:
from flask import Flask
app = Flask(__name__)
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def catch_all(path):
return 'You want path: %s' % path
if __name__ == '__main__':
app.run()
Decorators can be translated to the following to code look more similar to Flask-RESTful achieving the same functionality:
app.add_url_rule('/', 'catch_all', catch_all, defaults={'path': ''})
app.add_url_rule('/<path:path>', 'catch_all', catch_all, defaults={'path': ''})
If I'm right, this can be further translated to an equivalent Flask-RESTful app (at least debuging shows it creates the same URL routes):
class RESTapp(Resource):
def get(self, path):
# do get something
api.add_resource(RESTapp, '/', '/<path:path>', defaults={'path': ''})
The problem is that this app redirects all URLs to / and I can not get the requested path in get() function. I want to handle all paths ( / and '/') in the same function as in Flask, but using Flask-RESTful.
Similar questions:
Catch-All URL in flask-restful The Asker does not want to catch / or at least not in the same functions as other URL-s.
Flask restful API urls The Answerer proposes two classess as two resources. I have to initialize the class through resource_class_kwargs keyword argument and I want to keep only one instance, so it will not be good for me.
What I've tried:
Create two add_resource calls for the same class. It end with error.
Debug add_resource. It shows that it creates a resource view function from the Endpoint and that is given to the add_url_rule function. Else it works the same as the two subsequent add_url_rule functions.
By trial and error I've figured out the solution, which is neither documented nor looks like to the expected way of doing it (simmilarly as in Flask, showed in the question).
One must supply a Pythonic default argument to get() and other functions: get(stuff='DEF_VAL')
Full example which is working:
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class RESTapp(Resource):
#staticmethod
def get(path=''): # <-- You should provide the default here not in api.add_resource()!
return 'You want path: \'%s\'' % path # do get something
api.add_resource(RESTapp, '/', '/<path:path>') # Here just the URLs must be the arguments!
if __name__ == '__main__':
app.run()
On Successfully POSTing to a form endpoint I redirect back to the same endpoint with some URL params that my client side code can interact with.
#bp.route('/submit', methods=['GET', 'POST'])
def submit():
form = SubmissionForm()
labels = current_app.config['TRELLO_LABELS']
if form.validate_on_submit():
submission = Submission().create(
title=form.data['title'], email=form.data['email'], card_id=card.id, card_url=card.url)
# reset form by redirecting back and setting the URL params
return redirect(url_for('bp.submit', success=1, id=card.id))
return render_template('submit.html', form=form)
But I ran into some issues trying to write a test for this code as I can't figure out how to test that those URL params are on my redirect URL. My incomplete test code is:
import pytest
#pytest.mark.usefixtures('session')
class TestRoutes:
def test_submit_post(self, app, mocker):
with app.test_request_context('/submit',
method='post',
query_string=dict(
email='email#example.com',
title='foo',
pitch='foo',
format='IN-DEPTH',
audience='INTERMEDIATE',
description='foo',
notes='foo')):
assert resp.status_code == 200
I've tried a few different methods to test this. With and without the context manager and I've dug deep into the Flask and Werkzeug source on the test_client and test_request_context.
I just want to test that the URL params for success and id exist on redirect after a valid POST.
Here's a super simple yet inclusive example of patching Flask's url_for method (can be run as-is in a Python interpreter):
import flask
from unittest.mock import patch
#patch('flask.url_for')
def test(self):
resp = flask.url_for('spam')
self.assert_called_with('spam')
However, the above example will only work if you're importing Flask directly and not using from flask import url_for in your routes code. You'll have to patch the exact namespace, which would look something like this:
#patch('application.routes.url_for')
def another_test(self, client):
# Some code that envokes Flask's url_for, such as:
client.post('/submit', data={}, follow_redirects=True)
self.assert_called_once_with('bp.submit', success=1, id=1)
For more info, check out Where to Patch in the mock documentation.
You could use mock's function patch function to patch url_for capturing the provided arguments and then test against them.
I am using flask-mwoauth to create a simple application in Flask using OAuth authentication on Mediawiki (and Wikipedia in particular).
flask-mwoauth is a blueprint that provides some convenience methods to interact with Mediawiki Extensions:OAuth and adds the following URIs:
/login - runs the OAuth handshake and returns the user to /
/login?next=/someurl will return the user to /someurl
/logout - clears the users' access tokens
/logout?next=/someurl will return the user to /someurl
/oauth-callback - callback from MW to finish the handshake
The users' OAuth key and secret are stored in the session.
I would like to be able to create custom responses for some of this custom URIs. Take for example /logout, the definition of the response in very simple (__init__.py#L56):
#self.bp.route('/logout')
def logout():
session['mwo_token'] = None
session['username'] = None
if 'next' in request.args:
return redirect(request.args['next'])
return "Logged out!"
I would like to define in my application the route /logout with a custom response (for example, rendering a template), however if I use the blueprint then the route #app.route("/logout") is ignored.
What I would like to know if it is possible to "extend" the blueprint in the sense that I can define a route /logout in my app, call the original method from the blueprint and then serve a customized response.
If you want to completely redefine behavior of route the best way is override MWOAuth class. Here an example which works:
import os
from flask import Flask, Blueprint
from flask_mwoauth import MWOAuth
app = Flask(__name__)
app.secret_key = os.urandom(24)
class MyMWOAuth(MWOAuth):
def __init__(self,
base_url='https://www.mediawiki.org/w',
clean_url="Deprecated",
default_return_to='index',
consumer_key=None,
consumer_secret=None,
name="Deprecated"):
# I skipped other rows. It's just an example
self.bp = Blueprint('mwoauth', __name__)
# By the way you can customize here login and oauth-callback
#self.bp.route('/logout')
def logout():
# your custom logic here...
return "My custom logout"
mwoauth = MyMWOAuth(consumer_key='test', consumer_secret='test')
app.register_blueprint(mwoauth.bp)
if __name__ == "__main__":
app.run(debug=True, threaded=True)
Let's open /logout. You will see My custom logout.
As you can see registration of BluePrint routes takes place in init method of MWOAuth.
The second way is to use request callbacks. Here an example which demonstrates the change in the body of the response after logout.
from flask import g, request
def after_this_request(f):
if not hasattr(g, 'after_request_callbacks'):
g.after_request_callbacks = []
g.after_request_callbacks.append(f)
return f
#app.after_request
def call_after_request_callbacks(r):
for callback in getattr(g, 'after_request_callbacks', ()):
callback(r)
return r
#app.before_request
def before_logout():
#after_this_request
def after_logout(response):
# check if called route == '/logout'
# in our case response.data == 'Logged out!'
# see: https://github.com/valhallasw/flask-mwoauth/blob/master/flask_mwoauth/__init__.py#L48
if request.url_rule.endpoint == 'mwoauth.logout':
# custom logic here...
# for example I change data in request
response.data = 'Data from after_logout'
Let's open /logout. You will see Data from after_logout.
Hope this helps.
I can access /v1/folder but cannot access /v1/folder/<folder-id>. Could you tell me the reason? In the flask-request document said add_resource() can route multiple URI. But I cannot. Maybe I misunderstand something. Please tell me if you find the clue.
from flask import request
from flask_restful import Resource, abort
class Folder(Resource):
def post(self, folder_id):
return { "message":"post with folder_id"}, 200
def post(self):
return { "message":"post without folder_id"}, 201
app = Flask(__name__)
.....
api_bp = Blueprint('api', __name__)
api = Api(api_bp, serve_challenge_on_401=True)
api.add_resource( Folder, '/v1/folder', '/v1/folder/<string:folder_id>')
app.register_blueprint(api_bp)
if __name__ == "__main__":
app.run(host='0.0.0.0', debug=True )
The error messages is "TypeError: post() got an unexpected keyword argument 'folder_id' ". What's wrong?
Python does not support function/method overloading, so the post method you declared last is always going to be the one that's used. Instead, you should use the tools Python does provide - default values for arguments.
I would personally do the following:
from flask import request
from flask_restful import Resource, abort
class Folder(Resource):
def post(self, folder_id=None):
if folder_id is None:
return self.__simple_post()
else:
return self.__parameter_post(folder_id)
def __parameter_post(self, folder_id):
return { "message":"post with folder_id"}, 200
def __simple_post(self):
return { "message":"post without folder_id"}, 201
app = Flask(__name__)
.....
api_bp = Blueprint('api', __name__)
api = Api(api_bp, serve_challenge_on_401=True)
api.add_resource( Folder, '/v1/folder', '/v1/folder/<string:folder_id>')
app.register_blueprint(api_bp)
if __name__ == "__main__":
app.run(host='0.0.0.0', debug=True )
Or you could handle the logic in the post method, if the logic is similar enough and not too long. If the logic ends up being unreadable, though, consider using the approach I suggested.
How to get Flask to pass some of my own context along with url context? I would like to set the context when the URL is provided e.g. via add_url_rule:
app = Flask(__name__)
app.add_url_rule('/myproj/one, view_func=myfuncone,
methods=['GET'], context=mycontextone)
and I would like to access mycontextone when Flask calls myfuncone().
Curious - doesn't look like it's an option in flask as far as I can tell but you could wrap your view:
def myfuncone(id, **kwargs):
print kwargs.keys()
import functools
myfuncone_with_context = functools.partial(myfuncone, context=mycontextone)
app.add_url_rule('/myproj/one', methods=['GET'],
view_func=myfuncone_with_context)