Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested Blueprints in Flask?

I'm still new to Flask, so there may be an obvious way to accomplish this, but I haven't been able to figure it out so far from the documentation. My app is divided into several mostly disparate parts that share things like users/sessions/security and base template and everything but mostly do not interact much, and should be routed under different paths like /part1/.... I think this is pretty much exactly what blueprints are for. But what if I need to group routes and logic further under a blueprint?

For example, I have blueprint1 with url_prefix='/blueprint1' and maybe under that I want to have a collection of views revolving around a user sharing photos and other users commenting on them. I can't think of a better way of doing it than:

# app/blueprints/blueprint1/__init__.py

blueprint1 = Blueprint('blueprint1', __name__, template_folder='blueprint1')

@blueprint1.route('/photos')
def photos_index():
    return render_template('photos/index.html')

@blueprint.route('/photos/<int:photo_id>')
def photos_show(photo_id):
    photo = get_a_photo_object(photo_id)
    return render_template('photos/show.html', photo=photo)

@blueprint.route('/photos', methods=['POST'])
def photos_post():
    ...

The problem here is that all the views related to the photos section of blueprint1 are located at the "top level," right with maybe blueprints for videos or audio or whatever (named videos_index()...). Is there any way to group them in a more hierarchical manner, like how the templates go under the 'blueprint1/photos' sub-directory? Of course I can put all the photo views in their own module to keep them organized separately, but what if I want to change the parent 'blueprint1/photos' path to something else? I'm sure I can invent a function or decorator that groups related routes under the same root path, but then I still have to name all the functions with the photos_ prefix and reference them like url_for('blueprint1.photos_show') It seems like blueprints are the answer when a Flask app gets large and you need to group and compartmentalize similar parts together, but you cannot do the same thing when the blueprints themselves get large.

For reference, in Laravel you can group related "views" under a Controller class where the views are methods. Controllers can reside in hierarchical namespaces like app\Http\Controllers\Blueprint1\Photocontroller, routes can be grouped together like

Route::group(['prefix' => 'blueprint1'], function() {

    Route::group(['prefix' => 'photos'], function() {

        Route::get('/', ['as' => 'blueprint.photos.index', 'uses' => 'ModelApiController@index']);
        Route::post('/', ['as' => 'blueprint.photos.store', 'uses' => 'ModelApiController@store']);
        Route::get('/{id}', ['as' => 'blueprint.photos.get', 'uses' => 'ModelApiController@get'])
            ->where('id', '[0-9]+');

    });

});

and routes can be gotten like action('Blueprint1\PhotoController@index').

If only I could make a photos blueprint, then just do blueprint1.register_blueprint(photos_blueprint, url_prefix='/photos') or the like, these problems would pretty much be solved. Unfortunately Flask does not seem to support nesting blueprints like this. Is there an alternative way to handle this problem?

like image 216
JaredL Avatar asked Oct 07 '15 22:10

JaredL


People also ask

How do you use blueprints in flask?

To use any Flask Blueprint, you have to import it and then register it in the application using register_blueprint() . When a Flask Blueprint is registered, the application is extended with its contents. While the application is running, go to http://localhost:5000 using your web browser.

Why blueprints are used in flask?

Flask uses a concept of blueprints for making application components and supporting common patterns within an application or across applications. Blueprints can greatly simplify how large applications work and provide a central means for Flask extensions to register operations on applications.

What is Blueprint in flask RESTful?

A blueprint in Flask is an object to structure a Flask application into subsets. This helps in organizing code and separating functionality. For Flask-RESTful this can be used to create an application subset for your api. So for example you have a core blueprint, an auth blueprint and besides that an api blueprint.

What is the architecture of flask?

Flask is a micro web framework written in Python. It is classified as a microframework because it does not require particular tools or libraries. It has no database abstraction layer, form validation, or any other components where pre-existing third-party libraries provide common functions.


3 Answers

Unfortunately, nested blueprints are not a current feature in Flask. You'll have to do it manually. You could probably code something that works for your specific case, but a general solution has not been added to Flask. There has been some discussion on the issue tracker:

  • https://github.com/mitsuhiko/flask/issues/593
  • https://github.com/mitsuhiko/flask/issues/1548
  • https://github.com/pallets/flask/issues/3215

Adding nestable blueprints into Flask is not as trivial as automatically appending a prefix to routes. There are many other features of blueprints that need to be considered when nesting that make a general implementation significantly more complicated. The reason this has not been implemented yet is that no one in the community has had a great enough need for it that wasn't solved by a quick workaround vs contributing a general implementation.

like image 54
davidism Avatar answered Oct 23 '22 03:10

davidism


UPDATE

Flask 2 was released with support for nested blueprints.

[ START: Part from the docs ]

Nesting Blueprints

It is possible to register a blueprint on another blueprint.

parent = Blueprint('parent', __name__, url_prefix='/parent')
child = Blueprint('child', __name__, url_prefix='/child')
parent.register_blueprint(child)
app.register_blueprint(parent)

The child blueprint will gain the parent’s name as a prefix to its name, and child URLs will be prefixed with the parent’s URL prefix.

url_for('parent.child.create')
/parent/child/create

Blueprint-specific before request functions, etc. registered with the parent will trigger for the child. If a child does not have an error handler that can handle a given exception, the parent’s will be tried.

[ END: Part from the docs ]

Source: https://flask.palletsprojects.com/en/2.0.x/blueprints/#nesting-blueprints


OLD ANSWER

My hacky work around is that I made a class called ParentBP that has the following code

from typing import List
from flask import Blueprint

class ParentBP(object):
   name: str
   url_prefix: str
   subdomain: str
   blueprints: List[Blueprint]

def __init__(self, name="", url_prefix="", subdomain="") -> None:
    self.name = name
    self.url_prefix = url_prefix
    self.subdomain = subdomain
    self.blueprints = []

def register_blueprint(self, bp: Blueprint) -> None:
    bp.name = self.name + "-" + bp.name
    bp.url_prefix = self.url_prefix + (bp.url_prefix or "")
    if self.subdomain:
        bp.subdomain = self.subdomain
    self.blueprints.append(bp)

so you can call it similar to a blueprint like below

blueprint1 = Blueprint("blueprint1", __name__)
blueprint2 = Blueprint("blueprint2", __name__, url_prefix="/bp2")

api_v1 = ParentBP("api-v1", url_prefix="/api/v1")
api_v1.register_blueprint(blueprint1)
api_v1.register_blueprint(blueprint)

to make the interface similar to normal registering of blueprints to the flask app, I extended the Flask class as follows

class ExtendedFlask(Flask):

def register_blueprint(self, blueprint: Union[Blueprint, ParentBP], **options: Any) -> None:
    if isinstance(blueprint, ParentBP):
        for bp in blueprint.blueprints:
            super().register_blueprint(bp, **options)
    else:
        return super().register_blueprint(blueprint, **options)

now you can normally do the following

app = ExtendedFlask(__name__)
app.register_blueprint(api_v1)
like image 12
omar ali Avatar answered Oct 23 '22 03:10

omar ali


I made a class called NestedBlueprint to hack it.

class NestedBlueprint(object):
    def __init__(self, blueprint, prefix):
        super(NestedBlueprint, self).__init__()
        self.blueprint = blueprint
        self.prefix = '/' + prefix

    def route(self, rule, **options):
        rule = self.prefix + rule
        return self.blueprint.route(rule, **options)

Here is my base file which contains the blueprint: panel/__init__.py

from flask import Blueprint

panel_blueprint = Blueprint(PREFIX, __name__, url_prefix='/panel')

from . import customize

Here is the specific/nested file which contains nested blueprint: panel/customize.py

from rest.api.panel import panel_blueprint
from rest.api.util.nested_blueprint import NestedBlueprint

nested_blueprint = NestedBlueprint(panel_blueprint, 'customize')


@nested_blueprint.route('/test', methods=['GET'])
def test():
    return ':)'

You can then call like this:

$ curl http://localhost:5000/panel/customize/test
:)
like image 6
Abhishek Gupta Avatar answered Oct 23 '22 02:10

Abhishek Gupta