Overwrite route in flask blueprint - python

There is a blueprint with a lot of useful routes defined, but I have no control over it (can not change it's code in any way)
Trying to reuse it in a different app but one of the blueprint's endpoints must be overloaded. How can I achieve that?
I tried just adding a new route to blueprint on top of the existing one:
#blueprint.route('/my/route', methods=['PUT', 'POST'])
def my_new_view_func(program, project):
# some new behavior for the endpoint
As the result there is duplicate url_rule in app.url_map.iter_rules():
<Rule '/my/route' (PUT, POST) -> my_view_func>,
<Rule '/my/route' (PUT, POST) -> my_new_view_func>,
and when requesting /my/route old viewer my_view_func gets executed
Can I somehow get rid of the old url rule? Or maybe there is a better way to overwrite the route?

There are 2 solutions which I found. First:
from flask import Flask, Blueprint
simple_page = Blueprint('simple_page', __name__, )
#simple_page.route('/my/route/')
def my():
# for example it's a registered route somewhere...
return 'default'
#simple_page.route('/my/route/')
def new_my():
# new endpoint / should works instead my()
return 'new'
# map of views which we won't register in Flask app
# you can store this somewhere in settings
SKIP_VIEWS = (
# route, view function
('/my/route/', my, ),
)
class CustomFlask(Flask):
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
# Flask registers views when an application starts
# do not add view from SKIP_VIEWS
for rule_, view_func_ in SKIP_VIEWS: # type: str, func
if rule_ == rule and view_func == view_func_:
return
return super(CustomFlask, self).add_url_rule(rule, endpoint, view_func, **options)
app = CustomFlask(__name__)
app.register_blueprint(simple_page)
app.run(debug=True)
Second way:
two.py - default blueprint with endpoint
from flask import Blueprint
bp_two = Blueprint('simple_page2', __name__, )
#bp_two.route('/my/route/')
def default():
return 'default'
test.py - your blueprint + app
from flask import Flask, Blueprint
from two import bp_two
your_bp = Blueprint('simple_page', __name__, )
#your_bp.route('/my/route/')
def new_route():
return 'new'
app = Flask(__name__)
# register blueprint and turn off '/my/route/' endpoint
app.register_blueprint(bp_two, **{'url_defaults': {'/my/route/': None}})
app.register_blueprint(your_bp)
app.run(debug=True)
Run app. Open /my/route/. You will see that default endpoint wasn't add/works.
Hope this helps.

Related

How can I add a prefix to all routes with the Flask REST JSON module?

The flask-rest-jsonapi quickstart shows that you can create a route() like this:
api.route(PostList, 'post_list', '/posts')
api.route(PostDetail, 'post_detail', '/posts/<int:id>')
But I want to have all of my routes to be something like /api/posts and /api/poss/<int:id> and I want to avoid repeating the /api part in every route(). When I try to use a blueprint here, like this:
api_bp = Blueprint('API', __name__, url_prefix='/api')
api = Api(app, api_bp)
api.route(PostList, 'post_list', '/posts')
api.route(PostDetail, 'post_detail', '/posts/<int:id>')
app.register_blueprint(api_bp)
The endpoint is still /posts and not /api/posts. How do I properly make a URL prefix for all the routes?
Reading the discussions on Github do the following:
# Create blueprint
api_bp = Blueprint('API', __name__, url_prefix='/api')
# Create Api instance only passing the blueprint
api = Api(blueprint=api_bp)
# register routes
api.route(PostList, 'post_list', '/posts')
api.route(PostDetail, 'post_detail', '/posts/<int:id>')
# initialize Api instance to App
api.init_app(app)
Don't forget that the view names will change. i.e view 'post_list' becomes 'API.post_list' so you have to adjust your schemes, but not your route declarations. This is also discussed in the linked Github discussion.
You can use flask and Flask-RESTful here. You just need to create flask APP first and configure Flask-RESTful API with prefix.
Here is the example
from flask import Flask, request
from flask_restful import Api
app = Flask(__name__)
api = Api(app, prefix="/api")
Now you can add resource to the api.route as below
import types
api.route = types.MethodType(api_route, api)
def api_route(self, *args, **kwargs):
"""Add resource to the api route"""
def wrapper(cls):
self.add_resource(cls, *args, **kwargs)
return cls

Hierarchy of routes without overwriting handlers

I'm writing a flask application.
It makes sense to have multiple endpoints, like that:
prefix + '/'
prefix + '/<id>'
prefix + '/<id>/parameters'
prefix + '/<id>/parameters/<param>'
However, if I try to declare them all inside a blueprint, I'm getting a AssertionError: Handler is overwriting existing for endpoint _blueprintname_._firsthandlername_
Is there any way around this? I know it's been straight-forwardly done before, in technologies like .net core. Thanks in advance.
If you plan to add many parameters in your routes, you could have a look at this module for flask.
It helps you assigning routes to resources.
You could build up a set of routes as follows:
from flask import Flask, request
from flask_restful import Resource, Api, reqparse
app = Flask(__name__)
api = Api(app)
class Some(Resource):
def get(self, id=None, params=None):
"""
In order to allow empty params for routes, the named arguments
must be initialized
"""
if id and params:
return {'message':'this is get with id and params'}
if id:
return {'message':'this is get with id'}
return {'message':'this is get'}
def post():
"""
One can use here reqparse module to validate post params
"""
return {'message':'this is post'}
# Add the resource to the service
api.add_resource(Some, '/', '/<string:id>','/<string:id>/<string:params>', ...)
# start the service
if __name__ == '__main__':
app.run(debug=True)

Flask: get current blueprint webroot

I am building a Flask app with a blueprint mounted on two different endpoint (one is a legacy alias to the other).
In my blueprint class:
ldp = Blueprint('ldp', __name__)
#ldp.route('/<path:uuid>', methods=['GET'])
#ldp.route('/', defaults={'uuid': None}, methods=['GET'],
strict_slashes=False)
def get_resource(uuid):
# Route code...
In my main server code:
app = Flask(__name__)
app.config.update(config['flask'])
app.register_blueprint(ldp, url_prefix='/new_ep')
# Legacy endpoint. #TODO Deprecate.
app.register_blueprint(ldp, url_prefix='/old_ep')
How can I get the actual URL of the request up to the /old_ep or /new_ep part in the route method, e.g. http://localhost:5000/new_ep?
So far I have used
request.host_url + request.path.split('/')[1]
but it looks quite inelegant and possibly error-prone. I would like to use the information from the blueprint setup if possible.
Thanks for your help.
EDIT: I could get to the Blueprint instance from within the request with
current_app.blueprints[request.blueprint]
and I was hoping that the url_prefix attribute that I set when registering the blueprint was there, but it is None instead. As I read from the documentation for the supposedly related iter_blueprints() method, apparently these blueprints are listed without regard of how many times and with which parameters they were registered. Too bad.
Here is a full working example to get the idea based off issue 612
from flask import Flask, Blueprint, url_for, request, g
bp = Blueprint('whatever', __name__)
#bp.url_defaults
def bp_url_defaults(endpoint, values):
url_prefix = getattr(g, 'url_prefix', None)
if url_prefix is not None:
values.setdefault('url_prefix', url_prefix)
#bp.url_value_preprocessor
def bp_url_value_preprocessor(endpoint, values):
g.url_prefix = values.pop('url_prefix')
#bp.route('/something')
def index():
return 'host prefix is %s%s' % (request.host_url, g.url_prefix)
app = Flask(__name__)
app.register_blueprint(bp, url_prefix='/new_ep', url_defaults={'url_prefix': 'new_ep'})
app.register_blueprint(bp, url_prefix='/old_ep', url_defaults={'url_prefix': 'old_ep'})

How to protect custom endpoints using BasicAuth?

Say I have enabled authentication to the resources using BasicAuth:
class MyBasicAuth(BasicAuth):
def check_auth(self,username,password,allowed_roles,resource,method):
return username == 'secretusername' and password == 'secretpass'
I also have custom routes which are used to manage documents from a HTML view. How do I use the same MyBasicAuth to protect the all the custom routes? I also need to implement logic which authenticates using the above MyBasicAuth.
Please help me with this. It's for personal use, so I preferred hard coding the username and password.
If you are trying to use a custom end-point Authentication you will find it difficult as mentioned here:
https://github.com/pyeve/eve/issues/860
I ended up writing a wrapper to get around the issue of 'resource' not being passed to 'requires_auth':
def auth_resource(resource):
def fdec(f):
#wraps(f)
def wrapped(*args, **kwargs):
return f(resource=resource, *args, **kwargs)
return wrapped
return fdec
This way you can define in your DOMAIN an authentication class:
DOMAIN = {
'testendpoint'= {'authentication':MyCustomAuthetication},
'otherendpoints'=...
And in my app I have wrapped the requires_auth decorator and added this as a authentication resource.
#app.route('/testendpoint/<item>', methods=['GET'])
#auth_resource('testendpoint')
#requires_auth('item')
def my_end_point_function(*args, **kwargs):
dosomthinghere
As long as an authentication class is defined in the settings file for an endpoint, this also allows you to reuse any authentication defined in another endpoint which may be handy if you want to make sure all the endpoints use the same authentication.
You can leverage the requires_auth decorator which is used internally by Eve itself. That way, your auth class will also be used to protect your custom routes:
from eve import Eve
from eve.auth import requires_auth
app = Eve()
#app.route('/hello')
#requires_auth('resource')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
If you are using flask blueprints for your custom routes, you can add a before request function for your blueprint to do that.
First, create a function to check authentication from blueprints. You need to get the Authorization header from the flask request by yourself, like this:
from flask import request, abort, current_app
from werkzeug.http import parse_authorization_header
def check_blueprint_auth():
if 'Authorization' not in request.headers:
print('Authorization header not found for authentication')
return abort(401, 'Authorization header not found for authentication')
header = parse_authorization_header(request.headers['Authorization'])
username = None if header is None else header['username']
password = None if header is None else header['password']
return username == 'secretusername' and password == 'secretpass'
Then, you can set this function to be called before each blueprint's request. Below is an example of a blueprint definition, setting the before_request function:
from flask import Blueprint, current_app as app
# your auth function
from auth import check_blueprint_auth
blueprint = Blueprint('prefix_uri', __name__)
# this sets the auth function to be called
blueprint.before_request(check_blueprint_auth)
#blueprint.route('/custom_route/<some_value>', methods=['POST'])
def post_something(some_value):
# something
Finally, you need to bind the blueprint with your eve app. An example on how to bind blueprints, taken in part from here:
from eve import Eve
# your blueprint
from users import blueprint
from flask import current_app, request
app = Eve()
# register the blueprint to the main Eve application
app.register_blueprint(blueprint)
app.run()
Hope that helps.

Access Flask config outside of application factory

I'm currently using the Flask Application Factory pattern with Blueprints. The issue that I'm having is how do I access the app.config object outside of the application factory?
I don't need all the configuration options from the Flask app. I just need 6 keys. So the current way I do this is when the create_app(application factory) is called, I basically create a global_config dictionary object and I just set the global_config dictionary to have the 6 keys that I need.
Then, the other modules that need those configuration options, they just import global_config dictionary.
I'm thinking, there has to be a better way to do this right?
So, on to the code
My current init.py file:
def set_global_config(app_config):
global_config['CUPS_SAFETY'] = app_config['CUPS_SAFETY']
global_config['CUPS_SERVERS'] = app_config['CUPS_SERVERS']
global_config['API_SAFE_MODE'] = app_config['API_SAFE_MODE']
global_config['XSS_SAFETY'] = app_config['XSS_SAFETY']
global_config['ALLOWED_HOSTS'] = app_config['ALLOWED_HOSTS']
global_config['SQLALCHEMY_DATABASE_URI'] = app_config['SQLALCHEMY_DATABASE_URI']
def create_app(config_file):
app = Flask(__name__, instance_relative_config=True)
try:
app.config.from_pyfile(config_file)
except IOError:
app.config.from_pyfile('default.py')
cel.conf.update(app.config)
set_global_config(app.config)
else:
cel.conf.update(app.config)
set_global_config(app.config)
CORS(app, resources=r'/*')
Compress(app)
# Initialize app with SQLAlchemy
db.init_app(app)
with app.app_context():
db.Model.metadata.reflect(db.engine)
db.create_all()
from authenication.auth import auth
from club.view import club
from tms.view import tms
from reports.view import reports
from conveyor.view import conveyor
# Register blueprints
app.register_blueprint(auth)
app.register_blueprint(club)
app.register_blueprint(tms)
app.register_blueprint(reports)
app.register_blueprint(conveyor)
return app
An example of a module that needs access to those global_config options:
from package import global_config as config
club = Blueprint('club', __name__)
#club.route('/get_printers', methods=['GET', 'POST'])
def getListOfPrinters():
dict = {}
for eachPrinter in config['CUPS_SERVERS']:
dict[eachPrinter] = {
'code': eachPrinter,
'name': eachPrinter
}
outDict = {'printers': dict, 'success': True}
return jsonify(outDict)
There has to be a better way then passing a global dictionary around the application correct?
There is no need to use global names here, that defeats the purpose of using an app factory in the first place.
Within views, such as in your example, current_app is bound to the app handling the current app/request context.
from flask import current_app
#bp.route('/')
def example():
servers = current_app.config['CUPS_SERVERS']
...
If you need access to the app while setting up a blueprint, the record decorator marks functions that are called with the state the blueprint is being registered with.
#bp.record
def setup(state):
servers = state.app.config['CUPS_SERVERS']
...

Categories

Resources