Flask: get current blueprint webroot - python

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'})

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

Provide standard data to Flask template

I would like to be able to generate a navbar based on an object's contents, basically having a navbar array or class that holds subpages and generates the appropriate navbar with collapsing parts etc. I have experience with Laravel in PHP which is similar to Flask but I can't figure out a way to do it easily. I would have to provide a set of data objects to every single page since it's part of the layout but would prefer not to have to specify it specifically for each page. Is there a way to do this?
So far I only have the basics, an app factory, view and blueprint:
Factory
def create_app():
app = Flask(__name__)
app.config.from_pyfile('config.py')
app.register_blueprint(blueprint_index)
return app
Blueprint
from flask import Blueprint, render_template, session
blueprint_index = Blueprint('simple_page', __name__)
#blueprint_index.route('/')
def index():
if 'text' in session:
session['text'] += 1
else:
session['text'] = 0
return render_template('pages/index.html', text=str(session['text']))
Ignore the little bit of debug text I added to the route.
You could do this with a context processor. Add this to the end of your blueprint code-block:
#blueprint_index.context_processor
def inject_text():
return dict(text=session['text'])
{{text}} will now be available within every template on that blueprint.

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)

Overwrite route in flask blueprint

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.

How to use Flasgger with Flask applications using Blueprints?

I am adding Swagger UI to my Python Flask application using Flasgger. Most common examples on the Internet are for the basic Flask style using #app.route:
from flasgger.utils import swag_from
#app.route('/api/<string:username>')
#swag_from('path/to/external_file.yml')
def get(username):
return jsonify({'username': username})
That works.
In my application however, I am not using #app.route decorators to define the endpoints. I am using flask Blueprints. Like following:
from flask import Flask, Blueprint
from flask_restful import Api, Resource
from flasgger.utils import swag_from
...
class TestResourceClass(Resource):
#swag_from('docs_test_get.yml', endpoint='test')
def get() :
print "This is the get method for GET /1.0/myapi/test endpoint"
app = Flask(__name__)
my_api_blueprint = Blueprint('my_api', __name__)
my_api = Api(my_api_blueprint)
app.register_blueprint(my_api_blueprint, url_prefix='/1.0/myapi/')
my_api.add_resource(TestResourceClass, '/test/'
endpoint='test',
methods=['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
....
As seen above, I used #swag_from decorator on the TestResourceClass.get() method which is bound to the GET method endpoint. I also have the endpoint=test matching in the two places.
But I am not getting anything on the Swagger UI, it is all blank. The docs_test_get.yml file does contain the valid yaml markup to define the swagger spec.
What am I missing? How can I get Flasgger Swagger UI working with Flask Blueprint based setup?
Now there is an example of blueprint app in https://github.com/rochacbruno/flasgger/blob/master/examples/example_blueprint.py
"""
A test to ensure routes from Blueprints are swagged as expected.
"""
from flask import Blueprint, Flask, jsonify
from flasgger import Swagger
from flasgger.utils import swag_from
app = Flask(__name__)
example_blueprint = Blueprint("example_blueprint", __name__)
#example_blueprint.route('/usernames/<username>', methods=['GET', 'POST'])
#swag_from('username_specs.yml', methods=['GET'])
#swag_from('username_specs.yml', methods=['POST'])
def usernames(username):
return jsonify({'username': username})
#example_blueprint.route('/usernames2/<username>', methods=['GET', 'POST'])
def usernames2(username):
"""
This is the summary defined in yaml file
First line is the summary
All following lines until the hyphens is added to description
the format of the first lines until 3 hyphens will be not yaml compliant
but everything below the 3 hyphens should be.
---
tags:
- users
parameters:
- in: path
name: username
type: string
required: true
responses:
200:
description: A single user item
schema:
id: rec_username
properties:
username:
type: string
description: The name of the user
default: 'steve-harris'
"""
return jsonify({'username': username})
app.register_blueprint(example_blueprint)
swag = Swagger(app)
if __name__ == "__main__":
app.run(debug=True)
You just need to add your blueprint's name when you reference endpoint. Blueprints create namespaces. Example below. And useful tip: use app.logger.info(url_for('hello1')) for debugging problems with endpoint - it shows very useful error messages like this Could not build url for endpoint 'hello1'. Did you mean 'api_bp.hello1' instead?.
from flask import Flask, Blueprint, url_for
from flask_restful import Api, Resource
from flasgger import Swagger, swag_from
app = Flask(__name__)
api_blueprint = Blueprint('api_bp', __name__)
api = Api(api_blueprint)
class Hello(Resource):
#swag_from('hello1.yml', endpoint='api_bp.hello1')
#swag_from('hello2.yml', endpoint='api_bp.hello2')
def get(self, user=''):
name = user or 'stranger'
resp = {'message': 'Hello %s!' % name}
return resp
api.add_resource(Hello, '/hello', endpoint='hello1')
api.add_resource(Hello, '/hello/<string:user>', endpoint='hello2')
app.register_blueprint(api_blueprint)
swagger = Swagger(app)
app.run(debug=True)
swag_from function have some error to parse file path.you can use doc string to define api in get() first.
flasgger will parse MethodView() method like get,post.
Seems flasgger does not work or has no proper support for blue print style Flask definitions (yet). I used https://github.com/rantav/flask-restful-swagger which worked great!

Categories

Resources