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()
Related
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()
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.
I'm writing a proxy in Flask. The proxy should forward all requests except one small subset, for which some additional checks should be performed.
Since there is a variety of routes I found this example in the docs
to match all URLs:
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()
What I'm wondering is what is the best way to have one view function that handles this subset of routes, and another view function that handles all others ?
Flask has decorators to perform action before handling the request.
#app.after_request
def catch_all(response):
# * additional checks here *
You can stop the normal handling by returning data
If any of these function returns a value it’s handled as if it was the return value from the view and further request handling is stopped.
It is often use for authentication or permission checks.
I want to use Flask blueprints to organize my Flask-Restful resources into separate url prefixes. Regardless of what url prefix I set (during either blueprint creation or registration), everything gets mapped to the raw route paths. How do I correctly use Restful with blueprints?
app = Flask(__name__)
api = Api(app)
api.add_resource(Building, '/<int:id>', endpoint='building')
api.add_resource(Jack, '/<int:id>', endpoint='jack')
building_api = Blueprint('building_api', __name__)
jack_api = Blueprint('jack_api', __name__)
app.register_blueprint(building_api, url_prefix='/buildings')
app.register_blueprint(jack_api, url_prefix='/jacks')
All documentation I can find says that these should now be available at /buildings/<int:id> and /jacks/<int:id>, but both of those urls 404 and instead I can access the building one at /<int:id>. Hard coding the path in add_resource fixes it, but defeats the point of url_prefix.
You need to pass the blueprint to the Api instance, not the app.
building_bp = Blueprint('buildings', __name__)
building_api = Api(building_bp)
building_api.add_resource(Building, '/<int:id>')
app.register_blueprint(building_bp, url_prefix='/buildings')
This is zhe best way to do with blueprint:
from flask import Flask, Blueprint
from flask_restful import Api, Resource, url_for
app = Flask(__name__)
api_bp = Blueprint('api', __name__)
api = Api(api_bp)
class TodoItem(Resource):
def get(self, id):
return {'task': 'Say "Hello, World!"'}
api.add_resource(TodoItem, '/todos/<int:id>')
app.register_blueprint(api_bp)
you should send Blueprint'instance to Api
I do not know why but I struggled using the Blueprint as mentioned in the answers.
But here's a quick solution I found while going through the doc link. Making use of the prefix parameter in Api() does the job.
app = Flask(__name__)
api = Api(app, prefix='/buildings')
Now, all your routes will be prefixed with /buildings. Just make sure you use url_for('link') in places where you might have simply used a /link.
One more strange thing I noticed is that atleast for me, it did not work until I renamed my routes to the same name as their class names. For example, Class Home(Resource) should have a route to /home. Using /homeepage or any other route for Home Class causes an error. Not sure if it is only me.
I've been trying to understand how to generate dynamic Flask URLs. I've read the docs and several example, but can't figure out why this code doesn't work:
path = 'foo'
#app.route('/<path:path>', methods=['POST'])
def index(path=None):
# do some stuff...
return flask.render_template('index.html', path=path)
I'd expect my index.html template to be served to /foo, but it is not. I get a build error. What am I missing?
If I use a fixed path, like /bar, everything works without issue.
#app.route('/bar', methods=['POST'])
You've got the long and short of it already. All you need to do is decorate your view functions using the /<var> syntax (or the /<converter:var> syntax where appropriate).
from flask import Flask, render_template
app = Flask(__name__)
#app.route('/')
def index():
return render_template('index.html')
#app.route('/<word>', defaults={'word': 'bird'})
def word_up(word):
return render_template('whatstheword.html', word=word)
#app.route('/files/<path:path>')
def serve_file(path):
return send_from_directory(app.config['UPLOAD_DIR'], path, as_attachment=True)
if __name__ == '__main__':
app.debug = True
app.run(port=9017)
When Flask pulls a variable out of a URL for a dynamic route like you're trying to use, it'll be a unicode string in Python by default. If you create the variable with the <int:var> or <float:var> converters, it'll be converted to the appropriate type in the app space for you.
The <path:blah> converter will match on a string that contains slashes (/), so you can pass /blah/dee/blah and the path variable in your view function will contain that string. Without using the path converter, flask would try and dispatch your request to a view function registered on the route /blah/dee/blah, because the plain <var> is delineated by the next / in the uri.
So looking at my little app, the /files/<path:path> route would serve whatever file it could find that matched the path sent up by the user on the request. I pulled this example from the docs here.
Also, dig that you can specify defaults for your variable URLs via a keyword arg to the route() decorator.
If you want, you can even access the underlying url_map that Werkzeug builds based on how you specify your view functions and routes in your app space. For more stuff to chew on, check out the api docs on URL registrations.
You can use add_url_rule():
def index(path=None):
return render_template('index.html', path=path)
path = '/foo'
app.add_url_rule(path, 'index', index)
You also might want to look at blueprint objects if you end up doing this alot.