Flask Assertion Error using multiple blueprints - python

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

Related

Flask: pass url path to class constructor

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.

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 using host parameter with Blueprints (register_blueprint)

I cannot seem to get domain matching working when registering blueprints. Referring to the following issue #miracle2k posts the following code:
if in_production:
app.register_blueprint(shop, host='localhost:<port>')
app.register_blueprint(info, host='info.localhost:<port>')
I tried something similar with no luck. However when I declare the host in my route like this:
#foo.route('/', host='<host>.bar.com')
...
app.register_blueprint(foo)
Host routing works fine. I'd much rather declare the host in the blueprint so I don't have to have the host= on ever single route. Any ideas what might be wrong?
Note: that in my Flask app I have delcared app.url_map.host_matching = True.
I just posted in the issue you mention a solution (gist) that might help you, if you actually wanted your blueprints to behave as if there was no host matching (and specify only the host for the host-specific routes).
If you really need your blueprints to each have specific hosts, I would jut create a custom Blueprint class that inherits from the default one and behaves as you want by overriding the add_url_rule method:
class MyBlueprint(Blueprint):
def __init__(self, *args, **kwargs):
self.default_host = kwargs.pop('default_host', None)
Blueprint.__init__(self, *args, **kwargs)
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
options['host'] = options.pop('host', self.default_host)
super().add_url_rule(self, rule, endpoint=None, view_func=None, **options)
...
shop = Blueprint(..., default_host='localhost:<port>')
info = Blueprint(..., default_host='info.localhost:<port>')

Custom authentication method for Flask-Security

I'm using flask security to authenticate users. I've made sure the authentication works properly with the http_auth_required decorator - the user is being validated against the userstore (an SQLAlchemyUserDatastore in my case), and all is well.
I would like now to use my own authentication method (I'll be using a custom LDAP validation system), while still taking advantage of the things Flask-Security is giving me (things like current_user). I wrote a custom decorator that looks like this:
def authenticate_with_ldap(func):
#wraps(func)
def wrapper(*args, **kwargs):
if not request.authorization:
return unauthorized_user_handler()
user = user_datastore.get_user(request.authorization.username)
if not user or not authenticate_with_ldap(user.email, user.password):
return unauthorized_user_handler()
return func(*args, **kwargs)
return wrapper
However, when I look at the http_auth_required decorator I see that it uses a private function called _check_http_auth that is doing some stuff that I can't do on my own without accessing private members, like setting the user to the top of the request context stack and sending signals. The code looks like this:
def _check_http_auth():
auth = request.authorization or BasicAuth(username=None, password=None)
user = _security.datastore.find_user(email=auth.username)
if user and utils.verify_and_update_password(auth.password, user):
_security.datastore.commit()
app = current_app._get_current_object()
_request_ctx_stack.top.user = user
identity_changed.send(app, identity=Identity(user.id))
return True
return False
So my question is: what is the correct way to have a custom authentication method, while still utilizing Flask-Security to its fullest?
You can accomplish this with a quick monkey patch. Not ideal, but I'm not sure what else you can do until the Flask-Security team writes in a more elegant way to handle this.
import flask_security
def verify_and_update_password_custom(password, user):
return user.verify_password(password)
flask_security.forms.verify_and_update_password = verify_and_update_password_custom
I'm not sure if it is used anywhere else. The above works for my own purposes. If it does get called elsewhere, you would just need to monkeypatch it into place wherever that is.

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