Dynamically generate Flask routes - python

I am trying to dynamically generate routes in Flask from a list. I want to dynamically generate view functions and endpoints and add them with add_url_rule.
This is what I am trying to do but I get a "mapping overwrite" error:
routes = [
dict(route="/", func="index", page="index"),
dict(route="/about", func="about", page="about")
]
for route in routes:
app.add_url_rule(
route["route"], #I believe this is the actual url
route["page"], # this is the name used for url_for (from the docs)
route["func"]
)
app.view_functions[route["func"]] = return render_template("index.html")

You have one problem with two possible solutions. Either:
Have route[func] directly reference a function, not a string. In this case, you don't have to assign anything to app.view_functions.
Or:
Leave out the third argument of app.add_url_rule, and assign a function to app.view_functions[route["page"]]. The code
return render_template("index.html")
is not a function. Try something like
def my_func():
return render_template("index.html")
# ...
app.view_functions[route["page"]] = my_func
I'd recommend the first option.
Source: the docs.
Alternate solution:
Use variable parts in the URL. Something like this:
#app.route('/<page>')
def index(page):
if page=='about':
return render_template('about.html') # for example
else:
some_value = do_something_with_page(page) # for example
return render_template('index.html', my_param=some_value)

Not too familiar with Flask, so it is possible that there is a cleaner way to do this. (If someone who is knowledgeable about Flask thinks that my method is inherently wrong, I'll gladly delete my answer if they explain why in a comment.) Now that I got that disclaimer out of the way, here are my thoughts:
app.route("/") is a decorator function. The # notation is just syntactic sugar for something like index = app.route("/")(index). Therefore, you should be able to do something like this...
routes = [
("/", index),
("/about", about)
]
for route, view_func in routes:
view_func = app.route(route)(view_func)
which would allow you to create the Flask routes from dynamically created routes and functions.

Not directly replying to the OP but, as it could be useful to others, here is a tested example to dynamically create a POST route for multiple functions, each function taking a variable number of parameters and all returning a dict :
def dummy_func_1(x, y):
return {"a":0, "b":1}
def dummy_func_2(x):
return {"a":2}
FUNCTIONS_ROUTES = [dummy_func_1, dummy_func_2]
def create_view_func(func):
def view_func():
dict_input = request.get_json()
dict_output = func(**dict_input)
return flask.jsonify(dict_output)
return view_func
for func in FUNCTIONS_ROUTES:
app.add_url_rule(rule=f"/{func.__name__}",
endpoint=func.__name__,
view_func=create_view_func(func),
methods=["POST"])

This is how i got it to work #this-vidor and #PZP, the get page method is querying an sqlite db (but it could be any db), the generic function def is being looped over and in my actual code list of dictionaries is also being pulled from a db. So basically what I accomplished what I needed. The routes are dynamic. I can turn the routes on and off in the sql with out having to go to app.py to edit them.
defaultPage = "/"
#app.route(defaultPage)
def index():
page = getPage(defaultPage)
return render_template("index.html", page=page)
routes = [
dict(route="/", func="index", page="index"),
dict(route="/about", func="about", page="about")
]
def generic():
rule = request.url_rule
page = getPage(rule)
return render_template('index.html', page=page)
for route in routes:
app.add_url_rule(
route["route"], #I believe this is the actual url
route["page"] # this is the name used for url_for (from the docs)
)
app.view_functions[route["func"]] = generic`

Related

Nestable Blueprint not working

I would like to use Blueprint nesting so that similar blueprints can have similar #similar.before_request, etc (along with nice url name spacing).
I am aware that flask doesnt native support this feature.
However, this issue mentions a way to do it:
class NestableBlueprint(Blueprint):
"""
Hacking in support for nesting blueprints, until hopefully https://github.com/mitsuhiko/flask/issues/593 will be resolved
"""
def register_blueprint(self, blueprint, **options):
def deferred(state):
url_prefix = (state.url_prefix or u"") + (options.get('url_prefix', blueprint.url_prefix) or u"")
if 'url_prefix' in options:
del options['url_prefix']
state.app.register_blueprint(blueprint, url_prefix=url_prefix, **options)
self.record(deferred)
Trying the above fails to work, and gives me 404.
I have a file, which defines a blueprint like :
base_blueprint = Blueprint("BASE", __name__, url_prefix='/v0.1')
At the bottom, I have :
from .Admin import admin_base_blueprint
admin_base_blueprint.register_blueprint(base_blueprint,
url_prefix='/admin')
And Admin.py is defined as:
admin_base_blueprint = NestableBlueprint('admin', __name__)
#admin_base_blueprint.route('/', methods=['GET'])
def admin_echo_time():
"""
Just a function to echo current time.
May be useful for testing if servers are up.
:return: current date
"""
return datetime.now()
When I try to visit GET /v0.1/admin, it give me a 404.
I also tried https://stackoverflow.com/a/36326234/2670775. In this case, I am able to get url name spacing to work , but blueprint functions like before_request dont get called.
my flask version is 0.12.2

Flask - Routing with multiple optional parameters

I have a Route named search: #app.route('/search')
Is it possible to add multiple optional parameters to it? Example:
#app.route('/search/pg/<int:pg>')
#app.route('/search/types/<types>')
#app.route('/search/number/<int:number>')
#app.route('/search/subject/<subject>')
The order in the URL shouldnt matter, so I could call /search/pg/2, or /search/subject/MySubject/pg/2 or /search/types/posts/subject/MySubject/pg/2
I tried this, but it only works with the full paths and all the parameters:
#app.route('/search/pg/<int:pg>/types/<types>/subject/<subject>', methods=['GET', 'POST'])
#app.route('/search/subject', defaults={'subject', None})
#app.route('/search/pg/<int:pg>/types/<types>', methods=['GET', 'POST'])
#app.route('/search/types', defaults={'types', None})
#app.route('/search', defaults={'pg': 1}, methods=['GET', 'POST'])
#app.route('/search/pg/<int:pg>', methods=['GET', 'POST'])
def search(pg, types=None, subject=None):
pass
You can use filter in the URL instead of "sub-resources".
Then you can put search arguments in any order in your request:
/search?pg=<pg>&types=<types>
Inside the flask view function you can retrieve parameters from the request object:
#app.route('/search/')
def search():
pg = request.args.get('pg')
...
#David, I worked on a package that does this called flask_optional_routes. The code is located at: https://github.com/sudouser2010/flask_optional_routes.
from flask import Flask
from flask_optional_routes import OptionalRoutes
app = Flask(__name__)
optional = OptionalRoutes(app)
#optional.routes('/<user_id>/<user_name>?/')
def foobar(user_id, user_name=None):
return 'it worked!'
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000)
If you are trying to route a user based on multiple, optional form values as route parameters, then I found a useful workaround.
First, create an intermediate route that will create the query string. This route will only allow for POST methods (since the 1 or more of the form values would be submitted by the form).
#app.route('/example', methods=['POST']
def createQueryParams():
form = ExampleForm()
# Retrieve/cleanse/validate form data
example1 = form.example_field1.data
example2 = form.example_field2.data
return redirect(url_for('app.route', example1=example1, example2=example2))
Note that each keyword argument in the redirect(url_for()) needs to either be a parameter used in app.route or something you expect to add as a query parameter.
Next, alter your app.route() to have a GET method and extract the query parameters like #lee-pai-long mentioned
#app.route('/search', methods=['GET'])
def search():
if len(request.args) > 0:
example1 = request.args.get('example1')
example2 = request.args.get('example2')
# Do stuff
Finally, in the .html template where the form is submitted, make sure the form action directs to the createQueryParams route, not the search route. Such as
<form action="{{ url_for('app.createQueryParams') }}" method="POST">
<!-- Flask form stuff -->
</form>
I used this structure to create a page where users could filter posts based on up to X different criteria (title search, date sort, tags, etc.). I wanted to be able to have an optimized query parameter, but without the intermediary createQueryParams route, I was not able to determine what form values were selected until after the url was created, since the single route owned both methods.

Trailing slash in Flask route

Take for example the following two routes.
app = Flask(__name__)
#app.route("/somewhere")
def no_trailing_slash():
#case one
#app.route("/someplace/")
def with_trailing_slash():
#case two
According to the docs the following is understood:
In case one, a request for the route "/somewhere/" will return a 404 response. "/somewhere" is valid.
In case two, "/someplace/" is valid and "/someplace" will redirect to "/someplace/"
The behavior I would like to see is the 'inverse' of the case two behavior. e.g. "/someplace/" will redirect to "/someplace" rather than the other way around. Is there a way to define a route to take on this behavior?
From my understanding, strict_slashes=False can be set on the route to get effectively the same behavior of case two in case one, but what I'd like to do is get the redirect behavior to always redirect to the URL without the trailing slash.
One solution I've thought of using would be using an error handler for 404's, something like this. (Not sure if this would even work)
#app.errorhandler(404)
def not_found(e):
if request.path.endswith("/") and request.path[:-1] in all_endpoints:
return redirect(request.path[:-1]), 302
return render_template("404.html"), 404
But I'm wondering if there's a better solution, like a drop-in app configuration of some sort, similar to strict_slashes=False that I can apply globally. Maybe a blueprint or url rule?
You are on the right tracking with using strict_slashes, which you can configure on the Flask app itself. This will set the strict_slashes flag to False for every route that is created
app = Flask('my_app')
app.url_map.strict_slashes = False
Then you can use before_request to detect the trailing / for a redirect. Using before_request will allow you to not require special logic to be applied to each route individually
#app.before_request
def clear_trailing():
from flask import redirect, request
rp = request.path
if rp != '/' and rp.endswith('/'):
return redirect(rp[:-1])
If you want both routes to be handled the same way, I would do this:
app = Flask(__name__)
#app.route("/someplace/")
#app.route("/someplace")
def slash_agnostic():
#code for both routes
You can also use the option strict_slashes=False in your route definition:
app.Flask(__name__)
#app.route("/someplace", strict_slashes=False)
# Your code goes here

Do I need to name the url when using app.add_url_rule?

From the Flask docs:
def index():
pass
app.add_url_rule('/', 'index', index)
It also says:
endpoint – the endpoint for the registered URL rule. Flask itself assumes the name of the view function as endpoint
Indeed, if I do app.add_url_rule('/', None, index) it all seems to work fine. (I'm not sure of the terminology and what an "endpoint" actually is.)
Some further questions:
Is there any reason to specify the second argument?
Is the second argument indeed the "endpoint"?
What are the benefits/drawbacks of (not) specifying the second argument?
What if the same name is used in another url rule? Is the first overwritten? Is this intended?
In this case, an endpoint is simply a term for a valid URL of your application. From Wikipedia:
In service-oriented architecture, an endpoint is the entry point to a service, a process, or a queue or topic destination
As for naming your URLs - yes, it is optional. There are some benefits to defining names. When using *url_for*() or redirect() for example, you can specify the url's name as a shortcut. This comes with the added benefit of allowing your to change your url structure without changing every line of code that interacts with that url, since it only references it by name.
So in your example, you could reference your index url like this:
return redirect('index')
For your last question, I'm not sure what would happen. It likely would error when trying to resolve the url with that name. Either way, there's no reason to define two different urls with the same name.
The reason for the second argument is the following:
Let's say you're designing a website and you have /login be the page where your users enter their username, password, OpenID, whatever. Upon successfully logging in, you might want to send them to /. In Flask, the canonical way to do that is:
return redirect(url_for('index'))
Where 'index' is the name of the function you defined to be the handler for /, e.g.,
#app.route('/')
def index():
return render_template('index.html')
If you instead do:
def index():
return render_template('index.html')
app.add_url_rule('/', None, index)
It will work just fine when your user requests / explicitly, but when your users successfully login they'll be faced with a horrible error message and have to visit / manually.
In short, it's the proper thing to do.
Also, you should note that
#app.route('/')
def index():
return render_template('index.html')
And
def index():
return render_template('index.html')
app.add_url_rule('/', 'index', index)
Are exactly the same and in the both of these last two snippets redirect(url_for('index')) will work perfectly.

Flask context processors functions

Following the minimal example on the Flask pages I'm trying to build a context processor:
context_procesor.py
def inflect_this():
def inflectorize(number, word):
return "{} {}".format(number, inflectorizor.plural(word, number))
return dict(inflectorize=inflectorize)
app.py(within an app factory)
from context_processor import inflect_this
app.context_processor(inflect_this)
Using a previous inflection function that inflects a word based on number, simple I already have it as a jinja filter but wanted to see if I could do it as a context processor.
Given the example at the bootom of the page here: http://flask.pocoo.org/docs/templating/, this should work but does not. I get:
jinja2.exceptions.UndefinedError UndefinedError: 'inflectorize' is undefined
I do not understand enough you to see what is going on. Can anyone tell me what is wrong?
EDIT:
app.jinja_env.globals.update(inflectorize=inflectorize)
works to add functions and seems to be less overhead than wrapping a method in a method, where app.context_processor probably relays to jinja_env.globals anyway.
I'm not sure if this entirely answers your question, as I haven't used app factories.
However, I tried this from a blueprint, and this works for me. You just have to use the blueprint object in the decorator instead of the default "app":
thingy/view.py
from flask import Blueprint
thingy = Blueprint("thingy", __name__, template_folder='templates')
#thingy.route("/")
def index():
return render_template("thingy_test.html")
#thingy.context_processor
def utility_processor():
def format_price(amount, currency=u'$'):
return u'{1}{0:.2f}'.format(amount, currency)
return dict(format_price=format_price)
templates/thingy_test.html
<h1> {{ format_price(0.33) }}</h1>
and I see the expected "$0.33" in the template.
Hope that helps!

Categories

Resources