librelist archives

« back to archive

Blueprint usage advice

Blueprint usage advice

From:
Desmond Rivet
Date:
2012-10-21 @ 20:07
Hi all,

I have an application.  It handles URLs that look (more or less) like this:

path1/path2/action1
path1/path2/action2
path1/action1
...

The idea is that the "path" part of the URL (path1, path1/path2)
provides a context for the "action" part of the URL.

So, how do blueprints apply for this application?  I can think of two
ways I could make use of them:

1.
I could have an app-wide blueprint with a "path" url prefix, like this:

@myblueprint.route(’/’)
app.register_blueprint(myblueprint, url_prefix='/<path:category>')

My (limited) understanding of blueprints indicates that this would
relieve me of having to handle the <path:category> for every action I
wanted to implement.

2.
I could have a blueprint for every action:

@action1_blueprint.route(’/<path:category>/action1’)
@action2_blueprint.route(’/<path:category>/action2’)

In that case I'd have to handle the category context explicitly for
each action blueprint, right?

How would you guys lean?  Is option 1 even a possibility?  The
examples in the docs show things like /api or /admin for the blueprint
url_prefix.  Can the prefix be dynamic, like /<path:category>?  If so,
how can I get access to the prefix that matched?

Option 2 seems more in line with  conventional blueprint usage; you
have a division in your application (in this case, into actions) which
the blueprints can implement.  In that case, I'd be interested in
factoring out the "path context" handling, though I'm not sure how.

Any thought are appreciated,

Thanks,

Desmond

Re: [flask] Blueprint usage advice

From:
Steven Kryskalla
Date:
2012-10-21 @ 22:57
On Sun, Oct 21, 2012 at 1:07 PM, Desmond Rivet <desmond.rivet@gmail.com> wrote:
> How would you guys lean?  Is option 1 even a possibility?  The
> examples in the docs show things like /api or /admin for the blueprint
> url_prefix.  Can the prefix be dynamic, like /<path:category>?  If so,
> how can I get access to the prefix that matched?

Can you give some examples of what "path1", "path1/path2", "action1",
and "action2" would be? Some examples would help shed some light on
what you're trying to do.

If you have a bunch of common actions "action1", "action2", etc. that
can apply to many different objects then maybe you'd be better off
with class based views:

http://flask.pocoo.org/docs/views/

For your suggestion #1, yes, the url_prefix can be dynamic. It gets
passed into the view function as an argument to the function, the same
way it normally is:

https://gist.github.com/3928813

But it really depends on what "category" is and what logic you'd put
into the view function to handle different categories. I don't see
much difference between doing it that way and just making a blueprint
with routes like "/<path:category>/action1" and mounting the blueprint
at "/".

I would just forget about flask and blueprints for now and think about
the most logical way you want your code & objects to be structured
(OO-wise, if you like OO), flask should be flexible enough to match.

-Steve

Re: [flask] Blueprint usage advice

From:
Desmond Rivet
Date:
2012-10-22 @ 01:02
On Sun, Oct 21, 2012 at 6:57 PM, Steven Kryskalla <skryskalla@gmail.com> wrote:
> On Sun, Oct 21, 2012 at 1:07 PM, Desmond Rivet <desmond.rivet@gmail.com> wrote:
>> How would you guys lean?  Is option 1 even a possibility?  The
>> examples in the docs show things like /api or /admin for the blueprint
>> url_prefix.  Can the prefix be dynamic, like /<path:category>?  If so,
>> how can I get access to the prefix that matched?
>
> Can you give some examples of what "path1", "path1/path2", "action1",
> and "action2" would be? Some examples would help shed some light on
> what you're trying to do.

Thanks for the reply.

It's a blogging system (I know, I know...there are a lot of these.
It's a learning project :-).  The blog entries map to flat files on
disk, and they can be nested arbitrarily into folders.  The folders
leading up the blog entry becomes the "category".

An example of an "action" would be "tags".  So you can have a URL like this:

/cooking/indian/tags/recipes

And that returns all the entries in the "cooking/indian" category that
have a tag of "recipes".  You can start the tag search from any point
in the category tree.  So these work too:

/cooking/tags/recipes
/tags/recipes

Another example is the "search" action, which does a full text search
through the entries, starting from the category given.

So, with respect to my original posting, I guess I'm asking whether
one should use one blueprint with a url_prefix to capture the
category, or whether one should use several blueprints to modularize
the actions (tag, search, etc.).

> If you have a bunch of common actions "action1", "action2", etc. that
> can apply to many different objects then maybe you'd be better off
> with class based views:
>
> http://flask.pocoo.org/docs/views/
>
> For your suggestion #1, yes, the url_prefix can be dynamic. It gets
> passed into the view function as an argument to the function, the same
> way it normally is:
>
> https://gist.github.com/3928813

Oh cool! Thanks!

> But it really depends on what "category" is and what logic you'd put
> into the view function to handle different categories. I don't see

I don't really thing the logic would be any different based on the
category (although I guess it could be, in theory).  Right now, the
category is just a "root" for the action.

> much difference between doing it that way and just making a blueprint
> with routes like "/<path:category>/action1" and mounting the blueprint
> at "/".

I decided to implement the tagging and search as optional "plugins"
which you can choose not to configure.  If the notion of a "plugin"
maps to the notion of a "blueprint", then I find it a bit easier to
think about.

> I would just forget about flask and blueprints for now and think about
> the most logical way you want your code & objects to be structured
> (OO-wise, if you like OO), flask should be flexible enough to match.

I kind of want both.  I'd like to package things like "tagging" and
"search" into separate modules.  But they both work off of a "category
context", so I'd like to factor that out as well.

Desmond

Re: [flask] Blueprint usage advice

From:
Steven Kryskalla
Date:
2012-10-22 @ 05:04
On Sun, Oct 21, 2012 at 6:02 PM, Desmond Rivet <desmond.rivet@gmail.com> wrote:
> It's a blogging system (I know, I know...there are a lot of these.
> It's a learning project :-).  The blog entries map to flat files on
> disk, and they can be nested arbitrarily into folders.  The folders
> leading up the blog entry becomes the "category".
>
> An example of an "action" would be "tags".  So you can have a URL like this:
>
> /cooking/indian/tags/recipes
>
> And that returns all the entries in the "cooking/indian" category that
> have a tag of "recipes".  You can start the tag search from any point
> in the category tree.  So these work too:
>
> /cooking/tags/recipes
> /tags/recipes
>
> Another example is the "search" action, which does a full text search
> through the entries, starting from the category given.

Ah ok, I thought maybe it was going to refer to different types of
objects, like /users/search and /posts/search.

In that case, you might also consider just using query string params:

/?tags=recipes
/?search=recipes
/cooking?tags=recipes
/cooking?search=recipes
/cooking/indian?tags=recipes
/cooking/indian?search=recipes

That way you can also do ?tags=recipes&search=indian to do a search
and limit to a specific tag.

> So, with respect to my original posting, I guess I'm asking whether
> one should use one blueprint with a url_prefix to capture the
> category, or whether one should use several blueprints to modularize
> the actions (tag, search, etc.).

I think it'd be more natural to organize the functionality into a
CategoryBlueprint which has three view methods:

* index (shows a list of posts in that category)
* tag (same thing as index but restricts posts to a certain tag)
* search (same thing as index but searches the title/body of posts)

The only way I would organize the blueprints the other way around
(IndexBlueprint, TagBlueprint, SearchBlueprint) was if those
blueprints could be applied to multiple objects/resources/models (e.g.
users, posts, comments). And at that point, you might also switch to
class based views instead of blueprints.

> I don't really thing the logic would be any different based on the
> category (although I guess it could be, in theory).  Right now, the
> category is just a "root" for the action.

Yea this was just my thinking about it applying to different types of
objects, thus the need for different logic. It's not needed in your
example.

> I decided to implement the tagging and search as optional "plugins"
> which you can choose not to configure.  If the notion of a "plugin"
> maps to the notion of a "blueprint", then I find it a bit easier to
> think about.
> ...
> I kind of want both.  I'd like to package things like "tagging" and
> "search" into separate modules.  But they both work off of a "category
> context", so I'd like to factor that out as well.

Flask doesn't really prescribe any solutions to get that level of
modularity. You have to rely on python's OO features, which is why I
said to forget about flask & blueprints when coming up with the
design. You could do something like this (remember, not necessarily
any flask involved):

class ListView(object)
this provides a method like "get_items", which must be overridden

class CategoryView(ListView)
this implements "get_items" to return the list of posts in a category

class Modifier(object):
    def __init__(self, listview):
        self.listview = listview

class TagModifier(object)
    def tagged(self, tagname): ...
the "tagged" method calls self.listview.get_items, then filters out
any posts with tags that don't match

class SearchModifier(object)
    def search(self, query): ...
the "search" method calls self.listview.get_items, then filters out
any posts whose title/body don't match the search term

class CustomCategoryView(CategoryView):
    def __init__(self):
        super(CustomCategoryView).__init__()
        self.tagmod = TagModifier(self)
        self.searchmod = SearchModifier(self)
now this view has the default category listing behavior, plus it can
use the self.tagmod.tagged and self.searchmod.searchview methods

This is using "composition over inheritance":
http://stackoverflow.com/questions/49002/prefer-composition-over-inheritance

If you want to use inheritance instead of composition, you could do
CustomCategoryView(TagModifier, SearchModifier, CategoryView),
changing TagModifier and SearchModifier to use super(...,
self).get_items() instead of self.listview.get_items().

With this approach in place, you can then add flask's pluggable
(class-based) views, or use blueprint factories. I have an example of
a blueprint factory kind of thing here:


https://bitbucket.org/lost_theory/flask-stripe-blueprint/src/513aae2dc2be/itemshop/__init__.py?at=default#cl-15

Either way, first you design your objects, then you wire them up to
flask, because flask is very flexible! (just like python)

-steve

Re: [flask] Blueprint usage advice

From:
Steven Kryskalla
Date:
2012-10-22 @ 05:12
On Sun, Oct 21, 2012 at 10:04 PM, Steven Kryskalla <skryskalla@gmail.com> wrote:
> class Modifier(object):
>     def __init__(self, listview):
>         self.listview = listview
>
> class TagModifier(object)
>     def tagged(self, tagname): ...
> the "tagged" method calls self.listview.get_items, then filters out
> any posts with tags that don't match
>
> class SearchModifier(object)
>     def search(self, query): ...
> the "search" method calls self.listview.get_items, then filters out
> any posts whose title/body don't match the search term

Sorry, last minute edit broke my pseudocode :) These should read:

class Modifier(object):
    def __init__(self, listview):
        self.listview = listview
class TagModifier(Modifier):
    def tagged(self, tagname): ...
class SearchModifier(Modifier):
    def search(self, query): ...

And here is some real code demonstrating composition vs. inheritance:

https://gist.github.com/3929765

-steve