Hello community,

here is the log from the commit of package python-web.py for openSUSE:Factory 
checked in at 2012-07-02 11:07:46
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-web.py (Old)
 and      /work/SRC/openSUSE:Factory/.python-web.py.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-web.py", Maintainer is ""

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-web.py/python-web.py.changes      
2012-03-22 12:39:51.000000000 +0100
+++ /work/SRC/openSUSE:Factory/.python-web.py.new/python-web.py.changes 
2012-07-02 11:07:48.000000000 +0200
@@ -1,0 +2,19 @@
+Fri Jun 29 08:23:50 UTC 2012 - cfarr...@suse.com
+
+- Updated to 0.37
+* Fixed datestr issue on Windows -- #155
+* Fixed Python 2.4 compatability issues (tx fredludlow)
+* Fixed error in utils.safewrite (tx shuge) -- #95
+* Allow use of web.data() with app.request() -- #105
+* Fixed an issue with session initializaton (tx beardedprojamz) -- #109
+* Allow custom message on 400 Bad Request (tx patryk) -- #121
+* Made djangoerror work on GAE. -- #80
+* Handle malformatted data in the urls. -- #117
+* Made it easier to stop the dev server -- #100, #122
+* Added support fot customizing cookie_path in session (tx larsga) -- #89
+* Added exception for "415 Unsupported Media" (tx JirkaChadima) -- #145
+* Added GroupedDropdown to support `<optgroup>` tag (tx jzellman) -- #152
+* Fixed failure in embedded interpreter - #87
+* Optimized web.cookies (tx benhoyt) - #148 
+
+-------------------------------------------------------------------

Old:
----
  web.py-0.36.tar.gz

New:
----
  web.py-0.37.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-web.py.spec ++++++
--- /var/tmp/diff_new_pack.paMyRr/_old  2012-07-02 11:07:50.000000000 +0200
+++ /var/tmp/diff_new_pack.paMyRr/_new  2012-07-02 11:07:50.000000000 +0200
@@ -11,12 +11,13 @@
 # case the license is the MIT License). An "Open Source License" is a
 # license that conforms to the Open Source Definition (Version 1.9)
 # published by the Open Source Initiative.
-#
+
 # Please submit bugfixes or comments via http://bugs.opensuse.org/
 #
 
+
 Name:           python-web.py
-Version:        0.36
+Version:        0.37
 Release:        0
 Url:            http://webpy.org/
 Summary:        web.py: makes web apps

++++++ web.py-0.36.tar.gz -> web.py-0.37.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/web.py-0.36/PKG-INFO new/web.py-0.37/PKG-INFO
--- old/web.py-0.36/PKG-INFO    2011-07-04 12:11:27.000000000 +0200
+++ new/web.py-0.37/PKG-INFO    2012-06-26 07:21:31.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.0
 Name: web.py
-Version: 0.36
+Version: 0.37
 Summary: web.py: makes web apps
 Home-page:  http://webpy.org/
 Author: Anand Chitipothu
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/web.py-0.36/web/__init__.py 
new/web.py-0.37/web/__init__.py
--- old/web.py-0.36/web/__init__.py     2011-07-04 12:10:39.000000000 +0200
+++ new/web.py-0.37/web/__init__.py     2012-06-26 07:19:55.000000000 +0200
@@ -3,7 +3,7 @@
 
 from __future__ import generators
 
-__version__ = "0.36"
+__version__ = "0.37"
 __author__ = [
     "Aaron Swartz <m...@aaronsw.com>",
     "Anand Chitipothu <anandol...@gmail.com>"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/web.py-0.36/web/application.py 
new/web.py-0.37/web/application.py
--- old/web.py-0.36/web/application.py  2011-07-04 12:10:39.000000000 +0200
+++ new/web.py-0.37/web/application.py  2012-06-26 07:19:55.000000000 +0200
@@ -5,6 +5,8 @@
 import webapi as web
 import webapi, wsgi, utils
 import debugerror
+import httpserver
+
 from utils import lstrips, safeunicode
 import sys
 
@@ -201,7 +203,7 @@
         if 'HTTP_CONTENT_TYPE' in env:
             env['CONTENT_TYPE'] = env.pop('HTTP_CONTENT_TYPE')
 
-        if method in ["POST", "PUT"]:
+        if method not in ["HEAD", "GET"]:
             data = data or ''
             import StringIO
             if isinstance(data, dict):
@@ -309,6 +311,13 @@
         function.
         """
         return wsgi.runwsgi(self.wsgifunc(*middleware))
+
+    def stop(self):
+        """Stops the http server started by run.
+        """
+        if httpserver.server:
+            httpserver.server.stop()
+            httpserver.server = None
     
     def cgirun(self, *middleware):
         """
@@ -366,8 +375,10 @@
         ctx.fullpath = ctx.path + ctx.query
         
         for k, v in ctx.iteritems():
+            # convert all string values to unicode values and replace 
+            # malformed data with a suitable replacement marker.
             if isinstance(v, str):
-                ctx[k] = safeunicode(v)
+                ctx[k] = v.decode('utf-8', 'replace') 
 
         # status must always be str
         ctx.status = '200 OK'
@@ -636,9 +647,12 @@
     if so, reloads them.
     """
 
-    SUFFIX = '$py.class' if sys.platform.startswith('java') else '.pyc'
     """File suffix of compiled modules."""
-
+    if sys.platform.startswith('java'):
+        SUFFIX = '$py.class'
+    else:
+        SUFFIX = '.pyc'
+    
     def __init__(self):
         self.mtimes = {}
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/web.py-0.36/web/db.py new/web.py-0.37/web/db.py
--- old/web.py-0.36/web/db.py   2011-07-04 12:10:39.000000000 +0200
+++ new/web.py-0.37/web/db.py   2012-06-26 07:19:55.000000000 +0200
@@ -1019,6 +1019,7 @@
 
         self.paramstyle = db.paramstyle
         keywords['database'] = keywords.pop('db')
+        keywords['pooling'] = False # sqlite don't allows connections to be 
shared by threads
         self.dbname = "sqlite"        
         DB.__init__(self, db, keywords)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/web.py-0.36/web/debugerror.py 
new/web.py-0.37/web/debugerror.py
--- old/web.py-0.36/web/debugerror.py   2011-07-04 12:10:39.000000000 +0200
+++ new/web.py-0.37/web/debugerror.py   2012-06-26 07:19:55.000000000 +0200
@@ -217,7 +217,7 @@
 <div id="explanation">
   <p>
     You're seeing this error because you have <code>web.config.debug</code>
-    set to <code>True</code>. Set that to <code>False</code> if you don't to 
see this.
+    set to <code>True</code>. Set that to <code>False</code> if you don't want 
to see this.
   </p>
 </div>
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/web.py-0.36/web/form.py new/web.py-0.37/web/form.py
--- old/web.py-0.36/web/form.py 2011-07-04 12:10:39.000000000 +0200
+++ new/web.py-0.37/web/form.py 2012-06-26 07:19:55.000000000 +0200
@@ -8,8 +8,15 @@
 import utils, net
 
 def attrget(obj, attr, value=None):
-    if hasattr(obj, 'has_key') and obj.has_key(attr): return obj[attr]
-    if hasattr(obj, attr): return getattr(obj, attr)
+    try:
+        if hasattr(obj, 'has_key') and obj.has_key(attr): 
+            return obj[attr]
+    except TypeError:
+        # Handle the case where has_key takes different number of arguments.
+        # This is the case with Model objects on appengine. See #134
+        pass
+    if hasattr(obj, attr):
+        return getattr(obj, attr)
     return value
 
 class Form(object):
@@ -18,7 +25,7 @@
     
         >>> f = Form(Textbox("x"))
         >>> f.render()
-        '<table>\n    <tr><th><label for="x">x</label></th><td><input 
type="text" id="x" name="x"/></td></tr>\n</table>'
+        u'<table>\n    <tr><th><label for="x">x</label></th><td><input 
type="text" id="x" name="x"/></td></tr>\n</table>'
     """
     def __init__(self, *inputs, **kw):
         self.inputs = inputs
@@ -187,9 +194,9 @@
     """Textbox input.
     
         >>> Textbox(name='foo', value='bar').render()
-        '<input type="text" id="foo" value="bar" name="foo"/>'
+        u'<input type="text" id="foo" value="bar" name="foo"/>'
         >>> Textbox(name='foo', value=0).render()
-        '<input type="text" id="foo" value="0" name="foo"/>'
+        u'<input type="text" id="foo" value="0" name="foo"/>'
     """        
     def get_type(self):
         return 'text'
@@ -198,7 +205,7 @@
     """Password input.
 
         >>> Password(name='password', value='secret').render()
-        '<input type="password" id="password" value="secret" name="password"/>'
+        u'<input type="password" id="password" value="secret" 
name="password"/>'
     """
     
     def get_type(self):
@@ -208,7 +215,7 @@
     """Textarea input.
     
         >>> Textarea(name='foo', value='bar').render()
-        '<textarea id="foo" name="foo">bar</textarea>'
+        u'<textarea id="foo" name="foo">bar</textarea>'
     """
     def render(self):
         attrs = self.attrs.copy()
@@ -220,9 +227,9 @@
     r"""Dropdown/select input.
     
         >>> Dropdown(name='foo', args=['a', 'b', 'c'], value='b').render()
-        '<select id="foo" name="foo">\n  <option value="a">a</option>\n  
<option selected="selected" value="b">b</option>\n  <option 
value="c">c</option>\n</select>\n'
+        u'<select id="foo" name="foo">\n  <option value="a">a</option>\n  
<option selected="selected" value="b">b</option>\n  <option 
value="c">c</option>\n</select>\n'
         >>> Dropdown(name='foo', args=[('a', 'aa'), ('b', 'bb'), ('c', 'cc')], 
value='b').render()
-        '<select id="foo" name="foo">\n  <option value="a">aa</option>\n  
<option selected="selected" value="b">bb</option>\n  <option 
value="c">cc</option>\n</select>\n'
+        u'<select id="foo" name="foo">\n  <option value="a">aa</option>\n  
<option selected="selected" value="b">bb</option>\n  <option 
value="c">cc</option>\n</select>\n'
     """
     def __init__(self, name, args, *validators, **attrs):
         self.args = args
@@ -235,15 +242,48 @@
         x = '<select %s>\n' % attrs
         
         for arg in self.args:
-            if isinstance(arg, (tuple, list)):
-                value, desc= arg
-            else:
-                value, desc = arg, arg 
+            x += self._render_option(arg)
+
+        x += '</select>\n'
+        return x
+
+    def _render_option(self, arg, indent='  '):
+        if isinstance(arg, (tuple, list)):
+            value, desc= arg
+        else:
+            value, desc = arg, arg 
+
+        if self.value == value or (isinstance(self.value, list) and value in 
self.value):
+            select_p = ' selected="selected"'
+        else:
+            select_p = ''
+        return indent + '<option%s value="%s">%s</option>\n' % (select_p, 
net.websafe(value), net.websafe(desc))
+        
+
+class GroupedDropdown(Dropdown):
+    r"""Grouped Dropdown/select input.
+    
+        >>> GroupedDropdown(name='car_type', args=(('Swedish Cars', ('Volvo', 
'Saab')), ('German Cars', ('Mercedes', 'Audi'))), value='Audi').render()
+        u'<select id="car_type" name="car_type">\n  <optgroup label="Swedish 
Cars">\n    <option value="Volvo">Volvo</option>\n    <option 
value="Saab">Saab</option>\n  </optgroup>\n  <optgroup label="German Cars">\n   
 <option value="Mercedes">Mercedes</option>\n    <option selected="selected" 
value="Audi">Audi</option>\n  </optgroup>\n</select>\n'
+        >>> GroupedDropdown(name='car_type', args=(('Swedish Cars', (('v', 
'Volvo'), ('s', 'Saab'))), ('German Cars', (('m', 'Mercedes'), ('a', 
'Audi')))), value='a').render()
+        u'<select id="car_type" name="car_type">\n  <optgroup label="Swedish 
Cars">\n    <option value="v">Volvo</option>\n    <option 
value="s">Saab</option>\n  </optgroup>\n  <optgroup label="German Cars">\n    
<option value="m">Mercedes</option>\n    <option selected="selected" 
value="a">Audi</option>\n  </optgroup>\n</select>\n'
 
-            if self.value == value or (isinstance(self.value, list) and value 
in self.value):
-                select_p = ' selected="selected"'
-            else: select_p = ''
-            x += '  <option%s value="%s">%s</option>\n' % (select_p, 
net.websafe(value), net.websafe(desc))
+    """
+    def __init__(self, name, args, *validators, **attrs):
+        self.args = args
+        super(Dropdown, self).__init__(name, *validators, **attrs)
+
+    def render(self):
+        attrs = self.attrs.copy()
+        attrs['name'] = self.name
+        
+        x = '<select %s>\n' % attrs
+        
+        for label, options in self.args:
+            x += '  <optgroup label="%s">\n' % net.websafe(label)
+            for arg in options:
+                x += self._render_option(arg, indent = '    ')
+            x +=  '  </optgroup>\n'
             
         x += '</select>\n'
         return x
@@ -274,14 +314,14 @@
     """Checkbox input.
 
     >>> Checkbox('foo', value='bar', checked=True).render()
-    '<input checked="checked" type="checkbox" id="foo_bar" value="bar" 
name="foo"/>'
+    u'<input checked="checked" type="checkbox" id="foo_bar" value="bar" 
name="foo"/>'
     >>> Checkbox('foo', value='bar').render()
-    '<input type="checkbox" id="foo_bar" value="bar" name="foo"/>'
+    u'<input type="checkbox" id="foo_bar" value="bar" name="foo"/>'
     >>> c = Checkbox('foo', value='bar')
     >>> c.validate('on')
     True
     >>> c.render()
-    '<input checked="checked" type="checkbox" id="foo_bar" value="bar" 
name="foo"/>'
+    u'<input checked="checked" type="checkbox" id="foo_bar" value="bar" 
name="foo"/>'
     """
     def __init__(self, name, *validators, **attrs):
         self.checked = attrs.pop('checked', False)
@@ -311,9 +351,9 @@
     """HTML Button.
     
     >>> Button("save").render()
-    '<button id="save" name="save">save</button>'
+    u'<button id="save" name="save">save</button>'
     >>> Button("action", value="save", html="<b>Save Changes</b>").render()
-    '<button id="action" value="save" name="action"><b>Save 
Changes</b></button>'
+    u'<button id="action" value="save" name="action"><b>Save 
Changes</b></button>'
     """
     def __init__(self, name, *validators, **attrs):
         super(Button, self).__init__(name, *validators, **attrs)
@@ -331,7 +371,7 @@
     """Hidden Input.
     
         >>> Hidden(name='foo', value='bar').render()
-        '<input type="hidden" id="foo" value="bar" name="foo"/>'
+        u'<input type="hidden" id="foo" value="bar" name="foo"/>'
     """
     def is_hidden(self):
         return True
@@ -343,7 +383,7 @@
     """File input.
     
         >>> File(name='f').render()
-        '<input type="file" id="f" name="f"/>'
+        u'<input type="file" id="f" name="f"/>'
     """
     def get_type(self):
         return 'file'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/web.py-0.36/web/http.py new/web.py-0.37/web/http.py
--- old/web.py-0.36/web/http.py 2011-07-04 12:10:39.000000000 +0200
+++ new/web.py-0.37/web/http.py 2012-06-26 07:19:55.000000000 +0200
@@ -122,7 +122,7 @@
 
 def url(path=None, doseq=False, **kw):
     """
-    Makes url by concatinating web.ctx.homepath and path and the 
+    Makes url by concatenating web.ctx.homepath and path and the 
     query string created using the arguments.
     """
     if path is None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/web.py-0.36/web/httpserver.py 
new/web.py-0.37/web/httpserver.py
--- old/web.py-0.36/web/httpserver.py   2011-07-04 12:10:40.000000000 +0200
+++ new/web.py-0.37/web/httpserver.py   2012-06-26 07:19:55.000000000 +0200
@@ -131,6 +131,10 @@
     print "http://%s:%d/"; % server_address
     WSGIServer(func, server_address).serve_forever()
 
+# The WSGIServer instance. 
+# Made global so that it can be stopped in embedded mode.
+server = None
+
 def runsimple(func, server_address=("0.0.0.0", 8080)):
     """
     Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. 
@@ -138,16 +142,22 @@
 
     [cp]: http://www.cherrypy.org
     """
+    global server
     func = StaticMiddleware(func)
     func = LogMiddleware(func)
     
     server = WSGIServer(server_address, func)
 
-    print "http://%s:%d/"; % server_address
+    if server.ssl_adapter:
+        print "https://%s:%d/"; % server_address
+    else:
+        print "http://%s:%d/"; % server_address
+
     try:
         server.start()
-    except KeyboardInterrupt:
+    except (KeyboardInterrupt, SystemExit):
         server.stop()
+        server = None
 
 def WSGIServer(server_address, wsgi_app):
     """Creates CherryPy WSGI server listening at `server_address` to serve 
`wsgi_app`.
@@ -155,7 +165,7 @@
     """
     import wsgiserver
     
-    # Default values of wsgiserver.ssl_adapters uses cheerypy.wsgiserver
+    # Default values of wsgiserver.ssl_adapters uses cherrypy.wsgiserver
     # prefix. Overwriting it make it work with web.wsgiserver.
     wsgiserver.ssl_adapters = {
         'builtin': 'web.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
@@ -163,6 +173,32 @@
     }
     
     server = wsgiserver.CherryPyWSGIServer(server_address, wsgi_app, 
server_name="localhost")
+        
+    def create_ssl_adapter(cert, key):
+        # wsgiserver tries to import submodules as cherrypy.wsgiserver.foo.
+        # That doesn't work as not it is web.wsgiserver. 
+        # Patching sys.modules temporarily to make it work.
+        import types
+        cherrypy = types.ModuleType('cherrypy')
+        cherrypy.wsgiserver = wsgiserver
+        sys.modules['cherrypy'] = cherrypy
+        sys.modules['cherrypy.wsgiserver'] = wsgiserver
+        
+        from wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
+        adapter = pyOpenSSLAdapter(cert, key)
+        
+        # We are done with our work. Cleanup the patches.
+        del sys.modules['cherrypy']
+        del sys.modules['cherrypy.wsgiserver']
+
+        return adapter
+
+    # SSL backward compatibility
+    if (server.ssl_adapter is None and
+        getattr(server, 'ssl_certificate', None) and
+        getattr(server, 'ssl_private_key', None)):
+        server.ssl_adapter = create_ssl_adapter(server.ssl_certificate, 
server.ssl_private_key)
+
     server.nodelay = not sys.platform.startswith('java') # TCP_NODELAY isn't 
supported on the JVM
     return server
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/web.py-0.36/web/session.py 
new/web.py-0.37/web/session.py
--- old/web.py-0.36/web/session.py      2011-07-04 12:10:40.000000000 +0200
+++ new/web.py-0.37/web/session.py      2012-06-26 07:19:55.000000000 +0200
@@ -5,6 +5,7 @@
 
 import os, time, datetime, random, base64
 import os.path
+from copy import deepcopy
 try:
     import cPickle as pickle
 except ImportError:
@@ -27,6 +28,7 @@
 web.config.session_parameters = utils.storage({
     'cookie_name': 'webpy_session_id',
     'cookie_domain': None,
+    'cookie_path' : None,
     'timeout': 86400, #24 * 60 * 60, # 24 hours in seconds
     'ignore_expiry': True,
     'ignore_change_ip': True,
@@ -91,6 +93,7 @@
         """Load the session from the store, by the id from cookie"""
         cookie_name = self._config.cookie_name
         cookie_domain = self._config.cookie_domain
+        cookie_path = self._config.cookie_path
         httponly = self._config.httponly
         self.session_id = web.cookies().get(cookie_name)
 
@@ -109,7 +112,7 @@
 
             if self._initializer:
                 if isinstance(self._initializer, dict):
-                    self.update(self._initializer)
+                    self.update(deepcopy(self._initializer))
                 elif hasattr(self._initializer, '__call__'):
                     self._initializer()
  
@@ -139,9 +142,10 @@
     def _setcookie(self, session_id, expires='', **kw):
         cookie_name = self._config.cookie_name
         cookie_domain = self._config.cookie_domain
+        cookie_path = self._config.cookie_path
         httponly = self._config.httponly
         secure = self._config.secure
-        web.setcookie(cookie_name, session_id, expires=expires, 
domain=cookie_domain, httponly=httponly, secure=secure)
+        web.setcookie(cookie_name, session_id, expires=expires, 
domain=cookie_domain, httponly=httponly, secure=secure, path=cookie_path)
     
     def _generate_session_id(self):
         """Generate a random id for session"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/web.py-0.36/web/template.py 
new/web.py-0.37/web/template.py
--- old/web.py-0.36/web/template.py     2011-07-04 12:10:40.000000000 +0200
+++ new/web.py-0.37/web/template.py     2012-06-26 07:19:55.000000000 +0200
@@ -41,6 +41,7 @@
 import glob
 import re
 from UserDict import DictMixin
+import warnings
 
 from utils import storage, safeunicode, safestr, re_compile
 from webapi import config
@@ -920,11 +921,13 @@
         
         # make sure code is safe - but not with jython, it doesn't have a 
working compiler module
         if not sys.platform.startswith('java'):
-            import compiler
-            ast = compiler.parse(code)
-            SafeVisitor().walk(ast, filename)
+            try:
+                import compiler
+                ast = compiler.parse(code)
+                SafeVisitor().walk(ast, filename)
+            except ImportError:
+                warnings.warn("Unabled to import compiler module. Unable to 
check templates for safety.")
         else:
-            import warnings
             warnings.warn("SECURITY ISSUE: You are using Jython, which does 
not support checking templates for safety. Your templates can execute arbitrary 
code.")
 
         return compiled_code
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/web.py-0.36/web/utils.py new/web.py-0.37/web/utils.py
--- old/web.py-0.36/web/utils.py        2011-07-04 12:10:40.000000000 +0200
+++ new/web.py-0.37/web/utils.py        2012-06-26 07:19:55.000000000 +0200
@@ -131,8 +131,14 @@
         <Storage {'x': u'a'}>
     """
     _unicode = defaults.pop('_unicode', False)
+
+    # if _unicode is callable object, use it convert a string to unicode.
+    to_unicode = safeunicode
+    if _unicode is not False and hasattr(_unicode, "__call__"):
+        to_unicode = _unicode
+    
     def unicodify(s):
-        if _unicode and isinstance(s, str): return safeunicode(s)
+        if _unicode and isinstance(s, str): return to_unicode(s)
         else: return s
         
     def getvalue(x):
@@ -715,7 +721,7 @@
     f = file(filename + '.tmp', 'w')
     f.write(content)
     f.close()
-    os.rename(f.name, path)
+    os.rename(f.name, filename)
 
 def dictreverse(mapping):
     """
@@ -905,7 +911,12 @@
         if abs(deltadays) < 4:
             return agohence(deltadays, 'day')
 
-        out = then.strftime('%B %e') # e.g. 'June 13'
+        try:
+            out = then.strftime('%B %e') # e.g. 'June  3'
+        except ValueError:
+            # %e doesn't work on Windows.
+            out = then.strftime('%B %d') # e.g. 'June 03'
+
         if then.year != now.year or deltadays < 0:
             out += ', %s' % then.year
         return out
@@ -1199,7 +1210,7 @@
     def clear_all():
         """Clears all ThreadedDict instances.
         """
-        for t in ThreadedDict._instances:
+        for t in list(ThreadedDict._instances):
             t.clear()
     clear_all = staticmethod(clear_all)
     
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/web.py-0.36/web/webapi.py 
new/web.py-0.37/web/webapi.py
--- old/web.py-0.36/web/webapi.py       2011-07-04 12:10:40.000000000 +0200
+++ new/web.py-0.37/web/webapi.py       2012-06-26 07:19:55.000000000 +0200
@@ -19,9 +19,9 @@
     "Redirect", "Found", "SeeOther", "NotModified", "TempRedirect", 
     "redirect", "found", "seeother", "notmodified", "tempredirect",
 
-    # 400, 401, 403, 404, 405, 406, 409, 410, 412
-    "BadRequest", "Unauthorized", "Forbidden", "NotFound", "NoMethod", 
"NotAcceptable", "Conflict", "Gone", "PreconditionFailed",
-    "badrequest", "unauthorized", "forbidden", "notfound", "nomethod", 
"notacceptable", "conflict", "gone", "preconditionfailed",
+    # 400, 401, 403, 404, 405, 406, 409, 410, 412, 415
+    "BadRequest", "Unauthorized", "Forbidden", "NotFound", "NoMethod", 
"NotAcceptable", "Conflict", "Gone", "PreconditionFailed", 
"UnsupportedMediaType",
+    "badrequest", "unauthorized", "forbidden", "notfound", "nomethod", 
"notacceptable", "conflict", "gone", "preconditionfailed", 
"unsupportedmediatype",
 
     # 500
     "InternalError", 
@@ -122,13 +122,33 @@
 class BadRequest(HTTPError):
     """`400 Bad Request` error."""
     message = "bad request"
-    def __init__(self):
+    def __init__(self, message=None):
         status = "400 Bad Request"
         headers = {'Content-Type': 'text/html'}
-        HTTPError.__init__(self, status, headers, self.message)
+        HTTPError.__init__(self, status, headers, message or self.message)
 
 badrequest = BadRequest
 
+class Unauthorized(HTTPError):
+    """`401 Unauthorized` error."""
+    message = "unauthorized"
+    def __init__(self):
+        status = "401 Unauthorized"
+        headers = {'Content-Type': 'text/html'}
+        HTTPError.__init__(self, status, headers, self.message)
+
+unauthorized = Unauthorized
+
+class Forbidden(HTTPError):
+    """`403 Forbidden` error."""
+    message = "forbidden"
+    def __init__(self):
+        status = "403 Forbidden"
+        headers = {'Content-Type': 'text/html'}
+        HTTPError.__init__(self, status, headers, self.message)
+
+forbidden = Forbidden
+
 class _NotFound(HTTPError):
     """`404 Not Found` error."""
     message = "not found"
@@ -149,12 +169,6 @@
 
 notfound = NotFound
 
-unauthorized = Unauthorized = _status_code("401 Unauthorized")
-forbidden = Forbidden = _status_code("403 Forbidden")
-notacceptable = NotAcceptable = _status_code("406 Not Acceptable")
-conflict = Conflict = _status_code("409 Conflict")
-preconditionfailed = PreconditionFailed = _status_code("412 Precondition 
Failed")
-
 class NoMethod(HTTPError):
     """A `405 Method Not Allowed` error."""
     def __init__(self, cls=None):
@@ -172,6 +186,26 @@
         
 nomethod = NoMethod
 
+class NotAcceptable(HTTPError):
+    """`406 Not Acceptable` error."""
+    message = "not acceptable"
+    def __init__(self):
+        status = "406 Not Acceptable"
+        headers = {'Content-Type': 'text/html'}
+        HTTPError.__init__(self, status, headers, self.message)
+
+notacceptable = NotAcceptable
+
+class Conflict(HTTPError):
+    """`409 Conflict` error."""
+    message = "conflict"
+    def __init__(self):
+        status = "409 Conflict"
+        headers = {'Content-Type': 'text/html'}
+        HTTPError.__init__(self, status, headers, self.message)
+
+conflict = Conflict
+
 class Gone(HTTPError):
     """`410 Gone` error."""
     message = "gone"
@@ -182,6 +216,26 @@
 
 gone = Gone
 
+class PreconditionFailed(HTTPError):
+    """`412 Precondition Failed` error."""
+    message = "precondition failed"
+    def __init__(self):
+        status = "412 Precondition Failed"
+        headers = {'Content-Type': 'text/html'}
+        HTTPError.__init__(self, status, headers, self.message)
+
+preconditionfailed = PreconditionFailed
+
+class UnsupportedMediaType(HTTPError):
+    """`415 Unsupported Media Type` error."""
+    message = "unsupported media type"
+    def __init__(self):
+        status = "415 Unsupported Media Type"
+        headers = {'Content-Type': 'text/html'}
+        HTTPError.__init__(self, status, headers, self.message)
+
+unsupportedmediatype = UnsupportedMediaType
+
 class _InternalError(HTTPError):
     """500 Internal Server Error`."""
     message = "internal server error"
@@ -305,19 +359,101 @@
     if httponly:
         value += '; httponly'
     header('Set-Cookie', value)
+        
+def decode_cookie(value):
+    r"""Safely decodes a cookie value to unicode. 
+    
+    Tries us-ascii, utf-8 and io8859 encodings, in that order.
 
-def cookies(*requireds, **defaults):
+    >>> decode_cookie('')
+    u''
+    >>> decode_cookie('asdf')
+    u'asdf'
+    >>> decode_cookie('foo \xC3\xA9 bar')
+    u'foo \xe9 bar'
+    >>> decode_cookie('foo \xE9 bar')
+    u'foo \xe9 bar'
     """
-    Returns a `storage` object with all the cookies in it.
+    try:
+        # First try plain ASCII encoding
+        return unicode(value, 'us-ascii')
+    except UnicodeError:
+        # Then try UTF-8, and if that fails, ISO8859
+        try:
+            return unicode(value, 'utf-8')
+        except UnicodeError:
+            return unicode(value, 'iso8859', 'ignore')
+
+def parse_cookies(http_cookie):
+    r"""Parse a HTTP_COOKIE header and return dict of cookie names and decoded 
values.
+        
+    >>> sorted(parse_cookies('').items())
+    []
+    >>> sorted(parse_cookies('a=1').items())
+    [('a', '1')]
+    >>> sorted(parse_cookies('a=1%202').items())
+    [('a', '1 2')]
+    >>> sorted(parse_cookies('a=Z%C3%A9Z').items())
+    [('a', 'Z\xc3\xa9Z')]
+    >>> sorted(parse_cookies('a=1; b=2; c=3').items())
+    [('a', '1'), ('b', '2'), ('c', '3')]
+    >>> sorted(parse_cookies('a=1; b=w("x")|y=z; c=3').items())
+    [('a', '1'), ('b', 'w('), ('c', '3')]
+    >>> sorted(parse_cookies('a=1; b=w(%22x%22)|y=z; c=3').items())
+    [('a', '1'), ('b', 'w("x")|y=z'), ('c', '3')]
+
+    >>> sorted(parse_cookies('keebler=E=mc2').items())
+    [('keebler', 'E=mc2')]
+    >>> sorted(parse_cookies(r'keebler="E=mc2; L=\"Loves\"; 
fudge=\012;"').items())
+    [('keebler', 'E=mc2; L="Loves"; fudge=\n;')]
+    """
+    #print "parse_cookies"
+    if '"' in http_cookie:
+        # HTTP_COOKIE has quotes in it, use slow but correct cookie parsing
+        cookie = Cookie.SimpleCookie()
+        try:
+            cookie.load(http_cookie)
+        except Cookie.CookieError:
+            # If HTTP_COOKIE header is malformed, try at least to load the 
cookies we can by
+            # first splitting on ';' and loading each attr=value pair 
separately
+            cookie = Cookie.SimpleCookie()
+            for attr_value in http_cookie.split(';'):
+                try:
+                    cookie.load(attr_value)
+                except Cookie.CookieError:
+                    pass
+        cookies = dict((k, urllib.unquote(v.value)) for k, v in 
cookie.iteritems())
+    else:
+        # HTTP_COOKIE doesn't have quotes, use fast cookie parsing
+        cookies = {}
+        for key_value in http_cookie.split(';'):
+            key_value = key_value.split('=', 1)
+            if len(key_value) == 2:
+                key, value = key_value
+                cookies[key.strip()] = urllib.unquote(value.strip())
+    return cookies
+
+def cookies(*requireds, **defaults):
+    r"""Returns a `storage` object with all the request cookies in it.
+    
     See `storify` for how `requireds` and `defaults` work.
+
+    This is forgiving on bad HTTP_COOKIE input, it tries to parse at least
+    the cookies it can.
+    
+    The values are converted to unicode if _unicode=True is passed.
     """
-    cookie = Cookie.SimpleCookie()
-    cookie.load(ctx.env.get('HTTP_COOKIE', ''))
+    # If _unicode=True is specified, use decode_cookie to convert cookie value 
to unicode 
+    if defaults.get("_unicode") is True:
+        defaults['_unicode'] = decode_cookie
+        
+    # parse cookie string and cache the result for next time.
+    if '_parsed_cookies' not in ctx:
+        http_cookie = ctx.env.get("HTTP_COOKIE", "")
+        ctx._parsed_cookies = parse_cookies(http_cookie)
+
     try:
-        d = storify(cookie, *requireds, **defaults)
-        for k, v in d.items():
-            d[k] = v and urllib.unquote(v)
-        return d
+        return storify(ctx._parsed_cookies, *requireds, **defaults)
     except KeyError:
         badrequest()
         raise StopIteration
@@ -383,3 +519,7 @@
 `output`
    : A string to be used as the response.
 """
+
+if __name__ == "__main__":
+    import doctest
+    doctest.testmod()
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/web.py-0.36/web/wsgi.py new/web.py-0.37/web/wsgi.py
--- old/web.py-0.36/web/wsgi.py 2011-07-04 12:10:40.000000000 +0200
+++ new/web.py-0.37/web/wsgi.py 2012-06-26 07:19:55.000000000 +0200
@@ -54,11 +54,15 @@
     return httpserver.runsimple(func, validip(listget(sys.argv, 1, '')))
     
 def _is_dev_mode():
+    # Some embedded python interpreters won't have sys.arv
+    # For details, see https://github.com/webpy/webpy/issues/87
+    argv = getattr(sys, "argv", [])
+
     # quick hack to check if the program is running in dev mode.
     if os.environ.has_key('SERVER_SOFTWARE') \
         or os.environ.has_key('PHP_FCGI_CHILDREN') \
-        or 'fcgi' in sys.argv or 'fastcgi' in sys.argv \
-        or 'mod_wsgi' in sys.argv:
+        or 'fcgi' in argv or 'fastcgi' in argv \
+        or 'mod_wsgi' in argv:
             return False
     return True
 

-- 
To unsubscribe, e-mail: opensuse-commit+unsubscr...@opensuse.org
For additional commands, e-mail: opensuse-commit+h...@opensuse.org

Reply via email to