Merged branch py3k (closes #2)
commit
3408919faa
|
@ -2,8 +2,8 @@
|
||||||
<?eclipse-pydev version="1.0"?>
|
<?eclipse-pydev version="1.0"?>
|
||||||
|
|
||||||
<pydev_project>
|
<pydev_project>
|
||||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">python3</pydev_property>
|
||||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
|
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 3.0</pydev_property>
|
||||||
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
|
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
|
||||||
<path>/Milla/src</path>
|
<path>/Milla/src</path>
|
||||||
</pydev_pathproperty>
|
</pydev_pathproperty>
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -15,7 +15,7 @@ if sys.version_info < (2, 7):
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='Milla',
|
name='Milla',
|
||||||
version='tip',
|
version='py3k',
|
||||||
description='Lightweight WSGI framework for web applications',
|
description='Lightweight WSGI framework for web applications',
|
||||||
long_description='''\
|
long_description='''\
|
||||||
Milla is a simple WSGI framework for Python web applications. It is mostly
|
Milla is a simple WSGI framework for Python web applications. It is mostly
|
||||||
|
|
|
@ -19,8 +19,13 @@ from milla.app import *
|
||||||
from milla.auth.decorators import *
|
from milla.auth.decorators import *
|
||||||
from webob.exc import *
|
from webob.exc import *
|
||||||
import webob
|
import webob
|
||||||
import urllib
|
try:
|
||||||
import urlparse
|
import urllib.parse
|
||||||
|
except ImportError: #pragma: no cover
|
||||||
|
import urllib
|
||||||
|
import urlparse
|
||||||
|
urllib.parse = urlparse
|
||||||
|
urllib.parse.urlencode = urllib.urlencode #@UndefinedVariable
|
||||||
|
|
||||||
def allow(*methods):
|
def allow(*methods):
|
||||||
'''Specify the allowed HTTP verbs for a controller callable
|
'''Specify the allowed HTTP verbs for a controller callable
|
||||||
|
@ -77,7 +82,7 @@ class Request(webob.Request):
|
||||||
url = self._merge_url(self.script_name, path)
|
url = self._merge_url(self.script_name, path)
|
||||||
|
|
||||||
if keywords:
|
if keywords:
|
||||||
url += '?' + urllib.urlencode(keywords)
|
url += '?' + urllib.parse.urlencode(keywords)
|
||||||
|
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
@ -97,7 +102,7 @@ class Request(webob.Request):
|
||||||
url = self._merge_url(self.application_url, path)
|
url = self._merge_url(self.application_url, path)
|
||||||
|
|
||||||
if keywords:
|
if keywords:
|
||||||
url += '?' + urllib.urlencode(keywords)
|
url += '?' + urllib.parse.urlencode(keywords)
|
||||||
|
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
@ -141,4 +146,4 @@ class Request(webob.Request):
|
||||||
if not root.endswith('/'):
|
if not root.endswith('/'):
|
||||||
root += '/'
|
root += '/'
|
||||||
|
|
||||||
return urlparse.urljoin(root, path)
|
return urllib.parse.urljoin(root, path)
|
||||||
|
|
|
@ -25,7 +25,6 @@ from milla.controllers import FaviconController
|
||||||
from milla.util import asbool
|
from milla.util import asbool
|
||||||
from webob.exc import HTTPNotFound, WSGIHTTPException, HTTPMethodNotAllowed
|
from webob.exc import HTTPNotFound, WSGIHTTPException, HTTPMethodNotAllowed
|
||||||
import milla.dispatch.traversal
|
import milla.dispatch.traversal
|
||||||
import webob
|
|
||||||
|
|
||||||
__all__ = ['Application']
|
__all__ = ['Application']
|
||||||
|
|
||||||
|
@ -93,51 +92,55 @@ class Application(object):
|
||||||
start_response_wrapper = StartResponseWrapper(start_response)
|
start_response_wrapper = StartResponseWrapper(start_response)
|
||||||
request.start_response = start_response_wrapper
|
request.start_response = start_response_wrapper
|
||||||
try:
|
try:
|
||||||
# If the callable has an __before__ attribute, call it
|
self._call_before(func)(request)
|
||||||
if hasattr(func, '__before__'):
|
|
||||||
func.__before__(request)
|
|
||||||
# If the callable is an instance method and its class has
|
|
||||||
# a __before__ method, call that
|
|
||||||
elif hasattr(func, 'im_self') and \
|
|
||||||
hasattr(func.im_self, '__before__'):
|
|
||||||
func.im_self.__before__(request)
|
|
||||||
# The callable might be a partial, so check the inner func
|
|
||||||
elif hasattr(func, 'func'):
|
|
||||||
if hasattr(func.func, '__before__'):
|
|
||||||
func.func.__before__(request)
|
|
||||||
elif hasattr(func.func, 'im_self') and \
|
|
||||||
hasattr(func.func.im_self, '__before__'):
|
|
||||||
func.func.im_self.__before__(request)
|
|
||||||
response = func(request)
|
response = func(request)
|
||||||
except WSGIHTTPException as e:
|
except WSGIHTTPException as e:
|
||||||
return e(environ, start_response)
|
return e(environ, start_response)
|
||||||
finally:
|
finally:
|
||||||
# If the callable has an __after__ method, call it
|
self._call_after(func)(request)
|
||||||
if hasattr(func, '__after__'):
|
|
||||||
func.__after__(request)
|
|
||||||
# If the callable is an instance method and its class has
|
|
||||||
# an __after__ method, call that
|
|
||||||
elif hasattr(func, 'im_self') and \
|
|
||||||
hasattr(func.im_self, '__after__'):
|
|
||||||
func.im_self.__after__(request)
|
|
||||||
# The callable might be a partial, so check the inner func
|
|
||||||
elif hasattr(func, 'func'):
|
|
||||||
if hasattr(func.func, '__after__'):
|
|
||||||
func.func.__after__(request)
|
|
||||||
elif hasattr(func.func, 'im_self') and \
|
|
||||||
hasattr(func.func.im_self, '__after__'):
|
|
||||||
func.func.im_self.__after__(request)
|
|
||||||
|
|
||||||
# The callable might have returned just a string, which is OK,
|
# The callable might have returned just a string, which is OK,
|
||||||
# but we need to wrap it in a WebOb response
|
# but we need to wrap it in a Response object
|
||||||
|
try:
|
||||||
|
# In Python 2, it could be a str or a unicode object
|
||||||
|
basestring = basestring #@UndefinedVariable
|
||||||
|
except NameError:
|
||||||
|
# Python 3 has no unicode objects and thus no need for
|
||||||
|
# basestring so we, just make it an alias for str
|
||||||
|
basestring = str
|
||||||
if isinstance(response, basestring) or not response:
|
if isinstance(response, basestring) or not response:
|
||||||
response = webob.Response(response)
|
response = request.ResponseClass(response)
|
||||||
|
|
||||||
if not start_response_wrapper.called:
|
if not start_response_wrapper.called:
|
||||||
start_response(response.status, response.headerlist)
|
start_response(response.status, response.headerlist)
|
||||||
if not environ['REQUEST_METHOD'] == 'HEAD':
|
if not environ['REQUEST_METHOD'] == 'HEAD':
|
||||||
return response.app_iter
|
return response.app_iter
|
||||||
|
|
||||||
|
def _call_after(self, func):
|
||||||
|
try:
|
||||||
|
return self._find_attr(func, '__after__')
|
||||||
|
except AttributeError:
|
||||||
|
return lambda r: None
|
||||||
|
|
||||||
|
def _call_before(self, func):
|
||||||
|
try:
|
||||||
|
return self._find_attr(func, '__before__')
|
||||||
|
except AttributeError:
|
||||||
|
return lambda r: None
|
||||||
|
|
||||||
|
def _find_attr(self, obj, attr):
|
||||||
|
try:
|
||||||
|
# Object has the specified attribute itself
|
||||||
|
return getattr(obj, attr)
|
||||||
|
except AttributeError:
|
||||||
|
# Object is a bound method; look for the attribute on the instance
|
||||||
|
if hasattr(obj, '__self__'):
|
||||||
|
return self._find_attr(obj.__self__, attr)
|
||||||
|
# Object is a partial; look for the attribute on the inner function
|
||||||
|
elif hasattr(obj, 'func'):
|
||||||
|
return self._find_attr(obj.func, attr)
|
||||||
|
raise
|
||||||
|
|
||||||
class StartResponseWrapper():
|
class StartResponseWrapper():
|
||||||
|
|
||||||
def __init__(self, start_response):
|
def __init__(self, start_response):
|
||||||
|
|
|
@ -37,7 +37,7 @@ class NotAuthorized(Exception):
|
||||||
All other arguments and keywords are ignored.
|
All other arguments and keywords are ignored.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
response = request.ResponseClass(unicode(self))
|
response = request.ResponseClass(str(self))
|
||||||
response.status_int = 403
|
response.status_int = 403
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
|
@ -124,7 +124,7 @@ class require_perms(object):
|
||||||
def __init__(self, *requirements):
|
def __init__(self, *requirements):
|
||||||
requirement = None
|
requirement = None
|
||||||
for req in requirements:
|
for req in requirements:
|
||||||
if isinstance(req, basestring):
|
if not hasattr(req, 'check'):
|
||||||
req = permissions.Permission(req)
|
req = permissions.Permission(req)
|
||||||
if not requirement:
|
if not requirement:
|
||||||
requirement = req
|
requirement = req
|
||||||
|
|
|
@ -86,17 +86,8 @@ class Permission(BasePermission):
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
if isinstance(self.name, unicode):
|
|
||||||
return self.name
|
|
||||||
else:
|
|
||||||
return self.name.decode('utf-8')
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if isinstance(self.name, str):
|
return str(self.name)
|
||||||
return self.name
|
|
||||||
else:
|
|
||||||
return self.name.encode('utf-8')
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self is other or str(self) == str(other)
|
return self is other or str(self) == str(other)
|
||||||
|
@ -119,7 +110,7 @@ class PermissionRequirement(BasePermission):
|
||||||
self.requirements = requirements
|
self.requirements = requirements
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return unicode(self).encode('utf-8')
|
return ', '.join(self.requirements)
|
||||||
|
|
||||||
class PermissionRequirementAll(PermissionRequirement):
|
class PermissionRequirementAll(PermissionRequirement):
|
||||||
'''Complex permission requirement needing all given permissions'''
|
'''Complex permission requirement needing all given permissions'''
|
||||||
|
|
|
@ -40,8 +40,14 @@ def read_config(filename):
|
||||||
app.config.update(config)
|
app.config.update(config)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
cparser = configparser.SafeConfigParser()
|
with open(filename) as f:
|
||||||
cparser.readfp(open(filename))
|
# ConfigParser API changed in Python 3.2
|
||||||
|
if hasattr(configparser.ConfigParser, 'read_file'):
|
||||||
|
cparser = configparser.ConfigParser()
|
||||||
|
cparser.read_file(f)
|
||||||
|
else:
|
||||||
|
cparser = configparser.SafeConfigParser()
|
||||||
|
cparser.readfp(f)
|
||||||
|
|
||||||
config = {}
|
config = {}
|
||||||
for section in cparser.sections():
|
for section in cparser.sections():
|
||||||
|
|
|
@ -200,7 +200,7 @@ class Router(object):
|
||||||
module ``some.module``.
|
module ``some.module``.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if isinstance(controller, basestring):
|
if not hasattr(controller, '__call__'):
|
||||||
controller = self._import_controller(controller)
|
controller = self._import_controller(controller)
|
||||||
self.routes.append((self._compile_template(template),
|
self.routes.append((self._compile_template(template),
|
||||||
controller, vars))
|
controller, vars))
|
||||||
|
|
|
@ -33,7 +33,7 @@ class ResponseMaker(object):
|
||||||
def __init__(self, http_version=1.1):
|
def __init__(self, http_version=1.1):
|
||||||
self.http_version = http_version
|
self.http_version = http_version
|
||||||
self.headers = ''
|
self.headers = ''
|
||||||
self.body = ''
|
self.body = b''
|
||||||
|
|
||||||
def start_response(self, status, response_headers):
|
def start_response(self, status, response_headers):
|
||||||
self.headers += 'HTTP/{0} {1}\r\n'.format(self.http_version, status)
|
self.headers += 'HTTP/{0} {1}\r\n'.format(self.http_version, status)
|
||||||
|
@ -78,7 +78,7 @@ def test_favicon():
|
||||||
app_iter = app(environ, response.start_response)
|
app_iter = app(environ, response.start_response)
|
||||||
response.finish_response(app_iter)
|
response.finish_response(app_iter)
|
||||||
assert response.headers.startswith('HTTP/1.1 200'), response.headers
|
assert response.headers.startswith('HTTP/1.1 200'), response.headers
|
||||||
assert response.body.startswith('\x00\x00\x01\x00'), response.body
|
assert response.body.startswith(b'\x00\x00\x01\x00'), response.body
|
||||||
|
|
||||||
def test_allow_header_disallowed():
|
def test_allow_header_disallowed():
|
||||||
'''HTTP 405 is returned for disallowed HTTP request methods
|
'''HTTP 405 is returned for disallowed HTTP request methods
|
||||||
|
@ -138,7 +138,7 @@ def test_emulated_method():
|
||||||
})
|
})
|
||||||
body = environ['wsgi.input']
|
body = environ['wsgi.input']
|
||||||
body.seek(0)
|
body.seek(0)
|
||||||
body.write('_method=PUT')
|
body.write(b'_method=PUT')
|
||||||
body.seek(0)
|
body.seek(0)
|
||||||
response = ResponseMaker()
|
response = ResponseMaker()
|
||||||
app_iter = app(environ, response.start_response)
|
app_iter = app(environ, response.start_response)
|
||||||
|
@ -358,7 +358,7 @@ def test_httperror_response():
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def controller(request):
|
def controller(request):
|
||||||
raise webob.exc.HTTPClientError()
|
raise webob.exc.HTTPClientError('NotFound')
|
||||||
|
|
||||||
app = milla.app.Application(StubResolver(controller))
|
app = milla.app.Application(StubResolver(controller))
|
||||||
environ = environ_for_testing()
|
environ = environ_for_testing()
|
||||||
|
@ -366,7 +366,7 @@ def test_httperror_response():
|
||||||
app_iter = app(environ, response.start_response)
|
app_iter = app(environ, response.start_response)
|
||||||
response.finish_response(app_iter)
|
response.finish_response(app_iter)
|
||||||
assert response.headers.startswith('HTTP/1.1 400'), response.headers
|
assert response.headers.startswith('HTTP/1.1 400'), response.headers
|
||||||
assert webob.exc.HTTPClientError.explanation in response.body, response.body
|
assert b'NotFound' in response.body, response.body
|
||||||
|
|
||||||
def test_single_start_response():
|
def test_single_start_response():
|
||||||
'''Ensure start_response is only called once'''
|
'''Ensure start_response is only called once'''
|
||||||
|
@ -393,7 +393,7 @@ def test_single_start_response():
|
||||||
response.finish_response(app_iter)
|
response.finish_response(app_iter)
|
||||||
assert start_response.call_count == 1, start_response.call_count
|
assert start_response.call_count == 1, start_response.call_count
|
||||||
assert response.headers.startswith('HTTP/1.1 200 OK'), response.headers
|
assert response.headers.startswith('HTTP/1.1 200 OK'), response.headers
|
||||||
assert response.body == 'test', response.body
|
assert response.body == b'test', response.body
|
||||||
|
|
||||||
def test_allow_decorator():
|
def test_allow_decorator():
|
||||||
'''Ensure allow decorator sets allowed_methods on controllers'''
|
'''Ensure allow decorator sets allowed_methods on controllers'''
|
||||||
|
|
Loading…
Reference in New Issue