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.
Related
In Flask, you create routes like this:
from flask import Flask
app = Flask(__name__)
#app.route("/foobar")
def foo_bar_endpoint():
...
But the route decorator also has an endpoint parameter. The documentation just states:
the endpoint for the registered URL rule. Flask itself assumes the name of the view function as endpoint
Why should / would one not simply name the function as the endpoint should be called? Is this relevant for anything else than url_for?
The "endpoint" is an arbitrary name you provide, which is basically the canonical thing that a route refers to. You're not actually mapping a URL to a function there, you're mapping a URL to an endpoint and are attaching a view function to that endpoint.
URL → endpoint → function
You're just doing all that in one action, and the name of the function automatically becomes the name of the endpoint in the process. But you could do this all separately and register an endpoint for a URL, and later attach a function to that endpoint:
Basically this example:
#app.route('/')
def index():
pass
Is equivalent to the following:
def index():
pass
app.add_url_rule('/', 'index', index)
If the view_func is not provided you will need to connect the endpoint to a view function like so:
app.view_functions['index'] = index
https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.add_url_rule
You may want to name your endpoint independently of your function, if you're under some constraint for the function name but are already referring to the endpoint from elsewhere, especially during refactoring, as one possible example where that might become relevant.
The URL is for external clients, for HTTP-requests.
The endpoint is for internal usage if you need to know which URL to redirect for example without any hardcoding.
In case you not defining endpoint your code looks like:
blueprint = Blueprint("bp_1", __name__)
#blueprint.route("/foobar")
def foo_bar_endpoint():
...
url = flask.url_for("bp_1.foo_bar_endpoint")
In case you define endpoint, you use function url_for with its name:
#blueprint.route("/foobar", endpoint="custom_name")
def foo_bar_endpoint():
...
url = flask.url_for("bp_1. custom_name")
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()
I want to define multiple endpoints that render different templates, without writing out each one. The endpoints are all similar, one looks like:
#app.route('/dashboard/')
def dashboard():
return render_template('endpoints/dashboard.html')
I tried defining a function in a for loop for each endpoint name, but the problem is that the name of the function stays the same and Flask raises an error about that.
routes = ['dashboard', 'messages', 'profile', 'misc']
for route in routes:
#app.route('/' + route + '/')
def route():
return render_template('endpoints/' + route + '.html')
How can I create these views without repeating myself?
You don't want to do this. Instead, use a variable in the route to capture the template name, try to render the template, and return a 404 error if the template doesn't exist.
from flask import render_template, abort
from jinja2 import TemplateNotFound
#app.route('/<page>/')
def render_page(page):
try:
return render_template('endpoints/{}.html'.format(page))
except TemplateNotFound:
abort(404)
Alternatively, and less preferably, you can use the same function name as long as you provide Flask with unique endpoint names. The default name is the name of the function, which is why Flask complains.
for name in routes:
#app.route('/', endpoint=name)
def page():
return render_template('endpoints/{}.html'.format(name))
I'm trying to add another view to my Flask app. My app/views.py looks like this:
from flask import render_template
from app import app
from helpfulFunctions import *
def getRankingList():
allPlayers = main()
return allPlayers
def displayLimitedNumberOfPlayers(limit):
allPlayers = main()
allPlayers[0] = limitPlayers(allPlayers[0], limit)
allPlayers[1] = limitPlayers(allPlayers[1], limit)
return allPlayers
#app.route("/")
#app.route("/index")
def index():
rankingList = getRankingList()
return render_template('index.html', title='Home', rankingList = rankingList)
#app.route("/top100")
def top100():
rankingList = displayLimitedNumberOfPlayers(100)
return render_template('top100.html', rankingList = rankingList)
if __name__ == '__main__':
app.run(debug=True)
I've tried to mimic how the Miguel Grinberg tutorial defines routes for / and for /index. I've created a view called top100.html in my templates folder, where the "index.html" file also lives. However, when I try to hit localhost:5000/top100.html, I get:
Not Found
The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
So it seems like Flask doesn't think that URL has a view associated with it...but I'm not sure why.
Any idea?
Thanks for the help,
bclayman
There is no view top100.html in your code.You can do either of these
localhost:5000/top100
OR
change #app.route("/top100") to #app.route("/top100.html")
The route (or url) is specified in the #app.route() definition, so you should visit localhost:5000/top100.
The render_template top100.html is only referenced internally within Flask to specify the template used. Really, this page could be named anything and does not have to be named in any similar way to the route...it just has to match the template file used to build the page served at that url.
The Flask documentation shows:
add_url_rule(*args, **kwargs)
Connects a URL rule. Works exactly like the route() decorator.
If a view_func is provided it will be registered with the endpoint.
endpoint – the endpoint for the registered URL rule. Flask itself assumes the name of the view function as endpoint
What exactly is meant by an "endpoint"?
How Flask Routing Works
The entire idea of Flask (and the underlying Werkzeug library) is to map URL paths to some logic that you will run (typically, the "view function"). Your basic view is defined like this:
#app.route('/greeting/<name>')
def give_greeting(name):
return 'Hello, {0}!'.format(name)
Note that the function you referred to (add_url_rule) achieves the same goal, just without using the decorator notation. Therefore, the following is the same:
# No "route" decorator here. We will add routing using a different method below.
def give_greeting(name):
return 'Hello, {0}!'.format(name)
app.add_url_rule('/greeting/<name>', 'give_greeting', give_greeting)
Let's say your website is located at 'www.example.org' and uses the above view. The user enters the following URL into their browser:
http://www.example.org/greeting/Mark
The job of Flask is to take this URL, figure out what the user wants to do, and pass it on to one of your many python functions for handling. It takes the path:
/greeting/Mark
...and matches it to the list of routes. In our case, we defined this path to go to the give_greeting function.
However, while this is the typical way that you might go about creating a view, it actually abstracts some extra info from you. Behind the scenes, Flask did not make the leap directly from URL to the view function that should handle this request. It does not simply say...
URL (http://www.example.org/greeting/Mark) should be handled by View Function (the function "give_greeting")
Actually, it there is another step, where it maps the URL to an endpoint:
URL (http://www.example.org/greeting/Mark) should be handled by Endpoint "give_greeting".
Requests to Endpoint "give_greeting" should be handled by View Function "give_greeting"
Basically, the "endpoint" is an identifier that is used in determining what logical unit of your code should handle the request. Normally, an endpoint is just the name of a view function. However, you can actually change the endpoint, as is done in the following example.
#app.route('/greeting/<name>', endpoint='say_hello')
def give_greeting(name):
return 'Hello, {0}!'.format(name)
Now, when Flask routes the request, the logic looks like this:
URL (http://www.example.org/greeting/Mark) should be handled by Endpoint "say_hello".
Endpoint "say_hello" should be handled by View Function "give_greeting"
How You Use the Endpoint
The endpoint is commonly used for the "reverse lookup". For example, in one view of your Flask application, you want to reference another view (perhaps when you are linking from one area of the site to another). Rather than hard-code the URL, you can use url_for(). Assume the following
#app.route('/')
def index():
print url_for('give_greeting', name='Mark') # This will print '/greeting/Mark'
#app.route('/greeting/<name>')
def give_greeting(name):
return 'Hello, {0}!'.format(name)
This is advantageous, as now we can change the URLs of our application without needing to change the line where we reference that resource.
Why not just always use the name of the view function?
One question that might come up is the following: "Why do we need this extra layer?" Why map a path to an endpoint, then an endpoint to a view function? Why not just skip that middle step?
The reason is because it is more powerful this way. For example, Flask Blueprints allow you to split your application into various parts. I might have all of my admin-side resources in a blueprint called "admin", and all of my user-level resources in an endpoint called "user".
Blueprints allow you to separate these into namespaces. For example...
main.py:
from flask import Flask, Blueprint
from admin import admin
from user import user
app = Flask(__name__)
app.register_blueprint(admin, url_prefix='admin')
app.register_blueprint(user, url_prefix='user')
admin.py:
admin = Blueprint('admin', __name__)
#admin.route('/greeting')
def greeting():
return 'Hello, administrative user!'
user.py:
user = Blueprint('user', __name__)
#user.route('/greeting')
def greeting():
return 'Hello, lowly normal user!'
Note that in both blueprints, the '/greeting' route is a function called "greeting". If I wanted to refer to the admin "greeting" function, I couldn't just say "greeting" because there is also a user "greeting" function. Endpoints allow for a sort of namespacing by having you specify the name of the blueprint as part of the endpoint. So, I could do the following...
print url_for('admin.greeting') # Prints '/admin/greeting'
print url_for('user.greeting') # Prints '/user/greeting'
Endpoint is the name used to reverse-lookup the url rules with url_for and it defaults to the name of the view function.
Small example:
from flask import Flask, url_for
app = Flask(__name__)
# We can use url_for('foo_view') for reverse-lookups in templates or view functions
#app.route('/foo')
def foo_view():
pass
# We now specify the custom endpoint named 'bufar'. url_for('bar_view') will fail!
#app.route('/bar', endpoint='bufar')
def bar_view():
pass
with app.test_request_context('/'):
print url_for('foo_view')
print url_for('bufar')
# url_for('bar_view') will raise werkzeug.routing.BuildError
print url_for('bar_view')
If you have same class name and want to map with multiple routes, then specify the endpoint, so that framework will differentiate between two:
class ClassName(Resource):
def get(self):
if request.endpoint!='hello':
return {"data": "Hello"}
elif:
return {"data" : "World"}
api.add_resource(ClassName, '/rout1', endpoint = "world")
api.add_resource(ClassName, '/rout2', endpoint="hello")
#app.route('/') #Endpoint
def a_function(): #View function
return 'view'
Inside Flask, every endpoint with its request methods mapped to a view function. When you use app.route decorator you are actually adding a URL rule.