Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Nestable blueprints #593

Open
teozkr opened this Issue · 27 comments
@teozkr

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 than url_prefix, which is handled similarly to in add_url_rule. A naíve implementation could look like this:

class Blueprint(object):
    ...

    def register_blueprint(self, blueprint, **options):
        def deferred(state):
            url_prefix = options.get('url_prefix')
            if url_prefix is None:
                url_prefix = blueprint.url_prefix
            if 'url_prefix' in options:
                del options['url_prefix']

            state.app.register_blueprint(blueprint, url_prefix, **options)
        self.record(deferred)
@smartboyathome

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.

@teozkr

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)
@mitsuhiko
Owner

Not sure what the semantics are for this. If someone wants this, please come up with a design for it :)

@smartboyathome

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.

@esbullington

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.

@homeworkprod

I could use something like this as well.

Not sure about the design, though – will test-drive it in my application.

@ghost

How strange - flask.Flask and flask.Blueprint are getting more and more similar.
Maybe they should inherit from a common class?

@untitaker
Collaborator

@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.

@nicksloan

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.

@homeworkprod

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.

@ghost

@homeworkprod AFAIU, nestable blueprints would be a solution to your problem, right?

@danielchatfield

@mitsuhiko Is this going anywhere? This seems like a great idea.

@homeworkprod

@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.

@ThiefMaster

Wouldn't a proper implementation of "sub-blueprints" pretty much automatically support infinite nesting of blueprints, too, anyway?

@homeworkprod

Some questions to examine the current proposal:

  • Would this mean we'd have endpoints like admin.news.posts.tags.update?
  • Based on the .view shortcuts, how would one relatively reference a sub-blueprint in an url_for call in a (first-level) blueprint's template?
  • Would ..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.

@teozkr

@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 a duplicate of this?

@Turbo87

@teozkr #750 is/was a pull request, while this is an issue without commits attached

@teozkr

@Turbo87 true, but @kennethreitz pretty much declined it with a back-to-the-drawing-board message.

@employ

This would be extremely useful when designing applications, especially with a scenario like this (ala shopify):

  • company.shopify.com
  • company.shopify.com/admin

Where the admin panel blueprint would be nest-able within the company page.

@danielchatfield

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.

@razzius

@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.

@grotewold grotewold referenced this issue in pocoo/metaflask
Open

Use waffle.io to prioritize issues? #9

@DasIch
Collaborator

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.

@untitaker
Collaborator
@iloahz

Why I need this

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.

@char101

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
@Jaza

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.