I'm thinking about adding a simple decorator to WebOb, to provide a kind of minimalistic framework. I looked at Sergey Schetinin's decorators (in http://svn.pythonpaste.org/Paste/WebOb/contrib) and borrowed a few ideas from that, as well as ideas of my own. Actually, the code isn't really that simple, except in the way I think of WebOb as "simple"... completeness adds size, but it's trying to accomplish something relatively simple.
The idea is basically to prefer the simple call signature of "resp = func(req)", as well as allowing HTTPExceptions. There are some other hooks to fiddle with the internals, though I'm not sure they are entirely useful. Also notable is wsgiwrap.unwrap, which converts a WSGI app into a function that takes a request object and returns a response object. And also a middleware factory in wsgiwrap.middleware, which makes middleware relatively easier to run (I got the idea if a middleware wrapper from Sergey's code, but by unwrapping the application, it makes it easier to write response-modifying middleware). Huh, "unwrap" is a bad name for this.
This isn't really tested, except to make sure it doesn't have syntax errors, as I'm really just trying to think about the API. Does this seem reasonable?
Well, a little context: I've been using no framework for projects lately, but I keep rewriting this decorator. And always with just slightly different features and level of completeness (in a bad way). I want it Right, and it seems time. Also, I noticed Simon Willison playing with such a minimal framework (http://github.com/simonw/djng/tree), which got me in the state of mind. For instance, looking at this code: http://github.com/simonw/djng/blob/72d8a5aa9088720841321f8e4177d7ceb6... here's how you'd do it with my code:
Not really that different, but eh. At least it works, which took a little refactoring to accomplish.
One thing I don't like here is default_500 has that extra e argument, making @wsgiwrap around it kind of useless. But I guess that's okay. One of the things I like with this is that you can have functions with parameters, and while they can't be WSGI apps (for reasonable reasons), they still look and act similar to everything else.
You actually can turn them into WSGI apps if you do @wsgiwrap(add_urlvars=True) and then you can set req.urlvars['e'].
Arguably add_urlvars should be set to true by default, except this means every function needs **kw to avoid TypeError just because req.urlvars is set at some point. There's code (that at least TG uses) that safely calls functions in these cases, but the code is complex so I worry about introducing it (at least by default).
I really like accepting a request and returning a response better than
the official way of creating middleware. A decorator to allow this
paradigm would be very nice. Rack pretty much got this part right
when they re-implemented wsgi 1.0
http://rack.rubyforge.org/doc/SPEC.html
That djng micro framework is neat. The idea of reloadable /
reconfigurable services sounds interesting but I've never needed to
change a conf setting during runtime myself.
On Mon, May 11, 2009 at 4:57 PM, Ian Bicking <i...@colorstudy.com> wrote:
> I'm thinking about adding a simple decorator to WebOb, to provide a
> kind of minimalistic framework. I looked at Sergey Schetinin's
> decorators (in http://svn.pythonpaste.org/Paste/WebOb/contrib) and
> borrowed a few ideas from that, as well as ideas of my own. Actually,
> the code isn't really that simple, except in the way I think of WebOb
> as "simple"... completeness adds size, but it's trying to accomplish
> something relatively simple.
> The idea is basically to prefer the simple call signature of "resp =
> func(req)", as well as allowing HTTPExceptions. There are some other
> hooks to fiddle with the internals, though I'm not sure they are
> entirely useful. Also notable is wsgiwrap.unwrap, which converts a
> WSGI app into a function that takes a request object and returns a
> response object. And also a middleware factory in
> wsgiwrap.middleware, which makes middleware relatively easier to run
> (I got the idea if a middleware wrapper from Sergey's code, but by
> unwrapping the application, it makes it easier to write
> response-modifying middleware). Huh, "unwrap" is a bad name for this.
> This isn't really tested, except to make sure it doesn't have syntax
> errors, as I'm really just trying to think about the API. Does this
> seem reasonable?
> Well, a little context: I've been using no framework for projects
> lately, but I keep rewriting this decorator. And always with just
> slightly different features and level of completeness (in a bad way).
> I want it Right, and it seems time. Also, I noticed Simon Willison
> playing with such a minimal framework
> (http://github.com/simonw/djng/tree), which got me in the state of
> mind. For instance, looking at this code:
> http://github.com/simonw/djng/blob/72d8a5aa9088720841321f8e4177d7ceb6... > here's how you'd do it with my code:
> Not really that different, but eh. At least it works, which took a
> little refactoring to accomplish.
> One thing I don't like here is default_500 has that extra e argument,
> making @wsgiwrap around it kind of useless. But I guess that's okay.
> One of the things I like with this is that you can have functions with
> parameters, and while they can't be WSGI apps (for reasonable
> reasons), they still look and act similar to everything else.
> You actually can turn them into WSGI apps if you do
> @wsgiwrap(add_urlvars=True) and then you can set req.urlvars['e'].
> Arguably add_urlvars should be set to true by default, except this
> means every function needs **kw to avoid TypeError just because
> req.urlvars is set at some point. There's code (that at least TG
> uses) that safely calls functions in these cases, but the code is
> complex so I worry about introducing it (at least by default).
<kumar.mcmil...@gmail.com> wrote: > I really like accepting a request and returning a response better than > the official way of creating middleware. A decorator to allow this > paradigm would be very nice. Rack pretty much got this part right > when they re-implemented wsgi 1.0 > http://rack.rubyforge.org/doc/SPEC.html
Yes, and people are talking about this for WSGI 2. But it doesn't really fix this either, as you are working with the raw WSGI-like objects (dicts and tuples) instead of request and response objects. So this has added value over even that.
> That djng micro framework is neat. The idea of reloadable / > reconfigurable services sounds interesting but I've never needed to > change a conf setting during runtime myself.
Probably there's some overlap with something like repoze.plugin too. There's a lot of ways of approaching configuration. I haven't figured out the right way myself.
Somewhat similar is something like Beaker that pokes things into the environ; it's essentially implementing a service in the scope of the request, and using the environ to indicate that.
I was using those decorators for a year and a half now and they work
great, sometimes better than I expected, I'll try to explain why I
think their design should be accepted into webob itself and try to
demonstrate how they change the webapp code. Also, if anyone here can
understand Russian, I was writing a series of articles on webdev w/o
framework which cover these decorators as well:
http://self.maluke.com/ The series is abandoned and I was thinking of
writing a guide covering similar territory in English, but that didn't
materialize yet, some nudging might help :)
Anyway, the idea is that a web application is something that takes
HTTP request and returns HTTP response, which naturally maps to a
function that takes webob.Request and returns webob.Response or raises
webob.exc.HTTPException. We need to be able to expose such functions
as WSGI app, so the obvious solution follows from there.
As I was using such a decorator one thing became obvious: returning a
webob.Response is a limitation, so the solution is (not as obviously)
is to return any WSGI app. This might seem strange as WSGI app does
not exactly maps to HTTP response, but as the wrapper knows the
request, then (envrion + WSGI app) does map to a HTTP response.
Anyway this works wonderfully and makes composing web apps from
disparate parts a breeze as well as writing new parts and implementing
them as really independent reusable components.
Before I show how the code looks when using this decorator I have to
mention that the decorator works on methods, which means that if your
webapp needs configuration or multiple instances, it still can use the
decorator.
Do have a look at the EvalException class -- it has to serve some
static content to help error pages look and work properly and it has
to find the saved traceback based on url. With webob_wrap we can scrap
huge chunks of code and the remaining will be clearly readable. For
example have a look at `media` and `mochikit' methods -- the entire
thing can be replaced with:
media = urlparser.StaticURLParser(os.path.join(os.path.dirname(__file__),
'media'))
mochikit = urlparser.StaticURLParser(os.path.join(os.path.dirname(__file__),
'mochikit'))
Let's go back to __call__ method, it becomes this:
@webob_wrap
def __call__(self, req):
assert not req.environ['wsgi.multiprocess'], (
"The EvalException middleware is not usable in a "
"multi-process environment")
req.environ['paste.evalexception'] = self
if req.path_info.startswith('/_debug/'):
return self.debug
else:
return self.respond
Note how we just return self.debug / self.respond -- those are WSGI
apps (in this case implemented as webob_wrapped methods).
Now let's clean up debug:
@webob_wrap
def debug(self, req):
assert req.path_info_pop() == '_debug'
next_part = req.path_info_pop()
if next_part in self.exposed_methods:
return getattr(self, next_part)
Now that's a much shorter and clearer method, isn't it? Note that the
decorator interprets the `None` return value as 404 Not Found.
One can go through entire module this way and make it a fraction of
the current size.
One thing that used to bother me was is that the wrapped functions
loose their signature becoming WSGI apps -- now if you want to change
their return value you don't call them directly but use
req.get_response(self.subapp) for example. But it's better that way --
as those apps might return something other than webob.Response and
using get_response gives us the guarantee that we get webob.Response.
Also, if you really need to get to the wrapped function you can use
self.subapp.func(req).
As of middleware, for most uses webob_wrap is enough, for example:
def gzip_middleware(app):
@webob_wrap
def wrapped(req):
resp = req.get_response(app)
if 'gzip' in req.accept_encoding:
resp.encode_content()
return resp
return wrapped
@webob_wrap
def __call__(self, req):
resp = req.get_response(app)
if 'gzip' in req.accept_encoding and resp.content_type in self.types:
resp.encode_content()
return resp
And some closing notes:
* Not only you can easily embed foreign code into you apps, you can
directly mesh your code with other apps (paste URLMap etc)
* Even if anyone wants more full-featured decorator, such as
extracting form values and passing them as arguments webob_wrap would
serve as a sound foundation for that, one doesn't need to make
everything one giant decorator. Also, I'd advice against using the
argument extraction described above: req.GET['arg'] works just fine.
* This is not a framework and not even a beginning of one --
frameworks imply inversion of control at some point and this is just a
decorator (or a bunch of). Frameworks always take their price for the
provided convenience, @webob_wrap doesn't.
On Tue, May 12, 2009 at 00:57, Ian Bicking <i...@colorstudy.com> wrote:
> I'm thinking about adding a simple decorator to WebOb, to provide a
> kind of minimalistic framework. I looked at Sergey Schetinin's
> decorators (in http://svn.pythonpaste.org/Paste/WebOb/contrib) and
> borrowed a few ideas from that, as well as ideas of my own. Actually,
> the code isn't really that simple, except in the way I think of WebOb
> as "simple"... completeness adds size, but it's trying to accomplish
> something relatively simple.
> The idea is basically to prefer the simple call signature of "resp =
> func(req)", as well as allowing HTTPExceptions. There are some other
> hooks to fiddle with the internals, though I'm not sure they are
> entirely useful. Also notable is wsgiwrap.unwrap, which converts a
> WSGI app into a function that takes a request object and returns a
> response object. And also a middleware factory in
> wsgiwrap.middleware, which makes middleware relatively easier to run
> (I got the idea if a middleware wrapper from Sergey's code, but by
> unwrapping the application, it makes it easier to write
> response-modifying middleware). Huh, "unwrap" is a bad name for this.
> This isn't really tested, except to make sure it doesn't have syntax
> errors, as I'm really just trying to think about the API. Does this
> seem reasonable?
> Well, a little context: I've been using no framework for projects
> lately, but I keep rewriting this decorator. And always with just
> slightly different features and level of completeness (in a bad way).
> I want it Right, and it seems time. Also, I noticed Simon Willison
> playing with such a minimal framework
> (http://github.com/simonw/djng/tree), which got me in the state of
> mind. For instance, looking at this code:
> http://github.com/simonw/djng/blob/72d8a5aa9088720841321f8e4177d7ceb6... > here's how you'd do it with my code:
> Not really that different, but eh. At least it works, which took a
> little refactoring to accomplish.
> One thing I don't like here is default_500 has that extra e argument,
> making @wsgiwrap around it kind of useless. But I guess that's okay.
> One of the things I like with this is that you can have functions with
> parameters, and while they can't be WSGI apps (for reasonable
> reasons), they still look and act similar to everything else.
> You actually can turn them into WSGI apps if you do
> @wsgiwrap(add_urlvars=True) and then you can set req.urlvars['e'].
> Arguably add_urlvars should be set to true by default, except this
> means every function needs **kw to avoid TypeError just because
> req.urlvars is set at some point. There's code (that at least TG
> uses) that safely calls functions in these cases, but the code is
> complex so I worry about introducing it (at least by default).
>> That djng micro framework is neat. The idea of reloadable / >> reconfigurable services sounds interesting but I've never needed to >> change a conf setting during runtime myself.
> Probably there's some overlap with something like repoze.plugin too. > There's a lot of ways of approaching configuration. I haven't figured > out the right way myself.
> Somewhat similar is something like Beaker that pokes things into the > environ; it's essentially implementing a service in the scope of the > request, and using the environ to indicate that.
Contextual works really well for these things, much better than messing with environ does.
On Tue, May 12, 2009 at 12:23 AM, Sergey Schetinin <mal...@gmail.com> wrote: >>> That djng micro framework is neat. The idea of reloadable / >>> reconfigurable services sounds interesting but I've never needed to >>> change a conf setting during runtime myself.
>> Probably there's some overlap with something like repoze.plugin too. >> There's a lot of ways of approaching configuration. I haven't figured >> out the right way myself.
>> Somewhat similar is something like Beaker that pokes things into the >> environ; it's essentially implementing a service in the scope of the >> request, and using the environ to indicate that.
> Contextual works really well for these things, much better than > messing with environ does.
One issue with all of these techniques is they make the interactive debugger break. paste.registry solves that (but very complexly) and as far as I know that's the only thing that tries to address the problem of restoring context. Contextual could address that, but I don't know if it ever will.
On Tue, May 12, 2009 at 12:19 AM, Sergey Schetinin <mal...@gmail.com> wrote: > I was using those decorators for a year and a half now and they work > great, sometimes better than I expected, I'll try to explain why I > think their design should be accepted into webob itself and try to > demonstrate how they change the webapp code. Also, if anyone here can > understand Russian, I was writing a series of articles on webdev w/o > framework which cover these decorators as well: > http://self.maluke.com/ The series is abandoned and I was thinking of > writing a guide covering similar territory in English, but that didn't > materialize yet, some nudging might help :)
Yes, and the implementation I gave is also very similar. It has some more complexity in generating the response (handling cases where the function doesn't return a response object), and I'm not sure if that is justified. Also None isn't turned into 404, but I have a personal dislike of unhelpful 404s, so I'm not too psyched about that feature anyway.
> Anyway, the idea is that a web application is something that takes > HTTP request and returns HTTP response, which naturally maps to a > function that takes webob.Request and returns webob.Response or raises > webob.exc.HTTPException. We need to be able to expose such functions > as WSGI app, so the obvious solution follows from there.
> As I was using such a decorator one thing became obvious: returning a > webob.Response is a limitation, so the solution is (not as obviously) > is to return any WSGI app. This might seem strange as WSGI app does > not exactly maps to HTTP response, but as the wrapper knows the > request, then (envrion + WSGI app) does map to a HTTP response.
> Anyway this works wonderfully and makes composing web apps from > disparate parts a breeze as well as writing new parts and implementing > them as really independent reusable components.
Sure, I've used this pattern myself in the past, specifically in FlatAtomPub (http://svn.pythonpaste.org/Paste/apps/FlatAtomPub/trunk). Ultimately I wasn't that happy with it, though, because I started passing around all my arguments as attributes on the request.
> Before I show how the code looks when using this decorator I have to > mention that the decorator works on methods, which means that if your > webapp needs configuration or multiple instances, it still can use the > decorator.
> Do have a look at the EvalException class -- it has to serve some > static content to help error pages look and work properly and it has > to find the saved traceback based on url. With webob_wrap we can scrap > huge chunks of code and the remaining will be clearly readable. For > example have a look at `media` and `mochikit' methods -- the entire > thing can be replaced with:
Well, also evalexception is kind of a mess, the result of exploratory coding that was never fully refactored. So... not a great comparison ;) But yes, it fits the pattern.
> Let's go back to __call__ method, it becomes this:
> @webob_wrap > def __call__(self, req): > assert not req.environ['wsgi.multiprocess'], ( > "The EvalException middleware is not usable in a " > "multi-process environment") > req.environ['paste.evalexception'] = self > if req.path_info.startswith('/_debug/'): > return self.debug > else: > return self.respond
> Note how we just return self.debug / self.respond -- those are WSGI > apps (in this case implemented as webob_wrapped methods). > Now let's clean up debug:
> Now that's a much shorter and clearer method, isn't it? Note that the > decorator interprets the `None` return value as 404 Not Found.
> One can go through entire module this way and make it a fraction of > the current size.
> One thing that used to bother me was is that the wrapped functions > loose their signature becoming WSGI apps -- now if you want to change > their return value you don't call them directly but use > req.get_response(self.subapp) for example. But it's better that way -- > as those apps might return something other than webob.Response and > using get_response gives us the guarantee that we get webob.Response. > Also, if you really need to get to the wrapped function you can use > self.subapp.func(req).
My decorators invert this, you use self.subapp(req) or self.subapp.wsgi_app(environ, start_response). I think this is better for most cases. I think it's much more obvious when you do:
return self.subapp(req)
instead of simply:
return self.subapp
Of course you can do "return self.subapp.wsgi_app", but I doubt people will do that much given the simpler technique of calling the method.
> @webob_wrap > def __call__(self, req): > resp = req.get_response(app) > if 'gzip' in req.accept_encoding and resp.content_type in self.types: > resp.encode_content() > return resp
With wsgiwrap.unwrap, I think the situation is improved -- the application you are wrapping feels more like "internal" code, even if it is an arbitrary WSGI application.
> And some closing notes: > * Not only you can easily embed foreign code into you apps, you can > directly mesh your code with other apps (paste URLMap etc) > * Even if anyone wants more full-featured decorator, such as > extracting form values and passing them as arguments webob_wrap would > serve as a sound foundation for that, one doesn't need to make > everything one giant decorator. Also, I'd advice against using the > argument extraction described above: req.GET['arg'] works just fine. > * This is not a framework and not even a beginning of one -- > frameworks imply inversion of control at some point and this is just a > decorator (or a bunch of). Frameworks always take their price for the > provided convenience, @webob_wrap doesn't.
While I've put more features than is perhaps necessary into one class, I think for the most part the code I give accomplishes the same things. Is there something specific that you think webob_wrap does that wsgiwrap does worse?
> One issue with all of these techniques is they make the interactive > debugger break. paste.registry solves that (but very complexly) and > as far as I know that's the only thing that tries to address the > problem of restoring context. Contextual could address that, but I > don't know if it ever will.
I think that saying that it "breaks" the debugger is a bit of exaggeration. Sure things change as the exception propagates, but then we could say that "finally" blocks break it too. I never really had a problem with debugging because of it, at least it's very clear when the services are being used so you know you can't rely on them being the same when exploring the traceback.
>> Anyway, the idea is that a web application is something that takes >> HTTP request and returns HTTP response, which naturally maps to a >> function that takes webob.Request and returns webob.Response or raises >> webob.exc.HTTPException. We need to be able to expose such functions >> as WSGI app, so the obvious solution follows from there.
>> As I was using such a decorator one thing became obvious: returning a >> webob.Response is a limitation, so the solution is (not as obviously) >> is to return any WSGI app. This might seem strange as WSGI app does >> not exactly maps to HTTP response, but as the wrapper knows the >> request, then (envrion + WSGI app) does map to a HTTP response.
>> Anyway this works wonderfully and makes composing web apps from >> disparate parts a breeze as well as writing new parts and implementing >> them as really independent reusable components.
> Sure, I've used this pattern myself in the past, specifically in > FlatAtomPub (http://svn.pythonpaste.org/Paste/apps/FlatAtomPub/trunk). > Ultimately I wasn't that happy with it, though, because I started > passing around all my arguments as attributes on the request.
Well, that breaks WSGI doesn't it? I'm with PJE on the issue of adding new keys to environ in middleware and applications, so when my apps need more parameters I implement them as classes or closures. This has the added benefit of them now being easily separable from the rest of the app. For example a forum topic would be a ForumTopic(id) which is a self-contained WSGI app that you can use anywhere at all.
> Well, also evalexception is kind of a mess, the result of exploratory > coding that was never fully refactored. So... not a great comparison > ;) But yes, it fits the pattern.
Well, that's true, but it also shows how evalexception was trying to do the most obvious thing with request dispatching and WSGI was standing in the way, and with webob_wrap the very same approach simply works and it's still all wsgi.
>> One thing that used to bother me was is that the wrapped functions >> loose their signature becoming WSGI apps -- now if you want to change >> their return value you don't call them directly but use >> req.get_response(self.subapp) for example. But it's better that way -- >> as those apps might return something other than webob.Response and >> using get_response gives us the guarantee that we get webob.Response. >> Also, if you really need to get to the wrapped function you can use >> self.subapp.func(req).
> My decorators invert this, you use self.subapp(req) or > self.subapp.wsgi_app(environ, start_response). I think this is better > for most cases. I think it's much more obvious when you do:
> return self.subapp(req)
> instead of simply:
> return self.subapp
> Of course you can do "return self.subapp.wsgi_app", but I doubt people > will do that much given the simpler technique of calling the method.
Well, the main difference between our approaches here, I think, is that I'm trying to make as few assumptions as possible -- when returning self.subapp the only assumption is that it's a WSGI app. With your approach it's assumed by default that subapp is not a wsgi app but a wrapped method, same thing if using self.subapp.wsgi_app -- you can't just replace it with something else.
When using webob_wrap you can use it in just one place and nothing else will need to know anything about it, not so with wsgiwrap.
>> @webob_wrap >> def __call__(self, req): >> resp = req.get_response(app) >> if 'gzip' in req.accept_encoding and resp.content_type in self.types: >> resp.encode_content() >> return resp
I guess I'll add the webob_middleware example as well
@webob_middleware def gzip_middleware(req, app, types=('text/plain',)): resp = req.get_response(app) if 'gzip' in req.accept_encoding and resp.content_type in types: resp.encode_content() return resp
> With wsgiwrap.unwrap, I think the situation is improved -- the > application you are wrapping feels more like "internal" code, even if > it is an arbitrary WSGI application.
I don't think trying to make everything adapt to some new convention is the right thing to do -- you adapt external components to it, but you inevitably write your code to conform to it in the first place -- this means that code is not reusable without more adaptation or rewrites. That's exactly the problem with frameworks -- reinventing the wheel in the name of convenience and isolating itself as a result. If we agree that webob_wrap is close in convenience of use, then I think it wins on the base of that it makes everything fit by default, simply by enforcing that every wrapped function and method is a WSGI app.
Surely sometimes you just want to call self.subapp(req, arg), but then we don't need no decorators for it -- self.subapp.wsgi_app wouldn't work anyway, right?
>> And some closing notes: >> * Not only you can easily embed foreign code into you apps, you can >> directly mesh your code with other apps (paste URLMap etc)
When we need to delegate the response to an URLMap instance for example, what would be the code with wsgiwrap, "return wsgiwrap.unwrap(urlmap)(req)" ? With webob_wrap it's "return urlmap".
>> * This is not a framework and not even a beginning of one -- >> frameworks imply inversion of control at some point and this is just a >> decorator (or a bunch of). Frameworks always take their price for the >> provided convenience, @webob_wrap doesn't.
> While I've put more features than is perhaps necessary into one class, > I think for the most part the code I give accomplishes the same > things. Is there something specific that you think webob_wrap does > that wsgiwrap does worse?
I've outlined what I think is it's flaw: introducing another interface that has no clear benefit and does have a potential of creating yet one more "walled garden". That's how I see it anyway.
On Mon, May 11, 2009 at 2:57 PM, Ian Bicking <i...@colorstudy.com> wrote: > I'm thinking about adding a simple decorator to WebOb, to provide a > kind of minimalistic framework. I looked at Sergey Schetinin's > decorators (in http://svn.pythonpaste.org/Paste/WebOb/contrib) and > borrowed a few ideas from that, as well as ideas of my own. Actually, > the code isn't really that simple, except in the way I think of WebOb > as "simple"... completeness adds size, but it's trying to accomplish > something relatively simple.
> The idea is basically to prefer the simple call signature of "resp = > func(req)", as well as allowing HTTPExceptions. There are some other > hooks to fiddle with the internals, though I'm not sure they are > entirely useful. Also notable is wsgiwrap.unwrap, which converts a > WSGI app into a function that takes a request object and returns a > response object. And also a middleware factory in > wsgiwrap.middleware, which makes middleware relatively easier to run > (I got the idea if a middleware wrapper from Sergey's code, but by > unwrapping the application, it makes it easier to write > response-modifying middleware). Huh, "unwrap" is a bad name for this.
This is great news. I have long been advocating an application-building approach based on WebOb rather than raw WSGI, and this looks like a step in the right direction. It's a little abstract for me at this point (I'm doing well with Pylons), but I hope to get involved with it later when it solidifies.
The unwrap concept sounds like one of the most important contributions, but I agree it needs to be renamed: "unwrap" sounds confusing and perhaps misleading.
I'm not sure about Sergey's assertion that Paste should remain together rather than smashed into pieces. Partly because I know Ian and trust his judgement, and he has long been thinking that the Paste components would do better standalone.
paste.deploy and paste.script are distributed standalone as a namespace package, and I think that's the worst of either world. They were split because some users would not use them if they were packaged with the whole kaboodle, and those users were considered vital to its success. At the same time, namespace packages add an unnecessary layer of complexity. For instance, don't they depend on Setuptools? Which causes problems for people who have trouble installing Setuptools, or using utilities like Py2exe which don't recognize Setuptools.
The problem with the Paste components was that they stagnated, and they couldn't be changed without breaking backward compatibility. Whereas with separate packages, you just don't install the ones that are behind the times. WebOb was an incompatible upgrade of Paste.Request/Response, at it has now gained as much use as Paste itself.
Users of PasteDeploy and PasteScript have some doubts about whether it's the best approach. Likewise, Paste.Config has been wrapped in Pylons because the raw implementation wasn't adequate. The rest of Paste is small useful pieces: FileApp and Composite probably being the most useful. NiserConfigParser (the lowerkase 'C' key just got stuk in my GMail) deserves to be unburied from loadwsgi and enhansed. But most of the rest is obsolete, like Webware and Wareweb. Perhaps FileApp and Composite do need a kollektion bekause they're too small to release standalone, but that doesn't mean Paste is nesessarily the best kollektion.
While I'm at it, "We have a pony" is more succulent and forceful than "We have a pony and/or a unicorn". Can the unicorn be moved, or the docs changed to "We have a pony. And now also a unicorn."
On Thu, May 14, 2009 at 10:28, Mike Orr <sluggos...@gmail.com> wrote: > I'm not sure about Sergey's assertion that Paste should remain > together rather than smashed into pieces. Partly because I know Ian > and trust his judgement, and he has long been thinking that the Paste > components would do better standalone.
On Thu, May 14, 2009 at 2:20 AM, Sergey Schetinin <mal...@gmail.com> wrote: >>> Anyway, the idea is that a web application is something that takes >>> HTTP request and returns HTTP response, which naturally maps to a >>> function that takes webob.Request and returns webob.Response or raises >>> webob.exc.HTTPException. We need to be able to expose such functions >>> as WSGI app, so the obvious solution follows from there.
>>> As I was using such a decorator one thing became obvious: returning a >>> webob.Response is a limitation, so the solution is (not as obviously) >>> is to return any WSGI app. This might seem strange as WSGI app does >>> not exactly maps to HTTP response, but as the wrapper knows the >>> request, then (envrion + WSGI app) does map to a HTTP response.
>>> Anyway this works wonderfully and makes composing web apps from >>> disparate parts a breeze as well as writing new parts and implementing >>> them as really independent reusable components.
>> Sure, I've used this pattern myself in the past, specifically in >> FlatAtomPub (http://svn.pythonpaste.org/Paste/apps/FlatAtomPub/trunk). >> Ultimately I wasn't that happy with it, though, because I started >> passing around all my arguments as attributes on the request.
> Well, that breaks WSGI doesn't it? I'm with PJE on the issue of adding > new keys to environ in middleware and applications, so when my apps > need more parameters I implement them as classes or closures. This has > the added benefit of them now being easily separable from the rest of > the app. For example a forum topic would be a ForumTopic(id) which is > a self-contained WSGI app that you can use anywhere at all.
Yes, I could have done it all as methods on a class, or closures, or something like that. If the class was instantiated per-request it'd be relatively easy, though that's a different pattern still.
I was thinking how PylonsController could fit into this pattern, actually. You could have something like:
class wrapinstance(object): def __init__(self, instantiate_with_request=False): self.instantiate_with_request = instantiate_with_request def __get__(self, obj, type=None): if obj is None: def wsgi_app(environ, start_response): if self.instantiate_with_request: instance = type(Request(environ)) else: instance = type() return wsgiwrap(instance).wsgi_app(environ, start_response) return wsgiwrap(obj)
>>> One thing that used to bother me was is that the wrapped functions >>> loose their signature becoming WSGI apps -- now if you want to change >>> their return value you don't call them directly but use >>> req.get_response(self.subapp) for example. But it's better that way -- >>> as those apps might return something other than webob.Response and >>> using get_response gives us the guarantee that we get webob.Response. >>> Also, if you really need to get to the wrapped function you can use >>> self.subapp.func(req).
>> My decorators invert this, you use self.subapp(req) or >> self.subapp.wsgi_app(environ, start_response). I think this is better >> for most cases. I think it's much more obvious when you do:
>> return self.subapp(req)
>> instead of simply:
>> return self.subapp
>> Of course you can do "return self.subapp.wsgi_app", but I doubt people >> will do that much given the simpler technique of calling the method.
> Well, the main difference between our approaches here, I think, is > that I'm trying to make as few assumptions as possible -- when > returning self.subapp the only assumption is that it's a WSGI app. > With your approach it's assumed by default that subapp is not a wsgi > app but a wrapped method, same thing if using self.subapp.wsgi_app -- > you can't just replace it with something else.
> When using webob_wrap you can use it in just one place and nothing > else will need to know anything about it, not so with wsgiwrap.
No, wsgiwrap works anywhere. If you look at wsgiwrap.unwrap it takes an *arbitrary* WSGI application and returns an object that acts like something wsgiwrap wrapped (it takes a single request argument and returns a WebOb response). It does this without any expectation beyond WSGI itself (though there's a shortcut in the code for the case when a wsgiwrap middleware wraps a wsgiapp application).
>>> @webob_wrap >>> def __call__(self, req): >>> resp = req.get_response(app) >>> if 'gzip' in req.accept_encoding and resp.content_type in self.types: >>> resp.encode_content() >>> return resp
> I guess I'll add the webob_middleware example as well
> @webob_middleware > def gzip_middleware(req, app, types=('text/plain',)): > resp = req.get_response(app) > if 'gzip' in req.accept_encoding and resp.content_type in types: > resp.encode_content() > return resp
>> With wsgiwrap.unwrap, I think the situation is improved -- the >> application you are wrapping feels more like "internal" code, even if >> it is an arbitrary WSGI application.
> I don't think trying to make everything adapt to some new convention > is the right thing to do -- you adapt external components to it, but > you inevitably write your code to conform to it in the first place -- > this means that code is not reusable without more adaptation or > rewrites. That's exactly the problem with frameworks -- reinventing > the wheel in the name of convenience and isolating itself as a result. > If we agree that webob_wrap is close in convenience of use, then I > think it wins on the base of that it makes everything fit by default, > simply by enforcing that every wrapped function and method is a WSGI > app.
> Surely sometimes you just want to call self.subapp(req, arg), but then > we don't need no decorators for it -- self.subapp.wsgi_app wouldn't > work anyway, right?
I want there to be a relatively small amount of impedance between a method that takes a request argument (which is common regardless of how anything else works) and a WSGI application.
For the case of classes, I think I'd like to see a situation where something like PylonsController is just *a little* closer to being a WSGI application. Right now it is after you instantiate it, but it isn't self-instantiating and instances can't be shared. And I think there's some other details that keep people from treating them like WSGI applications (or dropping in alternates) even though they can.
Also, for a general application it is quite likely you'll just have one up-front WSGI application, and everything internally will use this convention (request-in, response-out) regardless of whether it has to or if it could be WSGI internally. I like that this decorator meets that case halfway.
>>> And some closing notes: >>> * Not only you can easily embed foreign code into you apps, you can >>> directly mesh your code with other apps (paste URLMap etc)
> When we need to delegate the response to an URLMap instance for > example, what would be the code with wsgiwrap, "return > wsgiwrap.unwrap(urlmap)(req)" ? > With webob_wrap it's "return urlmap".
You can do that too, it treats "responses" as WSGI applications and doesn't make further assumptions. Our implementations aren't that different from each other.
>>> * This is not a framework and not even a beginning of one -- >>> frameworks imply inversion of control at some point and this is just a >>> decorator (or a bunch of). Frameworks always take their price for the >>> provided convenience, @webob_wrap doesn't.
>> While I've put more features than is perhaps necessary into one class, >> I think for the most part the code I give accomplishes the same >> things. Is there something specific that you think webob_wrap does >> that wsgiwrap does worse?
> I've outlined what I think is it's flaw: introducing another interface > that has no clear benefit and does have a potential of creating yet > one more "walled garden". That's how I see it anyway.
Well, please look more closely, as it's not a walled garden. It does introduce another interface, but it's basically the same internal interface as webob_wrap, it just makes that interface external as well.
>> Well, the main difference between our approaches here, I think, is >> that I'm trying to make as few assumptions as possible -- when >> returning self.subapp the only assumption is that it's a WSGI app. >> With your approach it's assumed by default that subapp is not a wsgi >> app but a wrapped method, same thing if using self.subapp.wsgi_app -- >> you can't just replace it with something else.
>> When using webob_wrap you can use it in just one place and nothing >> else will need to know anything about it, not so with wsgiwrap.
> No, wsgiwrap works anywhere. If you look at wsgiwrap.unwrap it takes > an *arbitrary* WSGI application and returns an object that acts like > something wsgiwrap wrapped (it takes a single request argument and > returns a WebOb response). It does this without any expectation > beyond WSGI itself (though there's a shortcut in the code for the case > when a wsgiwrap middleware wraps a wsgiapp application).
I understand that, what I'm concerned with is that with wsgiwrap the shorter way is to call the app / method with the request as argument and while you're at it, why not add more arguments? That immediately makes it harder to later replace that part with an arbitrary WSGI app. As soon as we start calling instead of returning subapps all kinds of interfaces will appear when they don't really need to exist. With webob_wrap adding an argument will immediately break things which is a good thing. I make it very explicit that you step away from WSGI when you do it. That nudges developer in the right direction (I, for one, appreciate this).
I'd really rather prefer apps to be WSGI wherever you slice them instead of "one up-front WSGI application, and everything internally will use this convention" which is albeit small but still a walled garden.
>> I don't think trying to make everything adapt to some new convention >> is the right thing to do -- you adapt external components to it, but >> you inevitably write your code to conform to it in the first place -- >> this means that code is not reusable without more adaptation or >> rewrites. That's exactly the problem with frameworks -- reinventing >> the wheel in the name of convenience and isolating itself as a result. >> If we agree that webob_wrap is close in convenience of use, then I >> think it wins on the base of that it makes everything fit by default, >> simply by enforcing that every wrapped function and method is a WSGI >> app.
>> Surely sometimes you just want to call self.subapp(req, arg), but then >> we don't need no decorators for it -- self.subapp.wsgi_app wouldn't >> work anyway, right?
> I want there to be a relatively small amount of impedance between a > method that takes a request argument (which is common regardless of > how anything else works) and a WSGI application.
> For the case of classes, I think I'd like to see a situation where > something like PylonsController is just *a little* closer to being a > WSGI application. Right now it is after you instantiate it, but it > isn't self-instantiating and instances can't be shared. And I think > there's some other details that keep people from treating them like > WSGI applications (or dropping in alternates) even though they can.
I don't think it's really relevant to discussion about decorators. Maybe this would be useful to make controllers / servlets play nice with WSGI, but I don't like that approach anyway.
> Also, for a general application it is quite likely you'll just have > one up-front WSGI application, and everything internally will use this > convention (request-in, response-out) regardless of whether it has to > or if it could be WSGI internally. I like that this decorator meets > that case halfway.
I don't like the fact that if something uses this decorator it doesn't mean you can use it as WSGI, it makes things inside decorator simpler but it doesn't help much the code that will use the wrapped method.
>>>> And some closing notes: >>>> * Not only you can easily embed foreign code into you apps, you can >>>> directly mesh your code with other apps (paste URLMap etc)
>> When we need to delegate the response to an URLMap instance for >> example, what would be the code with wsgiwrap, "return >> wsgiwrap.unwrap(urlmap)(req)" ? >> With webob_wrap it's "return urlmap".
> You can do that too, it treats "responses" as WSGI applications and > doesn't make further assumptions. Our implementations aren't that > different from each other.
That's right, but the calling code will have assumptions about returned value. With webob_wrap you either return the app and thus not concerned with the exact returned value or get the response via req.get_response in which case you know for sure you get webob.Response. There's a way to get the wrapped function return value directly (subapp.func(req)) but as most people will never need that they'll never do that, and this is the only case when the caller needs to know more than that subapp is a WSGI app.
With wsgiwrap you either always have to think about what the called method returns or you have to enforce "only return webob.Response" rule yourself -- that's a wrong default IMO.
>>>> * This is not a framework and not even a beginning of one -- >>>> frameworks imply inversion of control at some point and this is just a >>>> decorator (or a bunch of). Frameworks always take their price for the >>>> provided convenience, @webob_wrap doesn't.
>>> While I've put more features than is perhaps necessary into one class, >>> I think for the most part the code I give accomplishes the same >>> things. Is there something specific that you think webob_wrap does >>> that wsgiwrap does worse?
>> I've outlined what I think is it's flaw: introducing another interface >> that has no clear benefit and does have a potential of creating yet >> one more "walled garden". That's how I see it anyway.
> Well, please look more closely, as it's not a walled garden. It does > introduce another interface, but it's basically the same internal > interface as webob_wrap, it just makes that interface external as > well.
I think it's very important to note that wsgiwrap allows to add more arguments which makes it different. Also having that interface external is a big deal too. To repeat my point once again with webob_wrap everything is reduced to common denominator (WSGI) by default, with wsgiwrap it's sometimes one step away and sometimes can't be done (because of arguments).
(The webapps themselves are usually quite a bit more complex, but in my experience webob_wrap does work for them just as well.)
It just doesn't get any simpler than that, what would the same apps look with wsgiwrap? IIUC it will need some wrap / unwrap code and to be used as wsgi apps (even when combining these kinds of micro-components) you'll need to access .wsgi_app.
I guess the code will be quite similar, also using get_response instead of unwrap, but that just shows that if we make the assumption that everything is WSGI things don't get ugly. unwrap is only necessary if we don't make the assumption that every subapp is just WSGI, so instead of calling things we'd be doing .get_response on them and if we do that why not simply return them as is? Doesn't wsgiwrap choose to keep the signature to satisfy the cases where it shouldn't be used at all really?
Same deal, except I believe the way I've written it there's no positional arguments, so secure_app would have to be a keyword argument (at least when you instantiate the middleware). That could just be fixed.
Hmm... okay, this is a little tricky, because you can't just replace that with "r = app(req)", because there's one code path where that could return a non-Response object (instead returning some other WSGI app). I'll have to think about that, I think it can be resolved.
> (The webapps themselves are usually quite a bit more complex, but in > my experience webob_wrap does work for them just as well.)
> It just doesn't get any simpler than that, what would the same apps > look with wsgiwrap? IIUC it will need some wrap / unwrap code and to > be used as wsgi apps (even when combining these kinds of > micro-components) you'll need to access .wsgi_app.
Nope.
> I guess the code will be quite similar, also using get_response > instead of unwrap, but that just shows that if we make the assumption > that everything is WSGI things don't get ugly. unwrap is only > necessary if we don't make the assumption that every subapp is just > WSGI, so instead of calling things we'd be doing .get_response on them > and if we do that why not simply return them as is? Doesn't wsgiwrap > choose to keep the signature to satisfy the cases where it shouldn't > be used at all really?
Sure, it's equivalent to req.get_response(), and basically that's all the code does, is create a function that calls req.get_response() on the WSGI application.
As to allowing arguments to be added, that isn't really allowed for with wsgiwrap any more than webob_wrap (except with stuff like urlvars, which isn't on by default and is largely internal to how the function wants to consume the request). It's just that you can apply wsgiwrap to a one-request-argument-taking function and then continue to call it like normal, or call it as a wsgi application. I think this will encourage a transition from private internal functions (which people will always write, with eclectic signatures) to reusable small applications (when that is appropriate).
File "decorator.py", line 50, in __call__ normalize_3xx_exceptions = self.normalize_3xx_exceptions AttributeError: 'wsgiwrap' object has no attribute 'normalize_3xx_exceptions
Let's just fix it by replacing that line with normalize_3xx_exceptions = None
Now I get
File "decorator.py", line 195, in __call__ return self[req.host.split(':')[0]] AttributeError: 'function' object has no attribute 'host'
File "decorator.py", line 80, in wsgi_app resp = self.apply(req) File "decorator.py", line 137, in apply return self(*args, **kw) File "decorator.py", line 58, in __call__ resp = self.func(*args, **kw) TypeError: __call__() takes exactly 2 arguments (1 given)
What? Oh, you have a typo in __get__ if hasattr(obj, '__get__'):
Should be: if hasattr(self.func, '__get__'):
Whan now?
File "decorator.py", line 59, in __call__ resp = self.normalize_response(req, resp, normalize_exceptions=normalize_exceptions, NameError: global name 'req' is not defined
Let's remove that call completely. What next?
File "decorator.py", line 104, in normalize_response resp = req.response.merge_cookies(resp) AttributeError: 'Response' object has no attribute 'merge_cookies'
Huh? Let's skip the normalization completely. In the end it does work similarly, but you have to use "app.__call__.wsgi_app" which isn't even close to just "app" as is the case with webob_wrap. Not identical at all, right?
>> That's all the code you need to dispatch on hostname.
> Same deal, except I believe the way I've written it there's no > positional arguments, so secure_app would have to be a keyword > argument (at least when you instantiate the middleware). That could > just be fixed.
File "decorator.py", line 180, in middleware_factory kw = kw.copy() UnboundLocalError: local variable 'kw' referenced before assignment
C'mon, you didn't even run this thing once? I'll just stop here -- these examples show exactly what I claimed they'll show -- you need to bend backwards to make wsgi apps combine with wsgiwrap.
> Hmm... okay, this is a little tricky, because you can't just replace > that with "r = app(req)", because there's one code path where that > could return a non-Response object (instead returning some other WSGI > app). I'll have to think about that, I think it can be resolved.
>> (The webapps themselves are usually quite a bit more complex, but in >> my experience webob_wrap does work for them just as well.)
>> It just doesn't get any simpler than that, what would the same apps >> look with wsgiwrap? IIUC it will need some wrap / unwrap code and to >> be used as wsgi apps (even when combining these kinds of >> micro-components) you'll need to access .wsgi_app.
>> I guess the code will be quite similar, also using get_response >> instead of unwrap, but that just shows that if we make the assumption >> that everything is WSGI things don't get ugly. unwrap is only >> necessary if we don't make the assumption that every subapp is just >> WSGI, so instead of calling things we'd be doing .get_response on them >> and if we do that why not simply return them as is? Doesn't wsgiwrap >> choose to keep the signature to satisfy the cases where it shouldn't >> be used at all really?
> Sure, it's equivalent to req.get_response(), and basically that's all > the code does, is create a function that calls req.get_response() on > the WSGI application.
Do we need more code then?
> As to allowing arguments to be added, that isn't really allowed for > with wsgiwrap any more than webob_wrap (except with stuff like > urlvars, which isn't on by default and is largely internal to how the > function wants to consume the request).
> It's just that you can apply > wsgiwrap to a one-request-argument-taking function and then continue > to call it like normal, or call it as a wsgi application. I think > this will encourage a transition from private internal functions > (which people will always write, with eclectic signatures) to reusable > small applications (when that is appropriate).
Now I'm impressed by how wrong you are about your own code. Pretty puzzling really.
On Thu, May 14, 2009 at 2:26 PM, Sergey Schetinin <mal...@gmail.com> wrote: > On Thu, May 14, 2009 at 21:46, Ian Bicking <i...@colorstudy.com> wrote: >> On Thu, May 14, 2009 at 1:27 PM, Sergey Schetinin <mal...@gmail.com> wrote:
> File "decorator.py", line 50, in __call__ > normalize_3xx_exceptions = self.normalize_3xx_exceptions > AttributeError: 'wsgiwrap' object has no attribute 'normalize_3xx_exceptions
> Let's just fix it by replacing that line with normalize_3xx_exceptions = None
> Now I get
> File "decorator.py", line 195, in __call__ > return self[req.host.split(':')[0]] > AttributeError: 'function' object has no attribute 'host'
> File "decorator.py", line 80, in wsgi_app > resp = self.apply(req) > File "decorator.py", line 137, in apply > return self(*args, **kw) > File "decorator.py", line 58, in __call__ > resp = self.func(*args, **kw) > TypeError: __call__() takes exactly 2 arguments (1 given)
> What? Oh, you have a typo in __get__ > if hasattr(obj, '__get__'):
> Should be: > if hasattr(self.func, '__get__'):
> Whan now?
> File "decorator.py", line 59, in __call__ > resp = self.normalize_response(req, resp, > normalize_exceptions=normalize_exceptions, > NameError: global name 'req' is not defined
> Let's remove that call completely. What next?
> File "decorator.py", line 104, in normalize_response > resp = req.response.merge_cookies(resp) > AttributeError: 'Response' object has no attribute 'merge_cookies'
> Huh? Let's skip the normalization completely. In the end it does work > similarly, but you have to use "app.__call__.wsgi_app" which isn't > even close to just "app" as is the case with webob_wrap. Not identical > at all, right?
>>> That's all the code you need to dispatch on hostname.
>> Same deal, except I believe the way I've written it there's no >> positional arguments, so secure_app would have to be a keyword >> argument (at least when you instantiate the middleware). That could >> just be fixed.
> File "decorator.py", line 180, in middleware_factory > kw = kw.copy() > UnboundLocalError: local variable 'kw' referenced before assignment
> C'mon, you didn't even run this thing once? I'll just stop here -- > these examples show exactly what I claimed they'll show -- you need to > bend backwards to make wsgi apps combine with wsgiwrap.
>> Hmm... okay, this is a little tricky, because you can't just replace >> that with "r = app(req)", because there's one code path where that >> could return a non-Response object (instead returning some other WSGI >> app). I'll have to think about that, I think it can be resolved.
>>> (The webapps themselves are usually quite a bit more complex, but in >>> my experience webob_wrap does work for them just as well.)
>>> It just doesn't get any simpler than that, what would the same apps >>> look with wsgiwrap? IIUC it will need some wrap / unwrap code and to >>> be used as wsgi apps (even when combining these kinds of >>> micro-components) you'll need to access .wsgi_app.
>>> I guess the code will be quite similar, also using get_response >>> instead of unwrap, but that just shows that if we make the assumption >>> that everything is WSGI things don't get ugly. unwrap is only >>> necessary if we don't make the assumption that every subapp is just >>> WSGI, so instead of calling things we'd be doing .get_response on them >>> and if we do that why not simply return them as is? Doesn't wsgiwrap >>> choose to keep the signature to satisfy the cases where it shouldn't >>> be used at all really?
>> Sure, it's equivalent to req.get_response(), and basically that's all >> the code does, is create a function that calls req.get_response() on >> the WSGI application.
> Do we need more code then?
>> As to allowing arguments to be added, that isn't really allowed for >> with wsgiwrap any more than webob_wrap (except with stuff like >> urlvars, which isn't on by default and is largely internal to how the >> function wants to consume the request).
>> It's just that you can apply >> wsgiwrap to a one-request-argument-taking function and then continue >> to call it like normal, or call it as a wsgi application. I think >> this will encourage a transition from private internal functions >> (which people will always write, with eclectic signatures) to reusable >> small applications (when that is appropriate).
> Now I'm impressed by how wrong you are about your own code. Pretty > puzzling really.
On Thu, May 14, 2009 at 22:30, Ian Bicking <i...@colorstudy.com> wrote: > Hey no fair, I said it wasn't tested! I have a fixed version that I > haven't committed yet.
> On Thu, May 14, 2009 at 2:26 PM, Sergey Schetinin <mal...@gmail.com> wrote: >> On Thu, May 14, 2009 at 21:46, Ian Bicking <i...@colorstudy.com> wrote: >>> On Thu, May 14, 2009 at 1:27 PM, Sergey Schetinin <mal...@gmail.com> wrote:
>> File "decorator.py", line 50, in __call__ >> normalize_3xx_exceptions = self.normalize_3xx_exceptions >> AttributeError: 'wsgiwrap' object has no attribute 'normalize_3xx_exceptions
>> Let's just fix it by replacing that line with normalize_3xx_exceptions = None
>> Now I get
>> File "decorator.py", line 195, in __call__ >> return self[req.host.split(':')[0]] >> AttributeError: 'function' object has no attribute 'host'
>> File "decorator.py", line 80, in wsgi_app >> resp = self.apply(req) >> File "decorator.py", line 137, in apply >> return self(*args, **kw) >> File "decorator.py", line 58, in __call__ >> resp = self.func(*args, **kw) >> TypeError: __call__() takes exactly 2 arguments (1 given)
>> What? Oh, you have a typo in __get__ >> if hasattr(obj, '__get__'):
>> Should be: >> if hasattr(self.func, '__get__'):
>> Whan now?
>> File "decorator.py", line 59, in __call__ >> resp = self.normalize_response(req, resp, >> normalize_exceptions=normalize_exceptions, >> NameError: global name 'req' is not defined
>> Let's remove that call completely. What next?
>> File "decorator.py", line 104, in normalize_response >> resp = req.response.merge_cookies(resp) >> AttributeError: 'Response' object has no attribute 'merge_cookies'
>> Huh? Let's skip the normalization completely. In the end it does work >> similarly, but you have to use "app.__call__.wsgi_app" which isn't >> even close to just "app" as is the case with webob_wrap. Not identical >> at all, right?
>>>> That's all the code you need to dispatch on hostname.
>>> Same deal, except I believe the way I've written it there's no >>> positional arguments, so secure_app would have to be a keyword >>> argument (at least when you instantiate the middleware). That could >>> just be fixed.
>> File "decorator.py", line 180, in middleware_factory >> kw = kw.copy() >> UnboundLocalError: local variable 'kw' referenced before assignment
>> C'mon, you didn't even run this thing once? I'll just stop here -- >> these examples show exactly what I claimed they'll show -- you need to >> bend backwards to make wsgi apps combine with wsgiwrap.
>>> Hmm... okay, this is a little tricky, because you can't just replace >>> that with "r = app(req)", because there's one code path where that >>> could return a non-Response object (instead returning some other WSGI >>> app). I'll have to think about that, I think it can be resolved.
>>>> (The webapps themselves are usually quite a bit more complex, but in >>>> my experience webob_wrap does work for them just as well.)
>>>> It just doesn't get any simpler than that, what would the same apps >>>> look with wsgiwrap? IIUC it will need some wrap / unwrap code and to >>>> be used as wsgi apps (even when combining these kinds of >>>> micro-components) you'll need to access .wsgi_app.
>>>> I guess the code will be quite similar, also using get_response >>>> instead of unwrap, but that just shows that if we make the assumption >>>> that everything is WSGI things don't get ugly. unwrap is only >>>> necessary if we don't make the assumption that every subapp is just >>>> WSGI, so instead of calling things we'd be doing .get_response on them >>>> and if we do that why not simply return them as is? Doesn't wsgiwrap >>>> choose to keep the signature to satisfy the cases where it shouldn't >>>> be used at all really?
>>> Sure, it's equivalent to req.get_response(), and basically that's all >>> the code does, is create a function that calls req.get_response() on >>> the WSGI application.
>> Do we need more code then?
>>> As to allowing arguments to be added, that isn't really allowed for >>> with wsgiwrap any more than webob_wrap (except with stuff like >>> urlvars, which isn't on by default and is largely internal to how the >>> function wants to consume the request).
>>> It's just that you can apply >>> wsgiwrap to a one-request-argument-taking function and then continue >>> to call it like normal, or call it as a wsgi application. I think >>> this will encourage a transition from private internal functions >>> (which people will always write, with eclectic signatures) to reusable >>> small applications (when that is appropriate).
>> Now I'm impressed by how wrong you are about your own code. Pretty >> puzzling really.
> On Thu, May 14, 2009 at 21:46, Ian Bicking <i...@colorstudy.com> wrote: >> On Thu, May 14, 2009 at 1:27 PM, Sergey Schetinin <mal...@gmail.com> wrote:
I would plan on adding a check for .wsgi_app to get_response (or rather the underlying call_application), but even that wouldn't work, because hasattr(app, 'wsgi_app') isn't true.
OTOH, wsgify(app) *does* work reasonably, as the decorator doesn't mask the rest of the API. Class decorators wouldn't work... but it might be an interesting thought experiment.
>> Same deal, except I believe the way I've written it there's no >> positional arguments, so secure_app would have to be a keyword >> argument (at least when you instantiate the middleware). That could >> just be fixed.
>>> I guess the code will be quite similar, also using get_response >>> instead of unwrap, but that just shows that if we make the assumption >>> that everything is WSGI things don't get ugly. unwrap is only >>> necessary if we don't make the assumption that every subapp is just >>> WSGI, so instead of calling things we'd be doing .get_response on them >>> and if we do that why not simply return them as is? Doesn't wsgiwrap >>> choose to keep the signature to satisfy the cases where it shouldn't >>> be used at all really?
>> Sure, it's equivalent to req.get_response(), and basically that's all >> the code does, is create a function that calls req.get_response() on >> the WSGI application.
> Do we need more code then?
No.
>> As to allowing arguments to be added, that isn't really allowed for >> with wsgiwrap any more than webob_wrap (except with stuff like >> urlvars, which isn't on by default and is largely internal to how the >> function wants to consume the request).
>> It's just that you can apply >> wsgiwrap to a one-request-argument-taking function and then continue >> to call it like normal, or call it as a wsgi application. I think >> this will encourage a transition from private internal functions >> (which people will always write, with eclectic signatures) to reusable >> small applications (when that is appropriate).
> Now I'm impressed by how wrong you are about your own code. Pretty > puzzling really.
Well, I'm not sure if this discussion is moving forward much. I want to explore annotation of functions instead of replacing functions with WSGI versions, because I'm not comfortable with replacement generally, having used that technique quite a bit. And of course there's nothing to keep both from existing. At this point it doesn't seem like we're going to come to an agreement.
> 2009/5/14 Sergey Schetinin <mal...@gmail.com>: >> On Thu, May 14, 2009 at 21:46, Ian Bicking <i...@colorstudy.com> wrote: >>> On Thu, May 14, 2009 at 1:27 PM, Sergey Schetinin <mal...@gmail.com> wrote:
> I would plan on adding a check for .wsgi_app to get_response (or > rather the underlying call_application), but even that wouldn't work, > because hasattr(app, 'wsgi_app') isn't true.
And it would be completely pointless, because it's not about get_response. We might want to use the app as
> OTOH, wsgify(app) *does* work reasonably, as the decorator doesn't > mask the rest of the API. Class decorators wouldn't work... but it > might be an interesting thought experiment.
>>>> That's all the code you need to dispatch on hostname.
>>> Same deal, except I believe the way I've written it there's no >>> positional arguments, so secure_app would have to be a keyword >>> argument (at least when you instantiate the middleware). That could >>> just be fixed.
>>>> I guess the code will be quite similar, also using get_response >>>> instead of unwrap, but that just shows that if we make the assumption >>>> that everything is WSGI things don't get ugly. unwrap is only >>>> necessary if we don't make the assumption that every subapp is just >>>> WSGI, so instead of calling things we'd be doing .get_response on them >>>> and if we do that why not simply return them as is? Doesn't wsgiwrap >>>> choose to keep the signature to satisfy the cases where it shouldn't >>>> be used at all really?
>>> Sure, it's equivalent to req.get_response(), and basically that's all >>> the code does, is create a function that calls req.get_response() on >>> the WSGI application.
>> Do we need more code then?
> No.
What's the point of unwrap / reverse then? I see it as adapting WSGI to new interface, something webob_wrap doesn't need.
>>> As to allowing arguments to be added, that isn't really allowed for >>> with wsgiwrap any more than webob_wrap (except with stuff like >>> urlvars, which isn't on by default and is largely internal to how the >>> function wants to consume the request).
> You could also do this with webob_wrap, and then call demo.func. You > can do things wrong, sure.
Please go back and read what I was saying about this -- you were saying it's not true, but it is true. With wsgify adding arguments is natural and it will only break when someone else will try to use .wsgi_app.
With webob_wrap you're returning things, which makes adding parameters, then calling the thing forgoing the wrapper a very unnatural thing to do. At least one would wonder what the wrapper was for AND the default use case will break.
>>> It's just that you can apply >>> wsgiwrap to a one-request-argument-taking function and then continue >>> to call it like normal, or call it as a wsgi application. I think >>> this will encourage a transition from private internal functions >>> (which people will always write, with eclectic signatures) to reusable >>> small applications (when that is appropriate).
>> Now I'm impressed by how wrong you are about your own code. Pretty >> puzzling really.
> Well, I'm not sure if this discussion is moving forward much. I want > to explore annotation of functions instead of replacing functions with > WSGI versions, because I'm not comfortable with replacement generally, > having used that technique quite a bit. And of course there's nothing > to keep both from existing. At this point it doesn't seem like we're > going to come to an agreement.
I understand that you have a preference and I'm OK with wsgify in webob core, it's none of my business really. What I don't think is OK is that you say things are identical when they are different in exactly the ways I said. Where can a discussion move if the issues are not discussed but are said not to exist? You are OK with them and I'm OK with that, just please don't say things are identical when they are not. There were a few more issues I brought up that were just ignored apparently, which I guess is better :)
I think I've explained my opinion, now it's your call.
On Fri, May 15, 2009 at 3:49 AM, Sergey Schetinin <mal...@gmail.com> wrote: >> OK, this is indeed tricky, even after all the bugs are fixed. You can do:
>> I would plan on adding a check for .wsgi_app to get_response (or >> rather the underlying call_application), but even that wouldn't work, >> because hasattr(app, 'wsgi_app') isn't true.
> And it would be completely pointless, because it's not about > get_response. We might want to use the app as
> urlmap['/somepath'] = app
> but we can't.
Correct, the conversion to a WSGI app would be, unfortunately, a bit messy... at times it would happen automatically, but at times it would not, e.g., when interacting with URLMap().
>> Wait, what is OtherApp()? Is this another instance of the __call__ problem?
> Yes it's another instance of that problem, which is a problem I keep > talking about and you keep saying doesn't exist.
I've admitted it exists (otherwise I couldn't have referred to it there!), and I haven't thought of a good resolution.
>> Well, I'm not sure if this discussion is moving forward much. I want >> to explore annotation of functions instead of replacing functions with >> WSGI versions, because I'm not comfortable with replacement generally, >> having used that technique quite a bit. And of course there's nothing >> to keep both from existing. At this point it doesn't seem like we're >> going to come to an agreement.
> I understand that you have a preference and I'm OK with wsgify in > webob core, it's none of my business really. What I don't think is OK > is that you say things are identical when they are different in > exactly the ways I said. Where can a discussion move if the issues are > not discussed but are said not to exist? You are OK with them and I'm > OK with that, just please don't say things are identical when they are > not. There were a few more issues I brought up that were just ignored > apparently, which I guess is better :)
I'm not sure what I haven't addressed. I've actually repeated several of the examples in the tests (http://svn.pythonpaste.org/Paste/WebOb/branches/ianb-decorator-experi...) and indeed they have been helpful in considering the implications of the design choices. And there's still one particular awkwardness with the decorating of a __call__ method, which doesn't expose .wsgi_app on the parent object. Is there another case in particular that you think I haven't addressed?
On Sat, May 16, 2009 at 02:41, Ian Bicking <i...@colorstudy.com> wrote: > On Fri, May 15, 2009 at 3:49 AM, Sergey Schetinin <mal...@gmail.com> wrote: >>> OK, this is indeed tricky, even after all the bugs are fixed. You can do:
>>> I would plan on adding a check for .wsgi_app to get_response (or >>> rather the underlying call_application), but even that wouldn't work, >>> because hasattr(app, 'wsgi_app') isn't true.
>> And it would be completely pointless, because it's not about >> get_response. We might want to use the app as
>> urlmap['/somepath'] = app
>> but we can't.
> Correct, the conversion to a WSGI app would be, unfortunately, a bit > messy... at times it would happen automatically, but at times it would > not, e.g., when interacting with URLMap().
Right. In other words there's a barrier between the wsgified functions and generic WSGI apps that might combine them. It's not terribly big but it's there. Same thing about deferring work to WSGI apps from wsgified functions -- you don't do it the same way as you defer work to other wsgified functions. And that means you can't easily replace one thing with another because sometimes it's a call, sometimes it's a return and sometimes it's a messy attribute access.
>>> Wait, what is OtherApp()? Is this another instance of the __call__ problem?
>> Yes it's another instance of that problem, which is a problem I keep >> talking about and you keep saying doesn't exist.
> I've admitted it exists (otherwise I couldn't have referred to it > there!), and I haven't thought of a good resolution.
Well, maybe I misunderstood, but the examples were meant to illustrate how things can be lean but will immediately get messy with wsgify. Your next response was that everything will work identically, which it seems to me kinda ignores that there's an issue with all this additional unwrapping, rewrapping etc that goes on with wsgify. For example, when using wsgified app as WSGI you need to know if it was implemented as a function or a class (or, to hide that detail you need additional factory function) -- nasty.
>>> Well, I'm not sure if this discussion is moving forward much. I want >>> to explore annotation of functions instead of replacing functions with >>> WSGI versions, because I'm not comfortable with replacement generally, >>> having used that technique quite a bit. And of course there's nothing >>> to keep both from existing. At this point it doesn't seem like we're >>> going to come to an agreement.
>> I understand that you have a preference and I'm OK with wsgify in >> webob core, it's none of my business really. What I don't think is OK >> is that you say things are identical when they are different in >> exactly the ways I said. Where can a discussion move if the issues are >> not discussed but are said not to exist? You are OK with them and I'm >> OK with that, just please don't say things are identical when they are >> not. There were a few more issues I brought up that were just ignored >> apparently, which I guess is better :)
> I'm not sure what I haven't addressed. I've actually repeated several > of the examples in the tests > (http://svn.pythonpaste.org/Paste/WebOb/branches/ianb-decorator-experi...) > and indeed they have been helpful in considering the implications of > the design choices. And there's still one particular awkwardness with > the decorating of a __call__ method, which doesn't expose .wsgi_app on > the parent object. Is there another case in particular that you think > I haven't addressed?
The mutating signature issue. The need to know about implementation of called apps in default use case / making assumptions. Introduction of a new interface. If the wsgify app returns another non-Response WSGI app, the caller has to be ready for non-Response return value -- developers will normally won't bother and that will make refactoring / customization harder. Even if someone will try to work strictly on WSGI level, parts can't be replaced -- if you access self.subapp.wsgi_app -- it can't be replaced with arbitrary WSGI app. Which adds up to a walled garden.
Surely one can live with all that, but is that a justifiable cost for getting to call things instead of returning them? :)