Flask Blueprint Initialization - Initializing some global variables - python

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?)

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>{{ bp.name }}</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.

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.

Related

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.

Python: using database instance across submodules

I'm writing a Flask app and I instantiate a mongo database in main.py.
I've got a submodule called user.py that holds class User. main.py takes login credentials and sends it to the class User which handles the rest.
How can I cleanly pass my mongo instance to the User class? I've tried static variables in a config.py file but they don't work because the variables are always None when user.py tries to use them.
Right now I'm resorting to passing in mongo as a parameter, but this seems like a dirty way to do it considering there will be many modules. Here's my code;
# Setup app and database
app = Flask(__name__)
app.config['MONGO_URI'] = 'mongodb://'
mongo = PyMongo(app)
You can import mongo directly into your user.py module.
To avoid circular import error, you just have to move the imports to the bottom of the file. As long as you import User into main after app is defined, it should resolve the circular import:
user.py
from .main import mongo
# class User():
# ... Your Code
main.py
from flask import Flask
from flask_pymongo import PyMongo
app = Flask(__name__)
mongo = PyMongo(app)
from .user import User
Moving imports to the bottom are normally not a good idea, but in Flask is quite common. Below is an example of a similar scenario from the official flask documentation:
# app.py
from flask import Flask
app = Flask(__name__)
import app.views
# views.py
from app import app
#app.route('/')
def index():
return 'Hello World!'
http://flask-.readthedocs.io/en/0.6/patterns/packages/#simple-packages
Do the importing at the bottom of the file [...]
Every Python programmer hates them, and yet we just added some: circular imports (That’s when two modules depend on each other. In this case views.py depends on init.py). Be advised that this is a bad idea in general but here it is actually fine. The reason for this is that we are not actually using the views in init.py and just ensuring the module is imported and we are doing that at the bottom of the file.

Access a Flask extension that is defined in the app factory

I am using the app factory pattern to set up my Flask application. My app uses the Flask-Babel extension, and that is set up in the factory as well. However, I want to access the extension in a blueprint in order to use it,
The factory is in __init__.py.
def create_app(object_name):
app = Flask(__name__)
app.config.from_object(object_name)
babel = Babel(app)
app.register_blueprint(main_blueprint)
app.register_blueprint(category_blueprint)
app.register_blueprint(item_blueprint)
db.init_app(app)
return app
I want to add the following to main.py:
#babel.localeselector
def get_locale():
if 'locale' in session:
return session['locale']
return request.accept_languages.best_match(LANGUAGES.keys())
#application.route('/locale/<locale>/', methods=['GET'])
def set_locale(locale):
session['locale'] = locale
redirect_to = request.args.get('redirect_to', '/')
return redirect(redirect_to) # Change this to previous url
Unfortunately, main.py doesn't have access to the babel variable from the application factory. How should I go about solving this?
Flask extensions are designed to be instantiated without an app instance for exactly this case. Outside the factory, define your extensions. Inside the factory, call init_app to associate the app with the extension.
babel = Babel()
def create_app():
...
babel.init_app(app)
...
Now the babel name is importable at any time, not just after the app has been created.
You already appear to be doing this correctly with the db (Flask-SQLAlchemy) extension.
In the case of your specific babel.localeselector example, it might make more sense to put that next to babel since it's being defined there.

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']
...

Can I keep all Flask blueprints in one file?

I am using an app factory to initialize my app. In it, I import all the blueprints and register them one by one. Is there a way I can move the import and register statements to a different file or inform the factory about them without referencing them all individually?
def create_app(config_filename):
app = Flask(__name__)
app.config.from_object(config_filename)
from app.users.models import db
db.init_app(app)
from app.users.views import users
from app.posts.views import posts
app.register_blueprint(posts, url_prefix='/posts')
app.register_blueprint(users, url_prefix='/users')
return app
In my project I'm actually generating the blueprints with another script, so I'd like to be able to generate the registration too by appending to a file or something, rather than trying to modify code in the factory.
Yes, you can import and register the blueprints in some other module. But there's no practical point to this approach, it just moves the imports and register calls somewhere else.
myapp/blueprints.py:
from app.users.views import users
from app.posts.views import posts
def init_app(app):
app.register_blueprint(users, prefix='/users')
app.register_blueprint(posts, prefix='/posts')
myapp/__init__.py:
def create_app():
app = Flask(__name__)
# ...
from myapp import blueprints
blueprints.init_app(app)
# ...
Something more useful might be to tell the app what packages to import from and have the app expect to find a blueprint in some standard location for each package. Assuming the blueprint variable will always have the same name as the package, is defined in views, and has the same prefix as the name:
from werkzeug.utils import import_string
def create_app():
app = Flask(__name__)
# ...
for name in ('users', 'posts'):
bp = import_string('myapp.{0}.views:{1}'.format(name, name))
app.register_blueprint(bp, prefix='/{0}'.format(name))
# ...

Categories

Resources