Python Flask: How do converters work? (dynamic routing) - python

I am trying to get Flask converters up and running as shown in the manual: http://flask.pocoo.org/docs/0.12/api/#url-route-registrations
Right now, I have currently four pages defined like so:
#app.route('/page1')
def page1():
return render_template("page1.html")
#app.route('/page2')
def page2():
return render_template("page2.html")
#app.route('/page3')
def page3():
return render_template("page3.html")
#app.route('/page4')
def page4():
return render_template("page4.html")
I could not find yet a way to automate this repetitive notation and would be grateful for hints.

You can create dynamic routes by using converters.
You'd then define your route like this:
#app.route("/<page>")
def pages(page):
return render_template(page + ".html")
Which will accept all of your paths.
You can also be more specific in what you allow in the dynamic paths, such as:
#app.route("/post/<int:post_id>")
def show_post(post_id):
pass
Which would accept /post/1, /post/2, /post/100 but not /post/test.

Related

Python Generic functions with generic parameters

I'm not sure if I used the right terms in the title. This maybe a known way to program interface functions for a subsystem or module but because I don't know the keywords, I'm not finding the results in my search queries.
I want to create a function whose intention can be clearly described in the functions name but the parameters are flexible. I want to write the function to be generic enough so that the function can complete the intention with whatever parameters it receives from whichever caller.
Let's take a function do_foo.
do_foo can take in some_obj whose attributes allows do_foo to do its work. Additionally, do_foo can just take in the individual attributes it cares about like obj_attr0 or obj_attr1 and perform the same work. In both cases, the expected result is the same as well.
So this would look something like this:
Class SomeObj():
def __init__(self, obj_attr0, obj_attr1, obj_attrN):
self.obj_attr0 = None
self.obj_attr1 = None
self.obj_attrN = None # denotes an N number of attributes
def do_foo(params)
# unpack params. do_foo requires obj_attr0 and obj_attr1 and so its searching it in the params iterable
# determine which data is passed in
# execute the work the same way regardless of what form the data is passed in
pass
obj_attr0 = None
obj_attr1 = None
obj_attrN = None
some_obj = SomeObj(obj_attr0, obj_attr1, obj_attrN)
# One can either call with a subset of attributes that would make up SomeObj or SomeObj itself if all the information is there. E.g.:
params = (some_obj)
do_foo(params)
# OR
params = (obj_att0, obj_attr1)
do_foo(params)
I know python offers *args and **kwargs facilities that offer the flexibility above. I'm looking for some examples of where the implementation lends itself to reducing pitfalls. What is a good way to implement the above? And if there are any resources out there what are examples/articles/or terms that describe the above style of programming? Clearly, I'm trying to write my interface functions to be generic and usable in multiple logic paths where the users has its data in different forms where sticking to a specific parameter list is limiting.
Short answer:
You can use function decorators to do this
Long answer:
I have a concrete example for you. It might not be the prettiest code but it does something similar to what you are asking for.
Mini HTTP Testing library
I made a mini HTTP testing library because I make my REST http tests in python, and I realized that I always write the same code again and again. So I made a more general setup
The core
The core is kind of ugly and this is the part I don't want to write again and again.
Just skip this part quick and check how it is used in the interface section.
Then if you like it you can go back and try to understand how it is all tied together.
# base.py
import json, requests, inspect
# This function drops invallid parameters
def request(*args, **kwargs):
allowed = inspect.signature(requests.Session.request).parameters
return {k:v for (k,v) in kwargs.items() if k in allowed}
def response(r, code):
if r.status_code != code:
print(r.text)
return
data = r.json()
if data:
print(json.dumps(data, indent=2, ensure_ascii=False))
return data
# This is the core function it is not pretty but it creates all the abstaction in multiple levels of decorations.
def HTTP(base_url):
def outer(func_one):
def over(*args_one, **kwargs_one):
req, url, code = func_one(*args_one, **kwargs_one)
url = base_url + url
def inner(func_two):
def under(*args_two, **kwargs_two):
allowed = inspect.signature(func_two).parameters
kwparams = {k:v for (k,v) in kwargs_two.items() if k in allowed}
from_inner = func_two(*args_two, **kwparams)
u = url.format(id=kwargs_two.pop('_id')) if '{id}' in url else url
r = req(u, **request(**kwargs_two, **from_inner))
return response(r, code)
return under
return inner
return over
return outer
The interface
The interface functions are all each decorated by the HTTP function which makes them a HTTP caller function, it is still abstract since it will return a function.
Note: interface is just what I call it but it is really just functions which returns functions based on the HTTP decorator
BASE_URL = "https://example.com"
#HTTP(BASE_URL)
def POST(url, code=200): return requests.post, url, code
#HTTP(BASE_URL)
def PUT(url, code=200): return requests.put, url, code
#HTTP(BASE_URL)
def DELETE(url, code=200): return requests.delete, url, code
#HTTP(BASE_URL)
def GET(url, code=200): return requests.get, url, code
A middleware function
When one of the interface functions are decorated with this one then they need a token.
def AUTH(func):
def inner(token, *args, **kwargs):
headers = {'Authorization': f'bearer {token}'}
return func(*args, **kwargs, headers=headers)
return inner
The implementation
The interface can be used for many implementations.
Here I use the interface of POST, PUT, GET and DELETE for the user model.
This is the final decoration, and the functions returned will actually return content instead of other functions.
# users.py
from httplib.base import (
POST,
GET,
DELETE,
PUT,
AUTH,
request
)
#POST('/users',200)
def insert(user):
return request(json=user)
#AUTH
#GET('/users')
def find(_filter={}):
return request(params=_filter)
#AUTH
#GET('/users/{id}')
def find_one(_id):
return request()
#AUTH
#DELETE('/users/{id}')
def delete(_id):
return request()
#AUTH
#PUT('/users/{id}')
def update(_id, updates={}):
return request(json=updates)
Operation
Here you can see how the users delete insert and find functions work.
from httplib import users
def create_and_delete_users(token, n): return [
users.delete(token, _id=x['user']['id'])
for x in [
users.insert(user={
'username' : f'useruser{str(i).zfill(2)}',
'password' : 'secretpassword',
'email' : f'useruser{str(i).zfill(2)}#mail.com',
'gender' : 'male',
}) for i in range(n)]
]
def find_all_and_then_find_each(token): return [
users.find_one(token, _id=x['id'])
for x in users.find(token)['data']
]
I hope this was helpful.

passing arguments to API created by flask in python

I am a complete newbie to API and python. actually, after getting disappointed to find a free host supporting plumber in R I decided to try it by python. The simple problem is that I have a simple function which takes two numeric arguments and using a given CSV file do some calculations and returns a number (I have simply made this in R by the plumber in localhost). now for a test in python have written below code:
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello():
return "hello world!"
if __name__ == '__main__':
app.run(debug=True)
well, this correctly works. but when I try to make a function to take arguments like this:
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello(a):
return a + 2
if __name__ == '__main__':
app.run(debug=True)
I get this page which says I have not passed the arguments.
my main question is that how I can pass the arguments? (in API created by R plumber, for example, I call it like: localhost/5000/?a=2 )
my another question is, could be this kind of API host and request in something like Heroku?
From Flask documentation:
You can add variable sections to a URL by marking sections with <variable_name>. Your function then receives the <variable_name> as a keyword argument. Optionally, you can use a converter to specify the type of the argument like <converter:variable_name>.
So in your case that would be:
#app.route("/<int:a>")
def hello(a):
return a + 2
Other option would be to use request data.
You need to include the parameter "a" in the decorator #app.route:
#app.route('/<int:a>')
def hello(a):
return a + 2
You can also use it like that, pass name as parameter !
#app.route('/helloworld/<Name>')
def helloworld(Name):
print Name
another implementation would be like that, go through the python-flask documentation !
#app.route("/<int:a>")
def hello(a):
return a + 2

Loop using app.route on Python

I'm trying to create several URLs on my serv thanks to a loop . The issue is that each function I create in a app.route can't have the same name than the others . And I don't know how to create different function names ...
Here is the code :
json_tweets = []
for line in open('C:\Users\Benjamin\Desktop\DashboardProject\last_rated_set.json',"r"):
json_tweets.append(json.loads(line,"ISO-8859-1"))
cashtag_tab = []
for tweet in json_tweets:
if not(tweet['cashtag'] in cashtag_tab) :
cashtag_tab.append(tweet['cashtag'])
for i in range(0,(len(cashtag_tab)-1)) :
var=cashtag_tab[i]
#app.route("/"+var)
def company(var) :
finance=Share(var)
datas = finance.get_historical('2014-01-01', '2014-12-31')
datas = json.dumps(datas, default=json_util.default)
return datas
I'm getting the error AssertionError : View function mapping is overwritting an existing endpoint function : company
This fails because Flask derives the endpoint name from the function by default, but it would anyway fail later because the function company requires an argument var and the route is not parameterised. The simplest option would be just checking the value inside the handler:
#api.route('/<var>')
def company(var):
if var not in cashtag_tab:
abort(404)
If you want all the routes to be in the routing map for any reason, I once needed a similar thing and came up with something like this:
def url_family(source, methods=('GET',)):
def decorator(f):
for entry in source:
# create a handler that delegates to your function
def view_func(entry=entry, **kwargs):
return f(entry, **kwargs)
endpoint = '{0}_{1}'.format(f.__name__, entry)
url = '/{0}'.format(entry)
api.add_url_rule(url,
methods=methods,
endpoint=endpoint,
view_func=view_func)
return decorator
Then you register the handlers as:
#url_family(cashtag_tab)
def company(var):
...
Assuming that you are using flask now, you should consider Custom URL Converter. Check links below
http://flask.pocoo.org/docs/0.10/api/#flask.Flask.url_map - url_map UrlConverter API
https://exploreflask.com/views.html#url-converters - example url converter
https://stackoverflow.com/a/5872904/3451543 - RegexConverter by Philip Southam
Anyway, specifying more details on your question is always helpful to get accurate answer :)

Flask: get current route

In Flask, when I have several routes for the same function,
how can I know which route is used at the moment?
For example:
#app.route("/antitop/")
#app.route("/top/")
#requires_auth
def show_top():
....
How can I know, that now route was called using /top/ or /antitop/?
UPDATE
I know about request.path I don't want use it, because the request can be rather complex, and I want repeat the routing logic in the function. I think that the solution with url_rule it the best one.
Simply use request.path.
from flask import request
...
#app.route("/antitop/")
#app.route("/top/")
#requires_auth
def show_top():
... request.path ...
the most 'flasky' way to check which route triggered your view is, by request.url_rule.
from flask import request
rule = request.url_rule
if 'antitop' in rule.rule:
# request by '/antitop'
elif 'top' in rule.rule:
# request by '/top'
Another option is to use endpoint variable:
#app.route("/api/v1/generate_data", methods=['POST'], endpoint='v1')
#app.route("/api/v2/generate_data", methods=['POST'], endpoint='v2')
def generate_data():
version = request.endpoint
return version
If you want different behaviour to each route, the right thing to do is create two function handlers.
#app.route("/antitop/")
#requires_auth
def top():
...
#app.route("/top/")
#requires_auth
def anti_top():
...
In some cases, your structure makes sense. You can set values per route.
#app.route("/antitop/", defaults={'_route': 'antitop'})
#app.route("/top/", defaults={'_route': 'top'})
#requires_auth
def show_top(_route):
# use _route here
...
It seems to me that if you have a situation where it matters, you shouldn't be using the same function in the first place. Split it out into two separate handlers, which each call a common fiction for the shared code.
One thing I have to add to the answers above is that
request.path preserves the url parameter value(s) passed while request.url_rule gives you the url_rule you defined without the passed parameter(s)
#app.route("/antitop/")
#app.route("/top/")
#requires_auth
def show_top():
request.path
request.url_rule
# -> both will give you "/antitop/" or "/top/"
....
#app.route("/antitop/<username>")
#app.route("/top/<username>")
#requires_auth
def show_top():
request.path
# -> gives you "/antitop/name" or ...
request.url_rule
# -> gives you "/antitop/<username>" or ...
....
Since you don't have any variable routes definey (yet!) you don't have to care but for the sake of saving you work and the headache in the future I'd still suggest using request.path.

redirect while passing arguments

In flask, I can do this:
render_template("foo.html", messages={'main':'hello'})
And if foo.html contains {{ messages['main'] }}, the page will show hello. But what if there's a route that leads to foo:
#app.route("/foo")
def do_foo():
# do some logic here
return render_template("foo.html")
In this case, the only way to get to foo.html, if I want that logic to happen anyway, is through a redirect:
#app.route("/baz")
def do_baz():
if some_condition:
return render_template("baz.html")
else:
return redirect("/foo", messages={"main":"Condition failed on page baz"})
# above produces TypeError: redirect() got an unexpected keyword argument 'messages'
So, how can I get that messages variable to be passed to the foo route, so that I don't have to just rewrite the same logic code that that route computes before loading it up?
You could pass the messages as explicit URL parameter (appropriately encoded), or store the messages into session (cookie) variable before redirecting and then get the variable before rendering the template. For example:
from flask import session, url_for
def do_baz():
messages = json.dumps({"main":"Condition failed on page baz"})
session['messages'] = messages
return redirect(url_for('.do_foo', messages=messages))
#app.route('/foo')
def do_foo():
messages = request.args['messages'] # counterpart for url_for()
messages = session['messages'] # counterpart for session
return render_template("foo.html", messages=json.loads(messages))
(encoding the session variable might not be necessary, flask may be handling it for you, but can't recall the details)
Or you could probably just use Flask Message Flashing if you just need to show simple messages.
I found that none of the answers here applied to my specific use case, so I thought I would share my solution.
I was looking to redirect an unauthentciated user to public version of an app page with any possible URL params. Example:
/app/4903294/my-great-car?email=coolguy%40gmail.com to
/public/4903294/my-great-car?email=coolguy%40gmail.com
Here's the solution that worked for me.
return redirect(url_for('app.vehicle', vid=vid, year_make_model=year_make_model, **request.args))
Hope this helps someone!
I'm a little confused. "foo.html" is just the name of your template. There's no inherent relationship between the route name "foo" and the template name "foo.html".
To achieve the goal of not rewriting logic code for two different routes, I would just define a function and call that for both routes. I wouldn't use redirect because that actually redirects the client/browser which requires them to load two pages instead of one just to save you some coding time - which seems mean :-P
So maybe:
def super_cool_logic():
# execute common code here
#app.route("/foo")
def do_foo():
# do some logic here
super_cool_logic()
return render_template("foo.html")
#app.route("/baz")
def do_baz():
if some_condition:
return render_template("baz.html")
else:
super_cool_logic()
return render_template("foo.html", messages={"main":"Condition failed on page baz"})
I feel like I'm missing something though and there's a better way to achieve what you're trying to do (I'm not really sure what you're trying to do)
You can however maintain your code and simply pass the variables in it separated by a comma: if you're passing arguments, you should rather use render_template:
#app.route("/baz")
def do_baz():
if some_condition:
return render_template("baz.html")
else:
return render_template("/foo", messages={"main":"Condition failed on page baz"})

Categories

Resources