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!
Related
I am trying something probably highly unorthodox: I need to pass an argument, that comes in via the url path to the class constructor of a class-based MethodView.
http://127.0.0.1:5000/child/my_id_string
I want to pass my_id_string to the following constructor as arg1.
My most promissing try was based on this question, but I need to use class-based views instead of functions. Unfortunately the logic behind this call is somewhat more complex than the example, i.e I cannot simply refactor the code to not use "my_id" in the constructor.
from flask import Flask, request
from flask.views import MethodView
BASE = 11
app = Flask('mybase')
class Child(MethodView):
def __init__(self, base, arg1=None):
self.base = base
print('some init process, where I need arg1...')
def get(self, arg1):
return f'Some operation with {str(arg1)}.'
app.add_url_rule(
'/child/<arg1>',
'child',
view_func=Child.as_view(
'child_view',
BASE,
arg1 = request.args.get('my_id')
),
methods=['GET',]
)
I get the following error with the snippet as is, because I figure the registration occurs before/without a specific request, is that correct?
Hope, someone is able to help. Thanks in advance!
RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.
I am a little confused about your syntax of :
app = flask("mybase") :i use app = flask(__name__)
But this is what i would do.
#app.route("/child/<arg1>")
def x(arg1):
varArg1 = arg1
#now you have whatever is in the url will be passed into varArg1.
EDIT: So after a little more digging it seems the problem (if it is a problem, it is likely intentional for reasons beyond my comprehension) is with Flask-Cognito (lib I'm using to handle User Authtentication via AWS) - many of my routes have the decorators #cognito_auth_required and #cognito_group_permissions(['admin']) but it seems I can't apply this to more than 1 route per blueprint. However multiple routes on the blueprints need to be restricted to admin users only. Is there a way I can apply these decorators across multiple routes?
Code updated to provide context.
-- end edit --
apologies if this is a noob question, I'm fairly new to Flask.
I am writing a RESTful API, I am registering multiple blueprints in an attempt to keep code clean however, I keep receiving AssertionError: View function mapping is overwriting an existing endpoint function: products.wrapper message when trying to run my program locally. This error only started popping up when I implemented my second blueprint.
All blueprints are registered as so:
api_base_url = '/api/v1'
app.register_blueprint(products, url_prefix=api_base_url+'/products')
app.register_blueprint(categories, url_prefix=api_base_url+'/categories')
if __name__ == "__main__":
application = app
application.debug = True
application.run()
And in my routes.py file:
.. code omitted, Product + Category models imported from models ...
#products.route('/create', methods=['POST'])
#cognito_auth_required
#cognito_group_permissions(['admin'])
def products_create():
# code omitted for readability
return jsonify(error=False, message='New product created')
#categories.route('/create', methods=['POST'])
#cognito_auth_required
#cognito_group_permissions(['admin'])
def categories_create():
# code omitted for readability
return jsonify(error=False, message='New category created')
Initially I had had the routes both point to two different def all(): functions but I changed it in the hope that multiple functions with the same name was the problem, to no avail. I'm not sure what could be causing this endpoint conflict - each blueprint has their own URL prefix so it should mitigate endpoint collision but perhaps this is not the case? Is it possible, since the blueprints are registered just before the program spins up, that it hasn't prepended the URL prefixes before checking for collisions?
Any help would be greatly appreciated. Cheers.
Ok so I figured it out - it seems the decorator #cognito_group_permissions doesn't wrap the function. In order to get it working I created decorators.py imported wraps from functools and ammended the code from flask_cognito:
Original:
def cognito_check_groups(groups: list):
def decorator(function):
def wrapper(*args, **kwargs):
_cognito_check_groups(groups)
return function(*args, **kwargs)
return wrapper
return decorator
Decorators.py:
from functools import wraps
from flask_cognito import _cognito_check_groups
def cognito_in_groups(groups: list):
def decorator(function):
#wraps(function)
def wrapper(*args, **kwargs):
_cognito_check_groups(groups)
return function(*args, **kwargs)
return wrapper
return decorator
Then I imported into my routes and used the updated decorator and now all works perfectly:
from decorators import cognito_in_groups
#products.route('/create', methods=['POST'])
#cognito_auth_required
#cognito_in_groups(['admin'])
def products_create():
try:
# code omitted...
return jsonify(error=False, message='New product created')
except IntegrityError:
return jsonify(error=True, message='Duplicate SKU detected')
Leaving here in case anyone else runs into a similar problem.
Curious that cognito_auth_required is wrapped but cognito_check_groups isn't, if anyone could shed light on the reasons for this I'd would be interested to know.
Cheers
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
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
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`