Flask Blueprint Initialization - Initializing some global variables
Asked Answered
G

2

7

I'm new to Flask and I'm about to write a larger application. So far I'v divided functionality into blueprints. Now I want to be able to set some global variable from within a blueprint during initialization of it (so outside of a request context). Basically, I want to automatically initialize a navigation list for certain blueprints, so each blueprint needs to tell my base application how it wants to be named and what its default route is.

My goal is that other people can extend my application by just putting their custom blueprint into my application's "plugin" folder. In this scenario, my application doesn't know their routes or names. It needs to automatically learn it while loading the specific blueprint...

To explain it in some other way: I have a main application containing some sub applications implemented as blueprints. The main application should hold a navigation bar referencing to all sub applications (blueprints). How can the blueprint register something in that main menu navigation variable on (e.g. on initialization)?

(I didn't find a way to access something like "self.parent" or the application context from a blueprint. Don't blueprints have something like a constructor?)

Galer answered 30/1, 2016 at 21:1 Comment(0)
A
3

so each blueprint needs to tell my base application how it want's to be named and what it's default route is.

When you create a blueprint you already pass its name in the first parameter:

simple_page = Blueprint('simple_page')

You can pass to the constructor the url_prefix value too

simple_page = Blueprint('simple_page', url_prefix='/pages')

I have a main application containing some sub applications implemented as blueprints. The main application should hold a navigation bar referencing to all sub applications (blueprints)

This is an example in one python module, you should split each blueprint in its own module.

from flask import Flask, Blueprint, render_template

# ADMIN
admin = Blueprint('admin', __name__, url_prefix='/admin')

@admin.route('/')
def admin_index():
    return 'Admin module'

@admin.route('/settings')
def settings():
    return 'Admin Settings'


# USER
user = Blueprint('user', __name__, url_prefix='/user')

@user.route('/')
def user_index():
    return 'User module'

@user.route('/profile')
def profile():
    return 'User Profile'


app = Flask(__name__)
app.register_blueprint(admin)
app.register_blueprint(user)

@app.route('/')
def index():
    return render_template('index.html', blueprints=app.blueprints)

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True, port=7000)

The Jinja2 template. Remember to place this file in the templates folder in you root project, which is where flask search the templates by default.

<ul>
  {% for bp_name, bp in blueprints.iteritems() %}
    <li><a href="{{ bp.url_prefix }}">{{ bp.name }}</a></li>
  {% endfor %}
</ul>

From the flask main application object, in this case app, you can access the list of blueprints registered; app.blueprints, which is a python dictionary with the name of the blueprint that you passed in the constructor. So you pass this object to you template and loops on it to render the name and URL in the menu.

Furthermore, what if I want to enable blueprint authors to pass more data in a standardized way (e.g. in which color to display the link to his piece, or whatever...)

As this is a blueprint specific data, I think a good solution is to extend the Blueprint class and implement a custom method to save extra data in a standardized way and then access then from the main application object.

custom.py

from flask import Blueprint

class MyBlueprint(Blueprint):

    bp_data = {}

    def set_data(data):
        # here you can make extra task like ensuring if 
        # a minum group of value were provided for instance
        bp_data = data

admin.py

from .custom import MyBlueprint

admin = MyBlueprint('admin', __name__, url_prefix='/admin')
admin.set_data({'color': '#a569bd', 'enabled': true})

# all the blueprint's routes
...

app.py

from admin import admin

app = Flask(__name__)
app.register_blueprint(admin)

@app.route('/')
def index():
    for a, b in app.blueprints:
        print b.bp_data['color']
        print b.bp_data['enabled']
    ...

Of course, my custom Blueprint class needs more work on it, like validating what type of data its being passed or throwing an error if there isn't a required value, like; title, require_auth, etc. From this point, it's you who must define what is the minimum required data that a blueprint must provide to your main application to work properly.

Argenteuil answered 30/1, 2016 at 21:19 Comment(5)
Thank you for your answer. That is true, but this happens within the blueprint and is defined by the person writing the blueprint and adding it to my base application. But how can I now collect all blueprint main routes and names to generate a navigation out of it in the application context (e.g. saving it in the application context to use it later when URLs are called)? Furthermore, what if I want to enable blueprint authors to pass more data in a standardized way (e.g. in which color to display the link to his piece, or whatever...).Galer
well in the main application instance you have the blueprints dictionary, that you can loops to get each instance of the blueprint and access its properties. I will update my answer.Squinteyed
Thank's, that worked out and solved the core of my problem. Nevertheless, I'd really like to pass more settings/data from a blueprint to the application context... to further customize. Let's say a blueprint want's to tell its priority (the positionin in which it's link should be placed), or a color, or maybe whether to disable it from being listed...Galer
@j34, I update my aswer about how to pass more data between the application and the blueprints. Please check it and let me know if the solution works for you. Remember to mark the answer as correct if is valid to you.Squinteyed
Wow, I was thinking so much about that there must be some functionality provided by flask to do some custom blueprint initialization, that I didn't think of inheritance... Thank you very much for your effort.Galer
J
1

Here I present you a extensible app pattern that I usually use. It is proved that work fine.

Directory structure:

YourApp
|- plugins (will contain all the user's plugins)
     |- __init__.py (empty)
     |-plugin1
          |- __init__.py (empty)
          |- loader.py
|- app.py

app.py

from flask import Flask
import os

def plugin_loader(app):
  """ Function for discover new potencial plugins.
  After checking the validity (it means, check if it implements
  'register_as_plugin', load the user defined blueprint"""

  plugins = [x for x in os.listdir('./plugins')
             if os.path.isdir('./plugins/' + x)]
  for plugin in plugins:
    # TODO: Add checking for validation
    module = __import__('plugins.' + str(plugin) + '.loader', fromlist=['register_as_plugin'])

    app = module.register_as_plugin(app)

  return app

# Creates the flask app
app = Flask(__name__)
# Load the plugins as blueprints
app = plugin_loader(app)

print(app.url_map)


@app.route('/')
def root():
  return "Web root!"

app.run()

plugins/plugin1/loader.py

from flask import Blueprint


plugin1 = Blueprint('plugin1', __name__)

@plugin1.route('/version')
def version():
  return "Plugin 1"

def register_as_plugin(app):
  app.register_blueprint(plugin1, url_prefix='/plugin1')

  return app

So futures plugins should implement the register_as_plugin function, inside the loader.py file, inside their directory in plugins/.

For more information about directories discovery you can read https://docs.python.org/2/library/os.path.html. For more information about the dynamic import of modules you can read https://docs.python.org/2/library/functions.html#import.

Proved with Python 2.7.6, Flask 0.10.1 and Unix platform.

Janae answered 1/2, 2016 at 2:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.