Closed nightkr closed 3 years ago
I agree, this is something I'd like to see as well. I think the best use case would be a modular admin panel, where it is kept in a blueprint and plugins create sub-blueprints that are added to that blueprint.
I've been using the following for now for a thing I've been working on. While there are no guarantees etc I haven't encountered any major issues with it so far.
class NestableBlueprint(Blueprint):
"""
Hacking in support for nesting blueprints, until hopefully https://github.com/mitsuhiko/flask/issues/593 will be resolved
"""
def register_blueprint(self, blueprint, **options):
def deferred(state):
url_prefix = (state.url_prefix or u"") + (options.get('url_prefix', blueprint.url_prefix) or u"")
if 'url_prefix' in options:
del options['url_prefix']
state.app.register_blueprint(blueprint, url_prefix=url_prefix, **options)
self.record(deferred)
Not sure what the semantics are for this. If someone wants this, please come up with a design for it :)
I think that dontcare4free's NestableBlueprint provides a good rough design. All that would really be needed is the ability to register blueprints onto an already existing blueprint. I don't think this would really change any code in Blueprint.register() either since, from looking at that and BlueprintSetupState, the same function names are shared between Blueprint and Flask. Looks like it'd be mostly a duplication of register_blueprint from Flask to Blueprint.
Here is how I'd envision Blueprint.register_blueprint would work:
Each blueprint would have its own self.blueprints dict. When register_blueprint is called, it would check whether there is a blueprint with that name already in its internal dict. The logic on what to depending on that result do is duplicated between Flask.register_blueprint and Blueprint.register_blueprint. However, the ID of the child blueprint would have the id of the parent blueprint appended to it, with a '.' between the two. For example, if the parent blueprint was named 'config' and the child blueprint was named 'users', then ultimately its ID would be 'config.users'. Any paths in the child would be prefixed by the parent blueprint's url_prefix as well as the child's.
This is equally as rough as the dontcare4free's design above, and I haven't looked thoroughly through the code to see everywhere that this could affect, but it is possible to do.
I'm also hoping for this. Right now, I'm implementing a RESTful API alongside a traditional web application and am ending up writing a lot of non-DRY code because I can't nest blueprints, particularly wrt API error handling (i.e., I have to override Flask's build-in error handling for each API blueprint in the same way). In fact, I'm thinking of trying out dontcare4free's NestableBlueprints subclass to see if it would help solve my problem. I'd love it if this were built in.
I could use something like this as well.
Not sure about the design, though – will test-drive it in my application.
How strange - flask.Flask
and flask.Blueprint
are getting more and more similar.
Maybe they should inherit from a common class?
@gioi I already suggested something along these lines once in IRC and got some arguments against it (by @mitsuhiko ? i don't remember) but i can't recall the points that were made against it. I also suck at grepping the logs.
Personally, I like the idea of inheriting from a common class. I like to build my blueprints as fairly isolated from their siblings. It would be kind of nice if a blueprint was just a flask app itself. Perhaps there could be a distinction between blueprints that have to be bolted onto a parent, and blueprints/apps that can be run standalone. There is a lot of overlap between the two. I think bringing them closer together could really allow for some powerful uses.
To elaborate on my personal use case:
A specific application contains about two dozen blueprints. Most are coupled with a single entity (one pair for users, news posts, etc.) and contain the usual view methods (create, update, delete, etc.).
However, some blueprints and entities belong together (news posts and images, gallery albums and images, etc.), and that's why I would like to group them together, but preferably without sacrificing the namespace-free view methods (as opposed to switching to create_post
, create_image
, etc. and combining them in the same blueprint Python module).
So what I'm actually looking for is a way to group blueprints; but there is no need to (infinitely) nest them.
@homeworkprod AFAIU, nestable blueprints would be a solution to your problem, right?
@mitsuhiko Is this going anywhere? This seems like a great idea.
@gioi: Well, they would be a solution, but I'm worried that this would go too far.
The initial post speaks of "sub-blueprints", and I think one additional layer would suffice (versus "nested", which, to me, sounds like multiple layers, and people could really go wild with that).
But then again I might see a problem here that doesn't exist.
Wouldn't a proper implementation of "sub-blueprints" pretty much automatically support infinite nesting of blueprints, too, anyway?
Some questions to examine the current proposal:
admin.news.posts.tags.update
?.view
shortcuts, how would one relatively reference a sub-blueprint in an url_for
call in a (first-level) blueprint's template?..view
in a sub-blueprint point to the parent's view
function?@ThiefMaster: Well, I'd say it could get more complex, as it is usually the case with generalized implementations. But basically, yes, likely.
@homeworkprod
- Would this mean we'd have endpoints like
admin.news.posts.tags.update
?
The implementation in the OP doesn't handle this, but I think that makes the most sense.
@teozkr #750 is/was a pull request, while this is an issue without commits attached
@Turbo87 true, but @kennethreitz pretty much declined it with a back-to-the-drawing-board message.
This would be extremely useful when designing applications, especially with a scenario like this (ala shopify):
Where the admin panel blueprint would be nest-able within the company page.
I thought long and hard about this and whilst I absolutely agree that this would be very very nice to have it would require a rewrite of significant parts of flask (the way blueprints currently work under the hood is not very conducive of this) and I'm not sure it could be done in a completely backwards-compatible way.
@danielchatfield: I don't see why it couldn't be backwards-compatible — as Blueprint.register_blueprint
is a new method, the old API would be unchanged and things could shift around under the hood as much as they needed to.
Currently, it seems that the best way to implement @employ's Shopify example with /admin consisting of multiple blueprints would be to make admin
a normal Python module, implement the blueprints by importing from that module, and make the Flask app register the blueprints directly. My issue with this is that you'd have to re-enter the same "/admin"
url prefix for all of its blueprints, which isn't DRY.
This issue has now been open for almost two years. In that time frame there has not been a single serious attempt at creating a properly designed solution, which would include documentation and considering the impact a discussion on the implications of this change and potential problems.
Worse the discussion that has occured in this issue shows that fundamentally blueprints are probably a bad idea and better solutions for composing views might need to be found.
So for all those who intend to add another +1 comment, take this under consideration and post something useful, if you want to see something happen or I'm just going to unsubscribe because quite frankly I don't care at all about how many +1s this gets apart from that it's filling my inbox. A sentiment I which I'm probably not alone with.
A way to replace blueprints should probably start with finding out (or summarizing) what is actually wrong with blueprints. They don't seem like a very elegant solution to me, but i am not sure what problems should be solved by a replacement.
I suggest deleting all :+1: comments for the sake of readability, and also to lock this issue and create a new one about a possible replacement or the future of blueprints.
Say I wanna setup a admin page under /admin
, and have a view for test initially, then I added a monitor view. As the test views accumulate, someday I decided to put all of them under admin/test
, for unity, this operation is also done to monitor, so the desired file structure is like:
/app
/admin
/monitor
__init__.py
monitor.py
/test
__init__.py
test.py
__init__.py
admin.py
What I expect to do now is:
from .test import test_bp
from .monitor import monitor_bp
admin_bp.register_blueprint(test_bp, url_preix='/test')
admin_bp.register_blueprint(monitor_bp, url_preix='/monitor')
rather than:
import test
import monitor
@admin_bp.add_url_rule('/test/test1', 'test1_view', test.test1_view)
@admin_bp.add_url_rule('/test/test2', 'test2_view', test.test2_view)
@admin_bp.add_url_rule('/monitor/page1', 'page1_view', monitor.page1_view)
@admin_bp.add_url_rule('/monitor/page2', 'page2_view', monitor.page2_view)
Just something like urlpattern
's include
in Django. As the app grows, it would definitely be a complicated sub-site under some endpoint, what one want to do of course, is to reduce the dependency.
A modification which returns the blueprint
def add_blueprint(self, name, import_name, **kwargs):
url_subprefix = kwargs.pop('url_subprefix', None)
if url_subprefix:
kwargs.setdefault('url_prefix', self.url_prefix + url_subprefix)
bp = self.__class__(self.name + '.' + name, import_name, **kwargs)
def later(state):
state.app.register_blueprint(bp)
self.record(later)
return bp
An alternative to sub-blueprints, is to break a one-file Blueprint into multiple py files, to import the secondary files in the main Blueprint file, and to then loop through the imported routes and call bp.add_url_rule()
on them.
I created a Gist with the code for doing this:
https://gist.github.com/Jaza/61f879f577bc9d06029e
This doesn't provide sub-prefixing for the secondary routes, the way that nestable blueprints would (although in my use case I didn't want them anyway). Although, one could do auto-sub-prefixing in the for r in routes
loop, with some simple concatenation. It's mainly just for splitting up a Blueprint that's grown too big to have all the routes in one py file.
While the approach I ended up using is not as automatic as inheriting all properties of a parent blueprint except for the url_prefix, I was able to register and have nested blueprints "modules" physically located inside other blueprint "modules" directories as well by using a combination of default_config.py dictionary structure, a startup file that initialized from default_config.py the starting app module, which then auto initializes a create_app method that calls a common class file /common/classes.py makeBluePrint() factory method. Basically OnStart Read Config, then Take the "blueprints" array names and pass them all through the makeBluePrint() class which further checks config at runtime for any routes that need to be registered and registers them.
I think it would be pretty simple for me to adjust the below data structure and the bluePrintMaker() blueprint app factory method to achieve what you are asking for.
Notice that in my data dictionary in default_config.py where it lists the blueprint app names, public, admin, and admin.utilities.
Basically below in the default_config.py dictionary. "admin.utilities" is a folder/app inside of admin called "utilities".
I will adapt my data structure to support nested data elements inside of each blueprint in default_config.py with a couple switches like "inherit" or "override" with child elements / objects for template info.
In my bluePrintMaker() blueprint factory method it is able to find and register using the dot syntax and is currently how I have the module path being set by using the first data object name(s) inside of the dictionary object "blueprints"
List of files and methods: 1) a root default_config.py file holding "APP_INFO" dictionary with specific, keys, attributes and properties.
2) a /common directory in root with classes.py file containing a bluePrintMaker() class and method(s)
3) a root file in "runserver.py" that onload invokes create_app() with a nested call to classes.py 's bluePrintMaker() factory method which also creates routes and api endpoints from default_config.py dictionary data.
This structure and approach worked for me after many reviews of other approaches and code samples.
"""default_config.py"""
APP_INFO = {
"app": {
"properties": {
"name": "myApp",
"start": "public"
},
"blueprints": {
"public": {
"properties": {
"info": {
"name": "public",
"path": "/public/"
}
},
"views": {
"pageCreator": {
"urls": {
"/<url>": "",
"/": "{'url':'/'}"
}
}
},
"templates": {
"properties": {
"template_folder": "templates",
"static_folder": "static",
"static_url_path": "/public"
},
"layout": {
"directory": "/layouts/",
"extension": ".html"
},
"content": {
"directory": "/content/",
"extension": ".html"
},
"data": {
"directory": "/templates/data/",
"extension": ".txt"
},
"loader": {
"directory": "/content/",
"extension": ".html"
}
}
},
"admin": {
"properties": {
"info": {
"name": "admin",
"path": "/admin/"
}
},
"views": {
"checkAuth": {
"urls": {
"/admin": ""
}
}
},
"templates": {
"properties": {
"template_folder": "admin_templates",
"static_folder": "admin_static",
"static_url_path": "/admin"
}
}
},
"admin.utilities": {
"properties": {
"info": {
"name": "utilities",
"path": "/admin/utilities/"
}
},
"views": {
"launchUtility": {
"urls": {
"/admin/utilities/<utility>": "",
"/admin/utilities": "{'utility':'/'}"
}
}
},
"templates": {
"properties": {
"template_folder": "utilities_templates",
"static_folder": "utilities_static",
"static_url_path": "/admin/utilities/"
}
}
}
}
}
}
No it's not a "required feature", and for languages codes you certainly do not need this. Check e.g. http://flask.pocoo.org/docs/0.12/patterns/urlprocessors/
@ThiefMaster Yes, MAYBE! But tell me which one you prefer?
app.register_blueprint(blog_bp, url_prefix='/<lang>/blog')
app.register_blueprint(admin_bp, url_prefix='/<lang>/admin')
app.register_blueprint(portfolio_bp, url_prefix='/<lang>/portfolio')
# we should have `lang` or any other prefix in all over blueprints
or
main_bp = Blueprint('main_bp', url_prefix='/<lang>')
main_bp.register_blueprint(blog_bp, url_prefix='/blog')
main_bp.register_blueprint(admin_bp, url_prefix='/admin')
main_bp.register_blueprint(portfolio_bp, url_prefix='/portfolio')
# now we just need to add it on our main blueprint
Hi everyone, I appreciate that you're trying to help, but please stop posting variations of code that adds a prefix to all routes. The goal of nestable blueprints in Flask is to be fully nestable, including request.blueprint
, url_for
, before_request
, and all other parts of the request lifecycle. This is not impossible, but it's much more than just a prefix.
If this feature is important to you, please consider #3215 as a good place to start contributing.
Hi everyone, I appreciate that you're trying to help, but please stop posting variations of code that adds a prefix to all routes. The goal of nestable blueprints in Flask is to be fully nestable, including
request.blueprint
,url_for
,before_request
, and all other parts of the request lifecycle. This is not impossible, but it's much more than just a prefix.If this feature is important to you, please consider #3215 as a good place to start contributing.
@davidism does this PR https://github.com/pallets/flask/pull/3709 close the issue ?
No, AFAICT this is just refactoring to reduce similar/duplicate code between Flask and Blueprint.
I'd like to be able to register "sub-blueprints" using
Blueprint.register_blueprint(*args, **kwargs)
. This would register the nested blueprints with an app when the "parent" is registered with it. All parameters are preserved, other thanurl_prefix
, which is handled similarly to inadd_url_rule
. A naíve implementation could look like this: