On Sat, Jun 19, 2010 at 12:11:39AM +0200, Marcel Hellkamp wrote:

> > So, much more simple and readable, and with only one environ lookup:
> > 
> >         # Cast Files into iterables
> >         if hasattr(out, 'read'):
> >             wrapper = request.environ.get('wsgi.file_wrapper', None)
> >             if wrapper: out = wrapper(out)
> >             return out
> 
> The 'wsgi.file_wrapper' is optional and we must return an iterable. Some
> file-like objects may not be iterable. A working fallback won't hurt :)

I see, but...

> --- a/bottle.py
> +++ b/bottle.py
> @@ -508,8 +508,8 @@ class Bottle(object):
>  
>          # File-like objects. Wrap or transfer in chunks that fit into memory.
>          if hasattr(out, 'read'):
> -            out = request.environ.get('wsgi.file_wrapper',
> -                  lambda x, y: iter(lambda: x.read(y), tob('')))(out, 
> 1024*64)
> +            return request.environ.get('wsgi.file_wrapper',
> +                   lambda x: iter(lambda: x.read(1024*64), tob('')))(out)
>  
>          # Handle Iterables. We peek into them to detect their inner type.
>          try:

...in this case we still lose the .close() method in case
wsgi.file_wrapper is not present in the environment (which, if I
observed correctly, is the case at least with paste and wsgiref).

I, for one, rely on that close() behaviour a lot, since I'm streaming
the output of a command line tool and at the end of streaming I need to
wait() for the tool to end or kill it if close() is called before it has
finished (such as if the client closes connection prematurely).

The only way I can see to fix it is to create a proper wrapper which
also proxies the close() method, something like this (uglyish,
untested):

         # Cast Files into iterables
         if hasattr(out, 'read'):
             wrapper = request.environ.get('wsgi.file_wrapper', None)
             if wrapper:
                 out = wrapper(out)
             elif not hasattr(out, "__iter__"):
                 class FilelikeToIterable(object):
                     def __init__(self, fd)
                         self.__orig = fd
                     def read(self, *args):
                         return self.__orig.read(*args)
                     def __iter__(self):
                         return self
                     def __next__(self):
                         res = self.__orig.read(1024*64)
                         if not res: raise StopIteration()
                         return res
                     def close(self):
                         if hasattr(self.__orig, "close"):
                             self.__orig.close()
                 out = FilelikeToIterable(out)
             return out

Alternatively, one could document that if a file-like object is
returned, it must be file-like enough to be iterable. I'd assume there
are not many non-iterable file-like objects in use out there, although
I know I can't know the bottle userbase as well as you do.


Ciao,

Enrico

-- 
GPG key: 4096R/E7AD5568 2009-05-08 Enrico Zini <enr...@enricozini.org>

Attachment: signature.asc
Description: Digital signature

Reply via email to