From 6c034b88d3efa8734cf5af63d831e1691f3a0630 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Sat, 3 Nov 2012 11:07:30 -0500 Subject: [PATCH 01/12] py3k: Fix imports --HG-- branch : py3k --- src/milla/__init__.py | 4 ++-- src/milla/dispatch/routing.py | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/milla/__init__.py b/src/milla/__init__.py index c391afb..2b431c0 100644 --- a/src/milla/__init__.py +++ b/src/milla/__init__.py @@ -15,11 +15,11 @@ ''' -from app import * +from milla.app import * +from milla.auth.decorators import * from webob.exc import * from webob.request import * from webob.response import * -from auth.decorators import * def allow(*methods): '''Specify the allowed HTTP verbs for a controller callable diff --git a/src/milla/dispatch/routing.py b/src/milla/dispatch/routing.py index 0ef4e6f..c350e49 100644 --- a/src/milla/dispatch/routing.py +++ b/src/milla/dispatch/routing.py @@ -25,7 +25,11 @@ import milla import re import sys import urllib -import urlparse +try: + import urllib.parse +except ImportError: + import urlparse + urllib.parse = urlparse class Router(object): '''A dispatcher that maps arbitrary paths to controller callables @@ -236,7 +240,7 @@ class Generator(object): url = self.request.relative_url(path, to_application=True) if self.path_only: - split = urlparse.urlsplit(url) + split = urllib.parse.urlsplit(url) url = split.path if vars: url += '?' + urllib.urlencode(vars) From fa97bd7b0335dbb8274dab81958b6ad8ddc2f724 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Sat, 3 Nov 2012 11:26:01 -0500 Subject: [PATCH 02/12] py3k: Handle non-callable controllers in routes better --HG-- branch : py3k --- src/milla/dispatch/routing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/milla/dispatch/routing.py b/src/milla/dispatch/routing.py index c350e49..ff4702f 100644 --- a/src/milla/dispatch/routing.py +++ b/src/milla/dispatch/routing.py @@ -205,7 +205,7 @@ class Router(object): module ``some.module``. ''' - if isinstance(controller, basestring): + if not hasattr(controller, '__call__'): controller = self._import_controller(controller) self.routes.append((self._compile_template(template), controller, vars)) From 17e74867842ef8683aeeed9985220a5f1ba9dede Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Mon, 19 Nov 2012 22:34:44 -0600 Subject: [PATCH 03/12] Change version in setup.py to 'py3k' to allow projects to require Milla==py3k --HG-- branch : py3k --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a79b491..1c7cf2f 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ if sys.version_info < (2, 7): setup( name='Milla', - version='0.1.2', + version='py3k', description='Lightweight WSGI framework for web applications', long_description='''\ Milla is a simple WSGI framework for Python web applications. It is mostly From 10e2ad803b4ee55ac6db07afe6aceaa41e6785a5 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Tue, 27 Nov 2012 17:26:34 -0600 Subject: [PATCH 04/12] Bump PyDev interpreter and grammer for Python 3 --HG-- branch : py3k --- .pydevproject | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pydevproject b/.pydevproject index af8c4ef..9948920 100644 --- a/.pydevproject +++ b/.pydevproject @@ -2,8 +2,8 @@ -Default -python 2.7 +python3 +python 3.0 /Milla/src From d2705ad18eb7200e23d9d30ca1c0b56e9094c164 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Wed, 28 Nov 2012 12:13:03 -0600 Subject: [PATCH 05/12] Better way to determine if a requirement is a Permission --HG-- branch : py3k --- src/milla/auth/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/milla/auth/decorators.py b/src/milla/auth/decorators.py index edb8780..d40c759 100644 --- a/src/milla/auth/decorators.py +++ b/src/milla/auth/decorators.py @@ -124,7 +124,7 @@ class require_perms(object): def __init__(self, *requirements): requirement = None for req in requirements: - if isinstance(req, basestring): + if not hasattr(req, 'check'): req = permissions.Permission(req) if not requirement: requirement = req From a8a90294d2fc74dd78dfab3bf4f48ea857f786bc Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Wed, 28 Nov 2012 12:13:34 -0600 Subject: [PATCH 06/12] Call str() instead of unicode() on Exceptions to get the message --HG-- branch : py3k --- src/milla/auth/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/milla/auth/__init__.py b/src/milla/auth/__init__.py index 8909fab..daeb535 100644 --- a/src/milla/auth/__init__.py +++ b/src/milla/auth/__init__.py @@ -37,7 +37,7 @@ class NotAuthorized(Exception): All other arguments and keywords are ignored. ''' - response = request.ResponseClass(unicode(self)) + response = request.ResponseClass(str(self)) response.status_int = 403 return response From afcbf3d3142ff7cf607acbc8f512a150f0699e97 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Wed, 28 Nov 2012 12:26:36 -0600 Subject: [PATCH 07/12] Get rid of unicode business in Permission objects --HG-- branch : py3k --- src/milla/auth/permissions.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/milla/auth/permissions.py b/src/milla/auth/permissions.py index 00a78e0..553285d 100644 --- a/src/milla/auth/permissions.py +++ b/src/milla/auth/permissions.py @@ -86,17 +86,8 @@ class Permission(BasePermission): def __init__(self, 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): - if isinstance(self.name, str): - return self.name - else: - return self.name.encode('utf-8') + return str(self.name) def __eq__(self, other): return self is other or str(self) == str(other) @@ -119,7 +110,7 @@ class PermissionRequirement(BasePermission): self.requirements = requirements def __str__(self): - return unicode(self).encode('utf-8') + return ', '.join(self.requirements) class PermissionRequirementAll(PermissionRequirement): '''Complex permission requirement needing all given permissions''' From 5735d2b027833628316aaf279ba8d9b074bdbedf Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Wed, 28 Nov 2012 12:26:58 -0600 Subject: [PATCH 08/12] Better handling of string-type responses from controllers --HG-- branch : py3k --- src/milla/app.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/milla/app.py b/src/milla/app.py index d7117e1..e30da43 100644 --- a/src/milla/app.py +++ b/src/milla/app.py @@ -25,7 +25,6 @@ from milla.controllers import FaviconController from milla.util import asbool from webob.exc import HTTPNotFound, WSGIHTTPException, HTTPMethodNotAllowed import milla.dispatch -import webob __all__ = ['Application'] @@ -126,9 +125,16 @@ class Application(object): func.func.im_self.__after__(request) # 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: - response = webob.Response(response) + response = request.ResponseClass(response) if not start_response_wrapper.called: start_response(response.status, response.headerlist) From 232bb17d6f481e597624d7aa3b019bb32f26bd38 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Fri, 30 Nov 2012 14:16:57 -0600 Subject: [PATCH 09/12] Fix test_app to run in Python 3 --HG-- branch : py3k --- src/milla/tests/test_app.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/milla/tests/test_app.py b/src/milla/tests/test_app.py index 15a72d0..3238cff 100644 --- a/src/milla/tests/test_app.py +++ b/src/milla/tests/test_app.py @@ -33,7 +33,7 @@ class ResponseMaker(object): def __init__(self, http_version=1.1): self.http_version = http_version self.headers = '' - self.body = '' + self.body = b'' def start_response(self, status, response_headers): 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) response.finish_response(app_iter) 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(): '''HTTP 405 is returned for disallowed HTTP request methods @@ -138,7 +138,7 @@ def test_emulated_method(): }) body = environ['wsgi.input'] body.seek(0) - body.write('_method=PUT') + body.write(b'_method=PUT') body.seek(0) response = ResponseMaker() app_iter = app(environ, response.start_response) @@ -358,7 +358,7 @@ def test_httperror_response(): ''' def controller(request): - raise webob.exc.HTTPClientError() + raise webob.exc.HTTPClientError('NotFound') app = milla.app.Application(StubResolver(controller)) environ = testing_environ() @@ -366,7 +366,7 @@ def test_httperror_response(): app_iter = app(environ, response.start_response) response.finish_response(app_iter) 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(): '''Ensure start_response is only called once''' @@ -393,4 +393,4 @@ def test_single_start_response(): response.finish_response(app_iter) assert start_response.call_count == 1, start_response.call_count assert response.headers.startswith('HTTP/1.1 200 OK'), response.headers - assert response.body == 'test', response.body + assert response.body == b'test', response.body From 17cac57721dedf6ca4b538aa4381f0bc01f48065 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Fri, 30 Nov 2012 14:37:02 -0600 Subject: [PATCH 10/12] Clean up __before__ and __after__ handling and make it Python 3-compatible --HG-- branch : py3k --- src/milla/app.py | 57 +++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/milla/app.py b/src/milla/app.py index 20dd1ae..c0b67d8 100644 --- a/src/milla/app.py +++ b/src/milla/app.py @@ -90,40 +90,12 @@ class Application(object): start_response_wrapper = StartResponseWrapper(start_response) request.start_response = start_response_wrapper try: - # If the callable has an __before__ attribute, call it - 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) + self._call_before(func)(request) response = func(request) except WSGIHTTPException as e: return e(environ, start_response) finally: - # If the callable has an __after__ method, call it - 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) + self._call_after(func)(request) # The callable might have returned just a string, which is OK, # but we need to wrap it in a Response object @@ -141,6 +113,31 @@ class Application(object): start_response(response.status, response.headerlist) if not environ['REQUEST_METHOD'] == 'HEAD': 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(): From 32e96f580718574cfed7cf929186707b26c5f948 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Fri, 30 Nov 2012 22:16:08 -0600 Subject: [PATCH 11/12] Fixed more Python 3 import errors --HG-- branch : py3k --- src/milla/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/milla/__init__.py b/src/milla/__init__.py index 4d03d24..f8334b3 100644 --- a/src/milla/__init__.py +++ b/src/milla/__init__.py @@ -19,13 +19,13 @@ from milla.app import * from milla.auth.decorators import * from webob.exc import * import webob -import urllib try: import urllib.parse -except ImportError: +except ImportError: #pragma: no cover + import urllib import urlparse urllib.parse = urlparse - + urllib.parse.urlencode = urllib.urlencode #@UndefinedVariable def allow(*methods): '''Specify the allowed HTTP verbs for a controller callable @@ -82,7 +82,7 @@ class Request(webob.Request): url = self._merge_url(self.script_name, path) if keywords: - url += '?' + urllib.urlencode(keywords) + url += '?' + urllib.parse.urlencode(keywords) return url @@ -102,7 +102,7 @@ class Request(webob.Request): url = self._merge_url(self.application_url, path) if keywords: - url += '?' + urllib.urlencode(keywords) + url += '?' + urllib.parse.urlencode(keywords) return url From 0064f70160d869c78a2dfd216de09b4188e14203 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Sat, 5 Jan 2013 10:38:05 -0600 Subject: [PATCH 12/12] Update read_config for new API in Python 3.2 --HG-- branch : py3k --- src/milla/config.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/milla/config.py b/src/milla/config.py index f656411..f50790f 100644 --- a/src/milla/config.py +++ b/src/milla/config.py @@ -40,8 +40,14 @@ def read_config(filename): app.config.update(config) ''' - cparser = configparser.SafeConfigParser() - cparser.readfp(open(filename)) + with open(filename) as f: + # 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 = {} for section in cparser.sections():