librelist archives

« back to archive

Let me bounce an idea off of you....

Let me bounce an idea off of you....

From:
Jonas Galvez
Date:
2010-11-10 @ 14:16
My dear fellow Flaskers, I'm that guy with an unhealthy obsession with the
with statement...

So I've been working on a small Flask extension (well, two) that will let
you write the Arc Challenge[1] like the following:

from flask import Flask, request, session, redirect, g
from flaskext.html5witch import html
from flaskext.httpwitch import httpwitch

with httpwitch:
   app = Flask(__name__)

with app.route('/said'):
  def get():
    if session.get('foo'):
      return 'You said %s.' % session['foo']
    else:
      with html.form:
        html.input(None, name='foo')
        html.input(None, type='submit')
      return html
  def post():
    if request.form.get('foo'):
      session['foo'] = request.form['foo']
      return html.a('Click here.', href='/said')
    else:
      return redirect('/said')

Does that look good to you? Am I wasting my time? It's tricky setting the
handlers that way, and I still need to do a lot of tests, but so far the
basic idea works. I'm planning on pushing it all out to Github over the
weekend...

-- Jonas

[1] http://www.paulgraham.com/arcchallenge.html

Re: [flask] Let me bounce an idea off of you....

From:
Zahari Petkov
Date:
2010-11-10 @ 14:57
Hi Jonas,

On Wed, Nov 10, 2010 at 4:16 PM, Jonas Galvez <jonasgalvez@gmail.com> wrote:
> My dear fellow Flaskers, I'm that guy with an unhealthy obsession with the
> with statement...

:)

> So I've been working on a small Flask extension (well, two) that will let
> you write the Arc Challenge[1] like the following:
> from flask import Flask, request, session, redirect, g
> from flaskext.html5witch import html
> from flaskext.httpwitch import httpwitch
> with httpwitch:
>    app = Flask(__name__)
> with app.route('/said'):
>   def get():
>     if session.get('foo'):
>       return 'You said %s.' % session['foo']
>     else:
>       with html.form:
>         html.input(None, name='foo')
>         html.input(None, type='submit')
>       return html
>   def post():
>     if request.form.get('foo'):
>       session['foo'] = request.form['foo']
>       return html.a('Click here.', href='/said')
>     else:
>       return redirect('/said')
> Does that look good to you? Am I wasting my time? It's tricky setting the
> handlers that way, and I still need to do a lot of tests, but so far the
> basic idea works. I'm planning on pushing it all out to Github over the
> weekend...

I like that you are eager to explore new frontiers with the with
statement and runpy. I see that going in a direction as a fancy
micro-framework on top of Flask if prove to be efficient, or at least
a showcase of Python new features and robustness. It will be
interesting to watch that evolving, especially where more complex
things are involved. I am not sure how that complexity can be managed
this way, since this is something completely different to what we are
accustomed -- whether it will be just nested `with`s with some magic
for managing modularization, or something else, but that is the fun
part for you to explore :)

I would just suggest that you provide a couple of nice and easy to
understand examples as you go further.

Looking forward to see more from you,
Zahari

Re: [flask] Let me bounce an idea off of you....

From:
Jonas Galvez
Date:
2010-11-10 @ 15:22
Zahari Petkov wrote:
> I like that you are eager to explore new frontiers with the with
> statement and runpy. I see that going in a direction as a fancy
> micro-framework on top of Flask if prove to be efficient, or at least
> a showcase of Python new features and robustness. It will be
> interesting to watch that evolving, especially where more complex
> things are involved. I am not sure how that complexity can be managed
> this way, since this is something completely different to what we are
> accustomed -- whether it will be just nested `with`s with some magic
> for managing modularization, or something else, but that is the fun
> part for you to explore :)

That's awesome to hear, it gets lonely sometimes in the "let's try and
do things differently" land :)

I originally intended to adapt this directly into a Flask fork, but
I've noticed Armin is kind of conservative[1] about idiom
introductions so I thought I'd make it an extension first. It might
end up a cleaner implementation this way too. Of course it's slowly
becoming kind of my dream to see this built into Flask... ;)

The implementation, like I said, is kind of hacky, but the end result
are the same handlers defined at boot time, so it will definitely not
cause any impact on performance. It's just a fancy way of setting
handlers and loading them from files.

-- Jonas

[1] https://github.com/mitsuhiko/flask/pull/122

Re: [flask] Let me bounce an idea off of you....

From:
danjac354@gmail.com
Date:
2010-11-10 @ 14:26
I kind of like the pattern, but would it be easier to have in a class ?

e.g.

@app.route("/said/")
class Said(Handler):
    def GET():
    ....
    def POST():
    ....

of course that would necessitate class decorators, so a pre-2.6
pattern might use url_map directly (for example).

The Handler class would have a __call__ method to switch to GET/POST etc.

On 10 November 2010 14:16, Jonas Galvez <jonasgalvez@gmail.com> wrote:
> My dear fellow Flaskers, I'm that guy with an unhealthy obsession with the
> with statement...
> So I've been working on a small Flask extension (well, two) that will let
> you write the Arc Challenge[1] like the following:
> from flask import Flask, request, session, redirect, g
> from flaskext.html5witch import html
> from flaskext.httpwitch import httpwitch
> with httpwitch:
>    app = Flask(__name__)
> with app.route('/said'):
>   def get():
>     if session.get('foo'):
>       return 'You said %s.' % session['foo']
>     else:
>       with html.form:
>         html.input(None, name='foo')
>         html.input(None, type='submit')
>       return html
>   def post():
>     if request.form.get('foo'):
>       session['foo'] = request.form['foo']
>       return html.a('Click here.', href='/said')
>     else:
>       return redirect('/said')
> Does that look good to you? Am I wasting my time? It's tricky setting the
> handlers that way, and I still need to do a lot of tests, but so far the
> basic idea works. I'm planning on pushing it all out to Github over the
> weekend...
> -- Jonas
> [1] http://www.paulgraham.com/arcchallenge.html

Re: [flask] Let me bounce an idea off of you....

From:
Jonas Galvez
Date:
2010-11-10 @ 14:34
danjac354@gmail.com wrote:
> I kind of like the pattern, but would it be easier to have in a class ?
>
> e.g.
>
> @app.route("/said/")
> class Said(Handler):
>    def GET():
>    ....
>    def POST():
>    ....

I think that should definitely be possible to do, but not mandatory.
The main motivation is to avoid the need to /name/ things (functions,
classes) when I just need to run a block of code (like you can with
Sinatra in Ruby).

Also, I'm thinking of adding a module function to httpwitch that will
let you load all the .py files in a single directory and pre-embed
"app", "request", "session" in the globals dictionary. This feature
would be dependent on Python 2.7's runpy.run_path() function, but it
would eliminate the need to add a lot of boilerplate code in isolated
files defining handlers.

-- Jonas

Re: [flask] Let me bounce an idea off of you....

From:
Dag Odenhall
Date:
2010-11-10 @ 15:18
On Wed, 2010-11-10 at 12:34 -0200, Jonas Galvez wrote:
> I think that should definitely be possible to do, but not mandatory.
> The main motivation is to avoid the need to /name/ things (functions,
> classes) when I just need to run a block of code (like you can with
> Sinatra in Ruby).

Where are you naming your endpoints?

Re: [flask] Let me bounce an idea off of you....

From:
Jonas Galvez
Date:
2010-11-10 @ 15:23
Dag Odenhall wrote:
> Where are you naming your endpoints?

What you mean? It still uses (a slightly modified) app.route() to set
the routes...

-- Jonas

Re: [flask] Let me bounce an idea off of you....

From:
Dag Odenhall
Date:
2010-11-10 @ 16:02
On Wed, 2010-11-10 at 13:23 -0200, Jonas Galvez wrote:
> Dag Odenhall wrote:
> > Where are you naming your endpoints?
> 
> What you mean? It still uses (a slightly modified) app.route() to set
> the routes...
> 
> -- Jonas

Werkzeug is built around the concept of "endpoints" which in Flask are
the viewfunc.__name__s. It's what you pass to url_for() for example.

Re: [flask] Let me bounce an idea off of you....

From:
Jonas Galvez
Date:
2010-11-10 @ 16:12
Dag Odenhall wrote:
> Werkzeug is built around the concept of "endpoints" which in Flask are
> the viewfunc.__name__s. It's what you pass to url_for() for example.

Ah, right. I hadn't thought about that but think I can address that
with the following:

with app.route('/users') as users:
  def get():
    pass
  def post():
    pass

And it would automatically generate endpoints like get_users, post_users.

-- Jonas

Re: [flask] Let me bounce an idea off of you....

From:
Jonas Galvez
Date:
2010-11-10 @ 16:14
> with app.route('/users') as users:
>  def get():
>    pass
>  def post():
>    pass
>
> And it would automatically generate endpoints like get_users, post_users.

With the current test code, the endpoints ended up being simply "get"
and "post", which is a bug. I'll change that before pushing to GitHub.

-- Jonas

Re: [flask] Let me bounce an idea off of you....

From:
Dag Odenhall
Date:
2010-11-10 @ 16:32
On Wed, 2010-11-10 at 14:14 -0200, Jonas Galvez wrote:
> > with app.route('/users') as users:
> >  def get():
> >    pass
> >  def post():
> >    pass
> >
> > And it would automatically generate endpoints like get_users, post_users.
> 
> With the current test code, the endpoints ended up being simply "get"
> and "post", which is a bug. I'll change that before pushing to GitHub.
> 
> -- Jonas

Does Python really let you know the name of the variable a context
manager yields to, without AST parsing?

FWIW this kind of APIs is what made me stop doing Ruby. I don't mind it
as an extension but big -1 from me for it going in the core. It's
difficult to reason about, it makes view functions very special instead
of nothing special. I don't see any particular gain except the
subjective prettiness of a DSL (ignoring all the issues with DSLs). How
does it even work, are you hacking into the frame to get the functions
or what?

Re: [flask] Let me bounce an idea off of you....

From:
Jonas Galvez
Date:
2010-11-10 @ 17:39
Dag Odenhall wrote:
> FWIW this kind of APIs is what made me stop doing Ruby.

BTW, I too am a Pythonista who wandered into the Ruby land for some
time and have stopped using it ever since. There are many things that
I like about Ruby, but in order of importance, my reasons for leaving:
1) poor performance (even in 1.9) when compared to Python, 2) the
community (effing hipsters, man) and 3) no indentation-based blocks (I
hate all these 'end's).

I also share your concern about using nasty hacks, but in this case
particularly, I will keep my argument that the way the frame object is
being used in httpwitch is harmless.

Integration with the Flask core would involve simply turning
app.route() into a class and adapting the __enter__() and __exit__()
methods from httpwitch.route_context. It's not that complicated :)

-- Jonas

Re: [flask] Let me bounce an idea off of you....

From:
Jonas Galvez
Date:
2010-11-10 @ 17:15
Dag Odenhall wrote:
> Does Python really let you know the name of the variable a context
> manager yields to, without AST parsing?
>
> FWIW this kind of APIs is what made me stop doing Ruby. I don't mind it
> as an extension but big -1 from me for it going in the core. It's
> difficult to reason about, it makes view functions very special instead
> of nothing special. I don't see any particular gain except the
> subjective prettiness of a DSL (ignoring all the issues with DSLs). How
> does it even work, are you hacking into the frame to get the functions
> or what?

I /am/ hacking into the frame to get the function, but that's
harmless, because it only happens once at boot time. Once it gets the
function, then it simply uses add_url_rule() and that's done.

It makes 'get', 'post', 'put', 'delete' and 'options' reserverd words tho.

Here's the initial implementation, it's only 24 lines of code:

https://github.com/galvez/flask-httpwitch/blob/master/httpwitch.py

The endpoint issue is fixed, btw. I can just compare globals() before
and after using the context manage to get the variable defined in the
"as" clause.

-- Jonas

Re: [flask] Let me bounce an idea off of you....

From:
Dag Odenhall
Date:
2010-11-10 @ 17:53
On Wed, 2010-11-10 at 15:15 -0200, Jonas Galvez wrote:
> I /am/ hacking into the frame to get the function, but that's
> harmless, because it only happens once at boot time. Once it gets the
> function, then it simply uses add_url_rule() and that's done.
> 
> It makes 'get', 'post', 'put', 'delete' and 'options' reserverd words
> tho.

getframe isn't portable to other implementations.

> 
> Here's the initial implementation, it's only 24 lines of code:
> 
> https://github.com/galvez/flask-httpwitch/blob/master/httpwitch.py
> 
> The endpoint issue is fixed, btw. I can just compare globals() before
> and after using the context manage to get the variable defined in the
> "as" clause. 

Which can break if something uses the global statement.

Re: [flask] Let me bounce an idea off of you....

From:
Jonas Galvez
Date:
2010-11-10 @ 17:27
Here's how to use it as-is:

from httpwitch import httpwitch
from flask import Flask

app = Flask(__name__)
witchyapp = httpwitch(app)

with witchyapp.route('/omg') as omg:
  def get():
    return 'OMG IT WORKS!'

app.run()

-- Jonas

Re: [flask] Let me bounce an idea off of you....

From:
Dag Odenhall
Date:
2010-11-10 @ 18:09
On Wed, 2010-11-10 at 15:27 -0200, Jonas Galvez wrote:
> Here's how to use it as-is:
> 
> from httpwitch import httpwitch
> from flask import Flask
> 
> app = Flask(__name__)
> witchyapp = httpwitch(app)
> 
> with witchyapp.route('/omg') as omg:
>   def get():
>     return 'OMG IT WORKS!'
> 
> app.run()
> 
> -- Jonas

I suppose one thing I dislike about it is the procedural feel of it
compared to Flask's declarative feel. Not the most constructive
critique, I'll admit.

Using with like this feels like a hack, though I've had no objections to
the way Logbook uses it and that's somewhat similar. Your exact solution
though is quite hacky, maybe better:

with app.route('omg', '/omg') as method:
    if method.get:
        return 'OMG IT WORKS!'

Other ideas:

app.route(omg='/omg')
# Though route() takes some kwargs that'd need to be accessible
# from somewhere else to avoid name collisions

app.route.omg('/omg')
# Too metamagic/Rubyesque for me though


The above use of if method.get though probably don't work well or at all
without hacks because we can't defer the execution. Maybe something
like,

@app.routes
def routes():
    with ...

But then we might as well just do if's to check if a route matches a
rule ourself, imperatively. This is also too different from Flask or
Werkzeug that it should probably, if at all, be a separate framework.

Re: [flask] Let me bounce an idea off of you....

From:
Jonas Galvez
Date:
2010-11-10 @ 18:53
Dag Odenhall wrote:
> I suppose one thing I dislike about it is the procedural feel of it
> compared to Flask's declarative feel. Not the most constructive
> critique, I'll admit.
>
> Using with like this feels like a hack, though I've had no objections to
> the way Logbook uses it and that's somewhat similar. Your exact solution
> though is quite hacky [...]

I have the same feeling, trust me.

I can see you like the syntax, you just don't like the fact that it a)
uses currentframe() and b) imposes a procedural style by restricting
the definition of variables in the global space during the handler
definition process.

The latter is not too much of a practical concern to me, since I'll
hardly ever need to define a variable within the "with" context when
setting the handlers.

But the biggest problem is the currentframe() dependency. What I
hadn't realized tho, before this little chat, is that the dictionary
globals() returns is a persistent reference to that execution
context's globals, not just a snapshot. So I managed to remove the
need for currentframe() altogether by simply taking globals() in the
httpwitch constructor. Here's the commit:


https://github.com/galvez/flask-httpwitch/commit/0a2f73c7a2f2826f8960e41acd31f27020ed7f84

...
app = Flask(__name__)
witchyapp = httpwitch(app, globals())
...

How about that? :)

-- Jonas

Re: [flask] Let me bounce an idea off of you....

From:
Dag Odenhall
Date:
2010-11-10 @ 19:21
On Wed, 2010-11-10 at 16:53 -0200, Jonas Galvez wrote:
> Dag Odenhall wrote:
> > I suppose one thing I dislike about it is the procedural feel of it
> > compared to Flask's declarative feel. Not the most constructive
> > critique, I'll admit.
> >
> > Using with like this feels like a hack, though I've had no objections to
> > the way Logbook uses it and that's somewhat similar. Your exact solution
> > though is quite hacky [...]
> 
> I have the same feeling, trust me.
> 
> I can see you like the syntax, you just don't like the fact that it a)
> uses currentframe() and b) imposes a procedural style by restricting
> the definition of variables in the global space during the handler
> definition process.
> 
> The latter is not too much of a practical concern to me, since I'll
> hardly ever need to define a variable within the "with" context when
> setting the handlers.
> 
> But the biggest problem is the currentframe() dependency. What I
> hadn't realized tho, before this little chat, is that the dictionary
> globals() returns is a persistent reference to that execution
> context's globals, not just a snapshot. So I managed to remove the
> need for currentframe() altogether by simply taking globals() in the
> httpwitch constructor. Here's the commit:
> 
> 
https://github.com/galvez/flask-httpwitch/commit/0a2f73c7a2f2826f8960e41acd31f27020ed7f84
> 
> ...
> app = Flask(__name__)
> witchyapp = httpwitch(app, globals())
> ...
> 
> How about that? :)
> 
> -- Jonas

Doesn't that break as soon as you add globals after witchyapp, don't you
need to pass globals() to every app.route() rather?

Re: [flask] Let me bounce an idea off of you....

From:
Jonas Galvez
Date:
2010-11-10 @ 19:30
Dag Odenhall wrote:
> Doesn't that break as soon as you add globals after witchyapp, don't you
> need to pass globals() to every app.route() rather?

No, that's what I was trying to say. The reference returned by
globals() is persistent. Awesome, isn't it? This makes me feel a lot
less guilty from using this pattern.

-- Jonas

Re: [flask] Let me bounce an idea off of you....

From:
Dag Odenhall
Date:
2010-11-10 @ 20:16
On Wed, 2010-11-10 at 17:30 -0200, Jonas Galvez wrote:
> Dag Odenhall wrote:
> > Doesn't that break as soon as you add globals after witchyapp, don't you
> > need to pass globals() to every app.route() rather?
> 
> No, that's what I was trying to say. The reference returned by
> globals() is persistent. Awesome, isn't it? This makes me feel a lot
> less guilty from using this pattern.
> 
> -- Jonas

Other than the utter hackiness of it eh? ;)

I wonder if globals() is guaranteed to be the same reference in other
implementations.

Re: [flask] Let me bounce an idea off of you....

From:
Jonas Galvez
Date:
2010-11-11 @ 06:09
Dag Odenhall wrote:
> Other than the utter hackiness of it eh? ;)
>
> I wonder if globals() is guaranteed to be the same
> reference in other implementations.

Oh snap. Just tried it with PyPy, globals() is not persistent in it.

-- Jonas