Trailing slash in Flask route - python

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

Related

Flask routing by username at top-level route

Is there a prefered way of setting up Flask so that it routes by username (mysite.com/<username>) at the top level but still works well (and fast) for all other routes and static files?
The way I imagine it now is something like:
#app.route('/<username>', methods=['GET'])
def username_route(username):
if username_is_valid(username): # DB checks, not too fast
display_user_page(username)
render_template('user_not_found.html')
But would that have any unwanted effects on other routes or static assets, favicons, or something that I'm forgetting?
You can send data with post request on frontend for clicking profile cases.
<button onclick="urlfor('username', {'user_id': id})"/>
Top level solution is possible with app.config.
app.config['RESERVED_ROUTES'] = [
"faqs",
"settings",
"login",
...
]
Now we decide on request are user or reserved route. Because when we want to use blueprint in Flask it copy the config and give them an instance. So it is reachable for every request even you want to scale your app with blueprints.
#app.before_request
def decide_user_or_not():
if request.path in app.config['RESERVED_ROUTES']:
register_blueprint(route_blueprint)
else:
register_blueprint(user_blueprint)
Put your username_route at the end. Flask checks for each route from top to bottom. So, when you put faqs_route at the top which points to mysite.com/faqs, flask will consider that first.
Now, if you want to go to mysite.com/<username>, it will check all the top functions and since it can't find the corresponding route, it will go to the username_route at the end which will be the right route for mysite.com/<username>

FastAPI: Retrieve the endpoint for jinja2 `url_for` to give a jump link

Function description
In flask this code:
#app.route('/')
def index():
return ...
or
foo_router = Blueprint('foo', __name__)
#foo_router.route('/')
def index():
return ...
app.register_blueprint(foo_router, url_prefix='/api')
These ways you are able to use url_for('index') to retrieve url like https://hostname:8080/
or use url_for('foo.index') to retrive url like https://hostname:8080/api/foo
Desired function
Use url_for for jump link in fastapi like flask
Additional context
I have found a trick to do the implement
create url_naming.py and add the code below
from starlette.routing import Mount, Route
from .routers.foo.endpoints.area import index as foo_area_index
foo_router_for = Mount('/', routes=[
Route('/area', foo_area_index, name='foo_area.index'),
])
and in your main.py add
from .url_naming import foo_router_for
app.add_route('/foo', foo_router_for, name='foo_area.index')
In the codeblocks, I define name twice because which name I defined in a Route model cannot be detected by FastAPI, or it is overridden by FastAPI, and this impl make no sense because it is required to write down the real router name rather than the endpoint function name.
Despite that, what if I'd like to name my router in one file, which do not make effect on main.py?
I solve the problem with the following solution.
Just to name your handle function (endpoint) and the name can be normally used by url_for.
router = APIRouter(prefix='/api')
#router.get('/foo')
async def api_foo():
return ...
This way you can retrieve the url http://hostname:8080/api/foo/ using url_for('api_foo').
I guess this is because all the handle functions in FastAPI are named in the same space, and if they are named in FastAPI repeatedly, no error will be reported.

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

Dynamically generate Flask routes

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`

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.

Categories

Resources