1616from __future__ import with_statement
1717
1818__author__ = 'Marcel Hellkamp'
19- __version__ = '0.12.19 '
19+ __version__ = '0.12.25 '
2020__license__ = 'MIT'
2121
2222# The gevent server adapter needs to patch some modules before they are imported
4141from datetime import date as datedate , datetime , timedelta
4242from tempfile import TemporaryFile
4343from traceback import format_exc , print_exc
44- from inspect import getargspec
4544from unicodedata import normalize
4645
4746
@@ -93,6 +92,12 @@ def _e(): return sys.exc_info()[1]
9392 import pickle
9493 from io import BytesIO
9594 from configparser import ConfigParser
95+ from inspect import getfullargspec
96+ def getargspec (func ):
97+ spec = getfullargspec (func )
98+ kwargs = makelist (spec [0 ]) + makelist (spec .kwonlyargs )
99+ return kwargs , spec [1 ], spec [2 ], spec [3 ]
100+
96101 basestring = str
97102 unicode = str
98103 json_loads = lambda s : json_lds (touni (s ))
@@ -110,6 +115,7 @@ def _raise(*a): raise a[0](a[1]).with_traceback(a[2])
110115 from imp import new_module
111116 from StringIO import StringIO as BytesIO
112117 from ConfigParser import SafeConfigParser as ConfigParser
118+ from inspect import getargspec
113119 if py25 :
114120 msg = "Python 2.5 support may be dropped in future versions of Bottle."
115121 warnings .warn (msg , DeprecationWarning )
@@ -302,7 +308,7 @@ def add_filter(self, name, func):
302308 rule_syntax = re .compile ('(\\ \\ *)' \
303309 '(?:(?::([a-zA-Z_][a-zA-Z_0-9]*)?()(?:#(.*?)#)?)' \
304310 '|(?:<([a-zA-Z_][a-zA-Z_0-9]*)?(?::([a-zA-Z_]*)' \
305- '(?::((?:\\ \\ .|[^\\ \\ >]+ )+)?)?)?>))' )
311+ '(?::((?:\\ \\ .|[^\\ \\ >])+)?)?)?>))' )
306312
307313 def _itertokens (self , rule ):
308314 offset , prefix = 0 , ''
@@ -559,7 +565,7 @@ def get_callback_args(self):
559565 def get_config (self , key , default = None ):
560566 ''' Lookup a config field and return its value, first checking the
561567 route.config, then route.app.config.'''
562- for conf in (self .config , self .app .conifg ):
568+ for conf in (self .config , self .app .config ):
563569 if key in conf : return conf [key ]
564570 return default
565571
@@ -848,17 +854,19 @@ def default_error_handler(self, res):
848854 return tob (template (ERROR_PAGE_TEMPLATE , e = res ))
849855
850856 def _handle (self , environ ):
851- path = environ ['bottle.raw_path' ] = environ ['PATH_INFO' ]
852- if py3k :
853- try :
854- environ ['PATH_INFO' ] = path .encode ('latin1' ).decode ('utf8' )
855- except UnicodeError :
856- return HTTPError (400 , 'Invalid path string. Expected UTF-8' )
857-
858857 try :
858+
859859 environ ['bottle.app' ] = self
860860 request .bind (environ )
861861 response .bind ()
862+
863+ path = environ ['bottle.raw_path' ] = environ ['PATH_INFO' ]
864+ if py3k :
865+ try :
866+ environ ['PATH_INFO' ] = path .encode ('latin1' ).decode ('utf8' )
867+ except UnicodeError :
868+ return HTTPError (400 , 'Invalid path string. Expected UTF-8' )
869+
862870 try :
863871 self .trigger_hook ('before_request' )
864872 route , args = self .router .match (environ )
@@ -1087,6 +1095,7 @@ def forms(self):
10871095 :class:`FormsDict`. All keys and values are strings. File uploads
10881096 are stored separately in :attr:`files`. """
10891097 forms = FormsDict ()
1098+ forms .recode_unicode = self .POST .recode_unicode
10901099 for name , item in self .POST .allitems ():
10911100 if not isinstance (item , FileUpload ):
10921101 forms [name ] = item
@@ -1110,6 +1119,7 @@ def files(self):
11101119
11111120 """
11121121 files = FormsDict ()
1122+ files .recode_unicode = self .POST .recode_unicode
11131123 for name , item in self .POST .allitems ():
11141124 if isinstance (item , FileUpload ):
11151125 files [name ] = item
@@ -1235,15 +1245,16 @@ def POST(self):
12351245 newline = '\n ' )
12361246 elif py3k :
12371247 args ['encoding' ] = 'utf8'
1248+ post .recode_unicode = False
12381249 data = cgi .FieldStorage (** args )
12391250 self ['_cgi.FieldStorage' ] = data #http://bugs.python.org/issue18394#msg207958
12401251 data = data .list or []
12411252 for item in data :
1242- if item .filename :
1253+ if item .filename is None :
1254+ post [item .name ] = item .value
1255+ else :
12431256 post [item .name ] = FileUpload (item .file , item .name ,
12441257 item .filename , item .headers )
1245- else :
1246- post [item .name ] = item .value
12471258 return post
12481259
12491260 @property
@@ -1746,7 +1757,7 @@ def apply(self, callback, route):
17461757 def wrapper (* a , ** ka ):
17471758 try :
17481759 rv = callback (* a , ** ka )
1749- except HTTPError :
1760+ except HTTPResponse :
17501761 rv = _e ()
17511762
17521763 if isinstance (rv , dict ):
@@ -2689,6 +2700,7 @@ def auth_basic(check, realm="private", text="Access denied"):
26892700 ''' Callback decorator to require HTTP auth (basic).
26902701 TODO: Add route(check_auth=...) parameter. '''
26912702 def decorator (func ):
2703+ @functools .wraps (func )
26922704 def wrapper (* a , ** ka ):
26932705 user , password = request .auth or (None , None )
26942706 if user is None or not check (user , password ):
@@ -2792,7 +2804,11 @@ class server_cls(server_cls):
27922804
27932805class CherryPyServer (ServerAdapter ):
27942806 def run (self , handler ): # pragma: no cover
2795- from cherrypy import wsgiserver
2807+ depr ("The wsgi server part of cherrypy was split into a new "
2808+ "project called 'cheroot'. Use the 'cheroot' server "
2809+ "adapter instead of cherrypy." )
2810+ from cherrypy import wsgiserver # This will fail for CherryPy >= 9
2811+
27962812 self .options ['bind_addr' ] = (self .host , self .port )
27972813 self .options ['wsgi_app' ] = handler
27982814
@@ -2815,6 +2831,25 @@ def run(self, handler): # pragma: no cover
28152831 server .stop ()
28162832
28172833
2834+ class CherootServer (ServerAdapter ):
2835+ def run (self , handler ): # pragma: no cover
2836+ from cheroot import wsgi
2837+ from cheroot .ssl import builtin
2838+ self .options ['bind_addr' ] = (self .host , self .port )
2839+ self .options ['wsgi_app' ] = handler
2840+ certfile = self .options .pop ('certfile' , None )
2841+ keyfile = self .options .pop ('keyfile' , None )
2842+ chainfile = self .options .pop ('chainfile' , None )
2843+ server = wsgi .Server (** self .options )
2844+ if certfile and keyfile :
2845+ server .ssl_adapter = builtin .BuiltinSSLAdapter (
2846+ certfile , keyfile , chainfile )
2847+ try :
2848+ server .start ()
2849+ finally :
2850+ server .stop ()
2851+
2852+
28182853class WaitressServer (ServerAdapter ):
28192854 def run (self , handler ):
28202855 from waitress import serve
@@ -2838,7 +2873,7 @@ def run(self, handler):
28382873
28392874
28402875class FapwsServer (ServerAdapter ):
2841- """ Extremely fast webserver using libev. See http ://www.fapws.org/ """
2876+ """ Extremely fast webserver using libev. See https ://github.com/william-os4y/fapws3 """
28422877 def run (self , handler ): # pragma: no cover
28432878 import fapws ._evwsgi as evwsgi
28442879 from fapws import base , config
@@ -2982,7 +3017,9 @@ def run(self, handler):
29823017
29833018class AutoServer (ServerAdapter ):
29843019 """ Untested. """
2985- adapters = [WaitressServer , PasteServer , TwistedServer , CherryPyServer , WSGIRefServer ]
3020+ adapters = [WaitressServer , PasteServer , TwistedServer , CherryPyServer ,
3021+ CherootServer , WSGIRefServer ]
3022+
29863023 def run (self , handler ):
29873024 for sa in self .adapters :
29883025 try :
@@ -2996,6 +3033,7 @@ def run(self, handler):
29963033 'wsgiref' : WSGIRefServer ,
29973034 'waitress' : WaitressServer ,
29983035 'cherrypy' : CherryPyServer ,
3036+ 'cheroot' : CherootServer ,
29993037 'paste' : PasteServer ,
30003038 'fapws3' : FapwsServer ,
30013039 'tornado' : TornadoServer ,
@@ -3451,7 +3489,7 @@ class StplParser(object):
34513489 # Match the start tokens of code areas in a template
34523490 _re_split = '(?m)^[ \t ]*(\\ \\ ?)((%(line_start)s)|(%(block_start)s))(%%?)'
34533491 # Match inline statements (may contain python strings)
3454- _re_inl = '(?m)%%(inline_start)s((?:%s|[^\' "\n ]*?)+ )%%(inline_end)s' % _re_inl
3492+ _re_inl = '(?m)%%(inline_start)s((?:%s|[^\' "\n ])*? )%%(inline_end)s' % _re_inl
34553493 _re_tok = '(?m)' + _re_tok
34563494
34573495 default_syntax = '<% %> % {{ }}'
@@ -3653,7 +3691,7 @@ def wrapper(*args, **kwargs):
36533691 tplvars .update (result )
36543692 return template (tpl_name , ** tplvars )
36553693 elif result is None :
3656- return template (tpl_name , defaults )
3694+ return template (tpl_name , ** defaults )
36573695 return result
36583696 return wrapper
36593697 return decorator
0 commit comments