13

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")
davidism
  • 121,510
  • 29
  • 395
  • 339
Jesse
  • 785
  • 2
  • 9
  • 29

4 Answers4

14

You have one problem with two possible solutions. Either:

  1. 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:

  1. 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)
pjcunningham
  • 7,676
  • 1
  • 36
  • 49
A. Vidor
  • 2,502
  • 1
  • 16
  • 24
  • OK I will try that – Jesse Jul 17 '16 at 03:46
  • They are not defined, I wanted to know if there was a way to define them dynamically. The reason is I wanted my list to be used to generate all the routing. I don't know if that's possible. – Jesse Jul 17 '16 at 04:19
  • @Jesse See my revised answer; but you are now asking something else. What confuses you about generating a function dynamically? You can define a function that returns another function, or returns a `tuple` or `dict` containing a function and the name you want to associate with that function... – A. Vidor Jul 17 '16 at 04:22
  • Thank you so much I am almost there! I am going to post the code I wrote to make it work. The last thing I need to do is pass it an argument. I have and {% extends "layout.html" %} that is being used as a master template. I have {% block head %} for example that looks for page variable that is looking for page. – Jesse Jul 17 '16 at 04:34
  • 1
    @Jesse Are your remaining questions at all related to your original question? It is not clear how the `Jinja2` template you are describing even relates to my most recent follow-up question. – A. Vidor Jul 17 '16 at 04:38
  • Basically what I want is to have the routes call a generic function, the argument "page" in the routes is so I can pull it the page data from an sqlite or other db, so all the information to generate the page is in the routes list, so the process would be: the route is called the "page" argument is sent to the database the parts of the page are returned an rendered. So the routing is dynamic if I have 100 pages or 2 all I have to do is pass the page argument to the db. That's why I need a generic function so that I can add and remove pages with out having to edit routes manually – Jesse Jul 17 '16 at 04:46
  • A better solution would be the use of [variable parts in the route](http://flask.pocoo.org/docs/0.11/api/#url-route-registrations). On this approach, you would define one endpoint, e.g. `app.route('/')`. The decorated view function would accept a parameter `page`, and delegate other actions based on that parameter. I'll update my answer to include this solution. – A. Vidor Jul 17 '16 at 05:07
  • tried that actually. I ran into a problem with extending the routes. For example if I was going to write a REST api for it, setting the app.route("/") would make the page generic and you can pass in the variable you want and query against. But would you then not be able additional parameters? like app.route("//") for example? – Jesse Jul 17 '16 at 05:26
  • @Jesse You can indeed put multiple variable parts in your URL rules; `@app.route("//")` would decorate a function taking `page` and `item` as parameters. Or, you could "extend the route" using query-string parameters, e.g. `/some_page?item=some_item`. – A. Vidor Jul 17 '16 at 05:33
7

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.

pzp
  • 6,249
  • 1
  • 26
  • 38
  • OK I get it I don't know you could do it that way. I will give it a shot. – Jesse Jul 17 '16 at 05:03
  • @pzp There's nothing "inherently wrong" with this solution, it just doesn't express anything different from the `Flask.add_url_rule` approach. (The `Flask.route` decorator uses `Flask.add_url_rule` internally.) Your code does solve OP's issue, however, by binding `view_func` to a function instead of a string. – A. Vidor Jul 17 '16 at 05:16
  • @this-vidor Okay. I just read the docs for `add_url_rule` and indeed you are right. I gave you an upvote, but I'm going to leave my answer here as well for the sake of completeness. – pzp Jul 17 '16 at 05:18
  • @pzp Thanks! I wasn't commenting in a competitive spirit, but to address your uncertainty about whether your method was "inherently wrong". It is syntactically elegant. – A. Vidor Jul 17 '16 at 05:20
  • Minor correction - would be nice to add methods info: self.route(route, methods = route_methods)(func) – Vashu Feb 08 '17 at 03:57
2

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"])
Ismael EL ATIFI
  • 1,939
  • 20
  • 16
0

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`
Jesse
  • 785
  • 2
  • 9
  • 29
  • 1
    Glad you got it working! Bear in mind that for this to work, `route["func"]` must always equal `route["page"]` (so why define them separately?). Does this really not raise an exception? Because the code at the top defines a route for the `"/"` endpoint, as does the first iteration of the loop. Also, it might be cleaner to use variable parts (as described in my answer) rater than retrieving/parsing `request.url_rule` in the `generic` function. – A. Vidor Jul 17 '16 at 05:25
  • Especially since doing so would eliminate the need for multiple rule definitions altogether. – A. Vidor Jul 17 '16 at 05:30
  • @this-vidor - I am going to try your approach and use the app.route("/") and test it out. I think that is good method too. I apologize if I was unclear in what I was trying to do, I am used to working with Java, Coldfusion, C# and node.js. I think some of the methodology doesn't translate to well to python. I realize that your suggestion about using app.route("/page") makes more sense since use are only using one function the loop actually is generating a function per route. – Jesse Jul 17 '16 at 05:32