blueprint of blueprints (Flask) - python

I have a series of blueprints I'm using, and I want to be able to bundle them further into a package I can use as seamlessly as possible with any number of other applications. A bundle of blueprints that provides an entire engine to an application. I sort of created my own solution, but it is manual and requires too much effort to be effective. It doesn't seem like an extension, and it is more than one blueprint(several that provide a common functionality).
Is this done? How?
(Application dispatching methods of tying together several programs might work isn't what I'm looking for)

Check this out: Nesting Blueprints → https://flask.palletsprojects.com/en/2.0.x/blueprints/#nesting-blueprints
parent = Blueprint('parent', __name__, url_prefix='/parent')
child = Blueprint('child', __name__, url_prefix='/child')
parent.register_blueprint(child)
app.register_blueprint(parent)

I wish the Blueprint object has a register_blueprint function just as the Flask object does. It would automatically place and registered blueprints under the current Blueprints' url.

The simplest way would be to create a function that takes an instance of a Flask application and registers all your blueprints on it in one go. Something like this:
# sub_site/__init__.py
from .sub_page1 import bp as sb1bp
from .sub_page2 import bp as sb2bp
# ... etc. ...
def register_sub_site(app, url_prefix="/sub-site"):
app.register_blueprint(sb1bp, url_prefix=url_prefix)
app.register_blueprint(sb2bp, url_prefix=url_prefix)
# ... etc. ...
# sub_site/sub_page1.py
from flask import Blueprint
bp = Blueprint("sub_page1", __name__)
#bp.route("/")
def sub_page1_index():
pass
Alternately, you could use something like HipPocket's autoload function (full disclosure: I wrote HipPocket) to simplify the import handling:
# sub_site/__init__.py
from hip_pocket.tasks import autoload
def register_sub_site(app,
url_prefix="/sub-site",
base_import_name="sub_site"):
autoload(app, base_import_name, blueprint_name="bp")
However, as it currently stands you couldn't use the same structure as example #1 (HipPocket assumes you are using packages for each Blueprint). Instead, your layout would look like this:
# sub_site/sub_page1/__init__.py
# This space intentionally left blank
# sub_site/sub_page1/routes.py
from flask import Blueprint
bp = Blueprint("sub_page1", __name__)
#bp.route("/")
def sub_page1_index():
pass

I have solution for myself how to load blueprints defined in configuration, so then you can have something like CORE_APPS = ('core', 'admin', 'smth') in config and when you construct app you can register those apps (of course those strings in CORE_APPS must be the names of the files you want to import in your python path).
So I'm using function to create app:
app = create_app()
def create_app():
app = Flask(__name__)
# I have class for my configs so configuring from object
app.config.from_object('configsClass')
# does a lot of different stuff but the main thing could help you:
from werkzeug.utils import import_string
for app in app.config['CORE_APPS']
real_app = import_string(app)
app.register_blueprint(real_app)
After that your blueprint should be registered. Of course you can have different format in configs to support custom url prefixes and so on and so on :)
Of course you can also do something like this in your main blueprint, so in the application creation you will need to register that one main blueprint.

Related

How to append a prefix to a flask blueprint that already has a blueprint? [duplicate]

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?
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)
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.
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
:)
Here is my workaround:
When importing a blueprint, I define my nested routes:
app.register_blueprint(product_endpoints, url_prefix='/sites/<int:site_id>/menus/<int:menu_id>/categories/<int:category_id>/products/<int:product_id>')
app.register_blueprint(category_endpoints, url_prefix='/sites/<int:site_id>/menus/<int:menu_id>/categories/<int:category_id>')
app.register_blueprint(menu_endpoints, url_prefix='/sites/<int:site_id>/menus/<int:menu_id>')
app.register_blueprint(site_endpoints, url_prefix='/sites/<int:site_id>')
And inside the blueprints, I'm reusing route parse functions. For example, in the product_endpoints file:
from category_endpoints import get_category_data
product_endpoints = Blueprint('product_endpoints', __name__)
#product_endpoints.url_value_preprocessor
def get_product_data(endpoint, values):
if 'category_id' in values:
get_category_data(endpoint, values)
product = Product.get_by_id(int(values.pop('product_id')))
if not product:
abort(404)
g.product = product
and in category_endpoints file:
from menu_endpoints import get_menu_data
category_endpoints = Blueprint('category_endpoints', __name__)
#category_endpoints.url_value_preprocessor
def get_category_data(endpoint, values):
if 'menu_id' in values:
get_menu_data(endpoint, values)
category = ProductCategory.get_by_id(int(values.pop('category_id')))
if not category:
abort(404)
g.category = category
etc... With that approach, my blueprint is also usable with direct routes like /products/<int:product_id>.
This approach worked for me very well. I hope it can also help you.

How to put decorators on `app` when using Application Factory with Flask?

I'm trying to define some global constants for my app, and have found that this can be done with a function that's decorated as #app.context_processor.
However, the issue is that I don't have an app variable. My application uses an application factory, and I'd like to keep it that way. Is there a different way to register a function as the context_processor for my app?
One option I have seen is to apply the decorator to each Blueprint instead of applying it to the app. That's something I would like to avoid though, since it would lead to a lot of duplicate code.
The issue is that there is no app object in case of factories. You have a create_app function where the app gets created.
So to install the context processors you can use create_app itself
def create_app(config_filename):
app = Flask(__name__)
app.config.from_pyfile(config_filename)
from yourapplication.model import db
db.init_app(app)
from yourapplication.context_processor import myprocessor
app.context_processor(myprocessor)
from yourapplication.views.frontend import frontend
app.register_blueprint(frontend)
return app
You could also have the function in the same app.py file (wherever the create_app() function is written). In such a case, you could simply register the context_processor without importing it.
Another approach is to do it in a blueprint as shown in below
Flask context processors functions
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)

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?
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)
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.
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
:)
Here is my workaround:
When importing a blueprint, I define my nested routes:
app.register_blueprint(product_endpoints, url_prefix='/sites/<int:site_id>/menus/<int:menu_id>/categories/<int:category_id>/products/<int:product_id>')
app.register_blueprint(category_endpoints, url_prefix='/sites/<int:site_id>/menus/<int:menu_id>/categories/<int:category_id>')
app.register_blueprint(menu_endpoints, url_prefix='/sites/<int:site_id>/menus/<int:menu_id>')
app.register_blueprint(site_endpoints, url_prefix='/sites/<int:site_id>')
And inside the blueprints, I'm reusing route parse functions. For example, in the product_endpoints file:
from category_endpoints import get_category_data
product_endpoints = Blueprint('product_endpoints', __name__)
#product_endpoints.url_value_preprocessor
def get_product_data(endpoint, values):
if 'category_id' in values:
get_category_data(endpoint, values)
product = Product.get_by_id(int(values.pop('product_id')))
if not product:
abort(404)
g.product = product
and in category_endpoints file:
from menu_endpoints import get_menu_data
category_endpoints = Blueprint('category_endpoints', __name__)
#category_endpoints.url_value_preprocessor
def get_category_data(endpoint, values):
if 'menu_id' in values:
get_menu_data(endpoint, values)
category = ProductCategory.get_by_id(int(values.pop('category_id')))
if not category:
abort(404)
g.category = category
etc... With that approach, my blueprint is also usable with direct routes like /products/<int:product_id>.
This approach worked for me very well. I hope it can also help you.

Why there is no option to add custom url converters to blueprints like for main app?

In this post and in official docs we saw how to add custom url converters for main app object.
Here is short example:
app = Flask(__name__)
app.url_map.converters['list'] = ListConverter
But how to do it for blueprints? This global (app level) custom converter is unavailable for blueprints.
In source code I haven't found such posibility...
The technical reason why you can't have custom URL converters on a blueprint is that unlike applications, blueprints do not have a URL map.
When you use the blueprint's route decorator or add_url_map() method all the blueprint does is record the intention to call the application versions of these methods later when register_blueprint() is called.
I'm not sure there is a benefit in allowing blueprint specific url converters. But I think it would be reasonable to allow a blueprint to install an app wide converter. That could use the same techniques as other blueprint app-wide handlers, like before_app_request, for example.
def add_app_url_converter(self, name, f):
self.record_once(lambda s: s.app.url_map.converters[name] = f
return f
Blueprint.add_app_url_converter = add_app_url_converter
# ...
bp = Blueprint('mybp', __name__)
bp.add_app_url_converter('list', ListConverter)

How to share the global app object in flask?

I am using flask and trying to the following.
I have defined a main.py file through which I want to run my app ie python main.py -
from flask import Flask
from view import tags
app = Flask(__name__)
#app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
I have defined a package named view in which I will be declaring my different view modules, each having its own routes. view.tags.py -
from flask import Flask
app = Flask(__name__)
#app.route('/e')
def hello_world2():
return 'Hello World!'
So I need to have the global app object in my main.py for running the server, as well as in the view classes of my package for registering the routes. So how do I create the global app object and share it between all classes ?
Thanks,
Murtaza
You can import current_app from flask. It stores a reference to the global application object.
from flask import current_app as app
def home():
return render_template('base.html', name=app.name)
First, I would suggest to take a look at Blueprints http://flask.pocoo.org/docs/blueprints/ This will help to organize the app easily.
Also take a look at http://flask.pocoo.org/docs/api/#flask.current_app flask.current_app, the way how to get your app instance in other modules.
This link also could be helpful on how to organize and build flask app (it is not ideal for sure, but can give you some ideas) - Large-app-how-to.md
Have fun :)
One way is to create an overall package and adding a __init__.py file under that where you declare all global variables. In your case for example, you can create something like:
myapplication/
* __init__.py
* myviews/
* __init__.py
* view.py
* tags.py
etc
Now you add the following code in the __init__.py file:
app = Flask(__name__)
You can now use this app variable anywhere as long as you import the package myapplication.
import myapplication.myviews.view
Just import it from your other files. Perhaps the best way to do this is to put your app object in one single file and have everything else import from it.
For example, your main.py could still have:
from flask import Flask
from view import tags
app = Flask(__name__)
And then in other files, you could do:
from .main import app
or, if outside your package, just use the complete string
from mypackagename.main import app
One thing to be careful of is circular imports. The easiest way to handle this issue is to create your app first and then import whatever else you need to from your base file after you create it.
So for example:
from flask import Flask
app = Flask(__name__)
# do some stuff with app
from .views import view1, view2
from .server import run
So long as you put the imports after you've created app, like the above, you shouldn't have an issue with circular imports.
Regarding import and use of current_app from flask in a "helper" python function in a separate source file, this works as long as a current app context has already been set up (E.g. web request received). I have a case where, during application initialization (app.run not yet invoked), app.logger is invoked in the helper function.
Before I fixed it (see below), I got a stack trace punctuated by "RuntimeError: Working outside of application context".
Sample solution:
main.py:
import helper
...
app = Flask(__name__.split('.')[0],
template_folder="templates",
static_folder="static")
...
# Fix: Create an app context
with app.app_context():
helper.dbopen(...)
...
app.run(...)
helper.py:
from flask import current_app as app
...
def dbopen():
app.logger.info(...)
...
If you have a file AppName.py in which you define app, and then you have another file Foobar.py that needs it, you can always say in AppName.py:
import Foobar
Foobar.app = app
Then in Foobar.py you should be able to use app in your functions. One thing you want to be careful of is that you can't have code in Foobar.py that runs immediately when the file is called the depends on the app variable which is passed in after the import.

Categories