Blueprints are supposed to provide a very similar API to the Flask object itself, the difference being that Blueprints defer registration until later. Currently, blueprints can only be registered on apps, they're not nestable, and apps can't be registered with other apps, but this is something that #593 and #1548 want to change.
Blueprint and Flask do derive from helpers._PackageBoundObject, but this is mostly concerned with managing static files, templates, and other resource files. I think identifying the common API between the two and extracting it to a common base class may be a good step towards understanding how we can support app and blueprint nesting. Hopefully, it may at least make the code cleaner and easier to maintain.
Hey! Can I work on this?
Hi, this way is correct?
from flask import Flask, Blueprint
class NewBlueprint(Blueprint):
def register_blueprint(self, blueprint, **options):
def deferred(state):
blueprint.name = f'{state.blueprint.name}.{blueprint.name}'
options['url_prefix'] = f'{state.url_prefix or ""}{options.get("url_prefix", blueprint.url_prefix) or ""}'
state.app.register_blueprint(blueprint, **options)
self.record(deferred)
def register(self, app, options, first_registration=False):
self.record(self._prepare_before_request_funcs)
self.record(self._prepare_after_request_funcs)
super().register(app, options, first_registration=first_registration)
def _prepare_before_request_funcs(self, state):
self._prepare_request_funcs(state, state.app.before_request_funcs)
def _prepare_after_request_funcs(self, state):
self._prepare_request_funcs(state, state.app.after_request_funcs)
def _prepare_request_funcs(self, state, funcs):
if '.' not in state.blueprint.name:
for name in funcs:
if name is not None and name.startswith(state.blueprint.name) and '.' in name:
parent_name = '.'.join(name.split('.')[:-1])
funcs[name] = funcs.get(parent_name, []) + funcs.get(name, [])
print(funcs)
app = Flask(__name__)
@app.before_request
def app_before_request():
print(app_before_request.__name__)
api = NewBlueprint('api', __name__, url_prefix='/api')
@api.before_request
def api_before_request():
print(api_before_request.__name__)
v1 = NewBlueprint('v1', __name__, url_prefix='/v1')
@v1.before_request
def v1_before_request():
print(v1_before_request.__name__)
account = NewBlueprint('account', __name__, url_prefix='/account')
@account.before_request
def account_before_request():
print(account_before_request.__name__)
@account.after_request
def account_after_request(resp):
print(account_after_request.__name__)
return resp
@account.route('/')
def account_index():
return account_index.__name__
project = NewBlueprint('project', __name__, url_prefix='/project')
@project.before_request
def project_before_request():
print(project_before_request.__name__)
@project.route('/')
def project_index():
return project_index.__name__
v1.register_blueprint(account)
v1.register_blueprint(project)
api.register_blueprint(v1)
app.register_blueprint(api)
app.run()
{None: [<function app_before_request at 0x00AEE738>], 'api': [<function api_before_request at 0x038ACDB0>], 'api.v1': [<function api_before_request at 0x038ACDB0>, <function v1_before_request at 0x038ACE88>], 'api.v1.account': [<function api_before_request at 0x038ACDB0>, <function v1_before_request at 0x038ACE88>, <function account_before_request at 0x038ACF60>], 'api.v1.project': [<function api_before_request at 0x038ACDB0>, <function v1_before_request at 0x038ACE88>, <function project_before_request at 0x038B61E0>]}
{'api.v1.account': [<function account_after_request at 0x038ACED0>]}
@Maksych kind of, maybe? I'm having a hard time telling what the code is doing. Stuff like concatenating state.url_prefix with url_prefix definitely isn't right though. And the goal of this issue is to create a common base for Flask and Blueprint, not a new Blueprint subclass.
It looks like you're trying to solve nestable blueprints here, which is not what this issue is about.
@davidism
Thanks for clarification.
Maybe we just don't need blueprints? Maybe just nested applications?
No, we certainly don't need nested applications.
Views can already return WSGI applications, but this issue isn't about nesting applications or getting rid of blueprints.
Hi all,
Can I work on this issue?
Here's an example of what I'm thinking of. Flask and Blueprint both have a before_request decorator. They both take a function and record it. The difference is how they're recorded. The app stores them in a dict of lists, the blueprint records a lambda that runs the first time it's registered.
add_url_rule is again different in both, the app registers the route with the map, the blueprint records a lambda. But route is implemented exactly the same in both, it only calls add_url_rule.
So a theoretical _FlaskCommon (need a better name) would define before_request and route, then Flask and Blueprint would override add_url_rule.
class _FlaskCommon:
def __init__(self, ...):
self._before_request_funcs = {}
def before_request(self, f):
self._before_request_funcs.setdefault(None, []).append(f)
def add_url_rule(self, ...):
raise NotImplementedError
def route(self, ..., **kwargs):
def decorator(f):
self.add_url_rule(view_func=f, **kwargs)
return f
return decorator
class Flask(_FlaskCommon):
def add_url_rule(self, ...):
...
class Blueprint(_FlaskCommon):
def add_url_rule(self, ...):
...
Blueprints are currently not introspectable, because everything is an opaque list of lambda functions that gets called in register. While the record / record_once functions are still useful, the before_request example above records them in a list just like Flask, so Blueprint.register would need to be adjusted. I think this is probably worth it, it makes everything more concrete, but I'd have to see what the full implementation looks like.
@YKo20010 and I will be working on this! 馃憤
Most helpful comment
Here's an example of what I'm thinking of.
FlaskandBlueprintboth have abefore_requestdecorator. They both take a function and record it. The difference is how they're recorded. The app stores them in a dict of lists, the blueprint records a lambda that runs the first time it's registered.add_url_ruleis again different in both, the app registers the route with the map, the blueprint records a lambda. Butrouteis implemented exactly the same in both, it only callsadd_url_rule.So a theoretical
_FlaskCommon(need a better name) would definebefore_requestandroute, thenFlaskandBlueprintwould overrideadd_url_rule.Blueprints are currently not introspectable, because everything is an opaque list of lambda functions that gets called in
register. While therecord/record_oncefunctions are still useful, thebefore_requestexample above records them in a list just like Flask, soBlueprint.registerwould need to be adjusted. I think this is probably worth it, it makes everything more concrete, but I'd have to see what the full implementation looks like.