Skip to content

Commit

Permalink
Merge pull request #75 from plone/python3
Browse files Browse the repository at this point in the history
Python 3 support
  • Loading branch information
jensens authored Oct 17, 2018
2 parents b43bf52 + e20c000 commit c30de1e
Show file tree
Hide file tree
Showing 14 changed files with 76 additions and 33 deletions.
10 changes: 9 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
language: python
python: 2.7.15
python:
- 2.7
- 3.6
cache:
pip: true
directories:
Expand All @@ -8,6 +10,12 @@ env:
- PLONE_VERSION=4.3.x
- PLONE_VERSION=5.0.x
- PLONE_VERSION=5.1.x
matrix:
exclude:
- python: 3.6
env: PLONE_VERSION=4.3.x
- python: 3.6
env: PLONE_VERSION=5.0.x
before_install:
- mkdir -p $HOME/buildout-cache/{eggs,downloads}
- virtualenv .
Expand Down
3 changes: 2 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ Changelog
1.3.1 (unreleased)
------------------

- Nothing changed yet.
- Prepare for Python 2 / 3 compatibility
[tschorr,pbauer,frapell]


1.3.0 (2018-09-11)
Expand Down
1 change: 1 addition & 0 deletions plone-5.1.x.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ extends =
[versions]
plone.namedfile = 4.2.3
plone.app.event = 3.0.6
plone.protect = 4.0.1
4 changes: 4 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
def read(*rnames):
return open(os.path.join(os.path.dirname(__file__), *rnames)).read()


version = '1.3.1-dev.0'

long_description = (
Expand Down Expand Up @@ -32,6 +33,8 @@ def read(*rnames):
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
],
keywords='',
author='Plone Foundation',
Expand Down Expand Up @@ -61,6 +64,7 @@ def read(*rnames):
'zope.traversing',
'Products.CMFCore',
'Zope2',
'six',
],
entry_points="""
# -*- Entry points: -*-
Expand Down
4 changes: 2 additions & 2 deletions src/plone/rest/cors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from plone.rest.interfaces import ICORSPolicy
from zope.interface import implements
from zope.interface import implementer

# CORS preflight service registry
# A mapping of method -> service_id
Expand All @@ -18,8 +18,8 @@ def lookup_preflight_service_id(method):
return _services[method]


@implementer(ICORSPolicy)
class CORSPolicy(object):
implements(ICORSPolicy)

def __init__(self, context, request):
self.context = context
Expand Down
29 changes: 25 additions & 4 deletions src/plone/rest/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@
from six.moves.urllib.parse import quote
from six.moves.urllib.parse import unquote
from zExceptions import NotFound
try:
from ZPublisher.HTTPRequest import WSGIRequest
HAS_WSGI = True
except ImportError:
HAS_WSGI = False
from zope.component import adapter
from zope.component import queryUtility
from zope.component.hooks import getSite

import json
import six
import sys
import traceback

Expand Down Expand Up @@ -42,8 +48,12 @@ def __call__(self):
return

def render_exception(self, exception):
result = {u'type': type(exception).__name__.decode('utf-8'),
u'message': str(exception).decode('utf-8')}
name = type(exception).__name__
message = str(exception)
if six.PY2:
name = name.decode('utf-8')
message = message.decode('utf-8')
result = {u'type': name, u'message': message}

if isinstance(exception, NotFound):
# First check if a redirect from p.a.redirector exists
Expand All @@ -65,8 +75,19 @@ def render_exception(self, exception):
def render_traceback(self, exception):
_, exc_obj, exc_traceback = sys.exc_info()
if exception is not exc_obj:
return (u'ERROR: Another exception happened before we could '
u'render the traceback.')
if HAS_WSGI and \
isinstance(self.request, WSGIRequest) and \
str(exception) == str(exc_obj):
# WSGIRequest may "upgrade" the exception,
# resulting in a new exception which has
# the same string representation as the
# original exception.
# https://github.com/plone/Products.CMFPlone/issues/2474
# https://github.com/plone/plone.rest/commit/96599cc3bb3ef5a23b10eb585781d88274fbcaf5#comments
pass
else:
return (u'ERROR: Another exception happened before we could '
u'render the traceback.')

raw = '\n'.join(traceback.format_tb(exc_traceback))
return raw.strip().split('\n')
Expand Down
4 changes: 2 additions & 2 deletions src/plone/rest/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
from plone.rest.interfaces import ICORSPolicy
from plone.rest.interfaces import IService
from zope.component import queryMultiAdapter
from zope.interface import implements
from zope.interface import implementer


@implementer(IService)
class Service(object):
implements(IService)

def __call__(self):
policy = queryMultiAdapter((self.context, self.request), ICORSPolicy)
Expand Down
2 changes: 1 addition & 1 deletion src/plone/rest/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def setUpZope(self, app, configurationContext):
class InternalServerErrorService(Service):

def __call__(self):
from urllib2 import HTTPError
from six.moves.urllib.error import HTTPError
raise HTTPError(
'http://nohost/plone/500-internal-server-error',
500,
Expand Down
12 changes: 9 additions & 3 deletions src/plone/rest/tests/test_dexterity.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,13 @@ def test_dexterity_news_item_get(self):
'text/html'
)
image_file = os.path.join(os.path.dirname(__file__), u'image.png')
fd = open(image_file, 'rb')
self.portal.newsitem.image = NamedBlobImage(
data=open(image_file, 'r').read(),
data=fd.read(),
contentType='image/png',
filename=u'image.png'
)
fd.close()
self.portal.newsitem.image_caption = u'This is an image caption.'
import transaction
transaction.commit()
Expand Down Expand Up @@ -176,11 +178,13 @@ def test_dexterity_file_get(self):
pdf_file = os.path.join(
os.path.dirname(__file__), u'file.pdf'
)
fd = open(pdf_file, 'rb')
self.portal.file.file = NamedBlobFile(
data=open(pdf_file, 'r').read(),
data=fd.read(),
contentType='application/pdf',
filename=u'file.pdf'
)
fd.close()
intids = getUtility(IIntIds)
file_id = intids.getId(self.portal.file)
self.portal.file.file = RelationValue(file_id)
Expand All @@ -202,11 +206,13 @@ def test_dexterity_image_get(self):
self.portal.image.title = 'My Image'
self.portal.image.description = u'This is an image'
image_file = os.path.join(os.path.dirname(__file__), u'image.png')
fd = open(image_file, 'rb')
self.portal.image.image = NamedBlobImage(
data=open(image_file, 'r').read(),
data=fd.read(),
contentType='image/png',
filename=u'image.png'
)
fd.close()
import transaction
transaction.commit()

Expand Down
2 changes: 1 addition & 1 deletion src/plone/rest/tests/test_error_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,4 @@ def test_500_traceback_only_for_manager_users(self):
self.assertIsInstance(traceback, list)
self.assertRegexpMatches(
traceback[0],
r'^File "[^"]*", line \d*, in publish')
r'^File "[^"]*", line \d*, in (publish|transaction_pubevents)')
5 changes: 3 additions & 2 deletions src/plone/rest/tests/test_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ def traverse(self, path='/plone', accept='application/json', method='GET'):
request.environ['PATH_TRANSLATED'] = path
request.environ['HTTP_ACCEPT'] = accept
request.environ['REQUEST_METHOD'] = method
request._auth = 'Basic %s' % b64encode(
'%s:%s' % (TEST_USER_NAME, TEST_USER_PASSWORD))
auth = '%s:%s' % (TEST_USER_NAME, TEST_USER_PASSWORD)
b64auth = b64encode(auth.encode('utf8'))
request._auth = 'Basic %s' % b64auth.decode('utf8')
notify(PubStart(request))
return request.traverse(path)

Expand Down
18 changes: 9 additions & 9 deletions src/plone/rest/tests/test_redirects.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def test_get_to_moved_item_causes_301_redirect(self):
self.assertEqual(301, response.status_code)
self.assertEqual(self.portal_url + '/folder-new',
response.headers['Location'])
self.assertEqual(u'', response.text)
self.assertEqual(b'', response.raw.read())

def test_post_to_moved_item_causes_308_redirect(self):
response = requests.post(
Expand All @@ -50,7 +50,7 @@ def test_post_to_moved_item_causes_308_redirect(self):
self.assertEqual(308, response.status_code)
self.assertEqual(self.portal_url + '/folder-new',
response.headers['Location'])
self.assertEqual(u'', response.text)
self.assertEqual(b'', response.raw.read())

def test_unauthorized_request_to_item_still_redirects_first(self):
response = requests.get(
Expand All @@ -65,7 +65,7 @@ def test_unauthorized_request_to_item_still_redirects_first(self):
self.assertEqual(301, response.status_code)
self.assertEqual(self.portal_url + '/folder-new',
response.headers['Location'])
self.assertEqual(u'', response.text)
self.assertEqual(b'', response.raw.read())

# Following the redirect then leads to an item that will produce a 401
response = requests.get(
Expand All @@ -86,7 +86,7 @@ def test_query_string_gets_preserved(self):
self.assertEqual(301, response.status_code)
self.assertEqual(self.portal_url + '/folder-new?key=value',
response.headers['Location'])
self.assertEqual(u'', response.text)
self.assertEqual(b'', response.raw.read())

def test_named_service_on_moved_item_causes_301_redirect(self):
response = requests.get(
Expand All @@ -98,7 +98,7 @@ def test_named_service_on_moved_item_causes_301_redirect(self):
self.assertEqual(301, response.status_code)
self.assertEqual(self.portal_url + '/folder-new/namedservice',
response.headers['Location'])
self.assertEqual(u'', response.text)
self.assertEqual(b'', response.raw.read())

def test_named_service_plus_path_parameter_works(self):
response = requests.get(
Expand All @@ -110,7 +110,7 @@ def test_named_service_plus_path_parameter_works(self):
self.assertEqual(301, response.status_code)
self.assertEqual(self.portal_url + '/folder-new/namedservice/param',
response.headers['Location'])
self.assertEqual(u'', response.text)
self.assertEqual(b'', response.raw.read())

def test_redirects_for_regular_views_still_work(self):
response = requests.get(
Expand All @@ -122,7 +122,7 @@ def test_redirects_for_regular_views_still_work(self):
self.assertEqual(301, response.status_code)
self.assertEqual(self.portal_url + '/folder-new/@@some-view',
response.headers['Location'])
self.assertEqual(u'', response.text)
self.assertEqual(b'', response.raw.read())

def test_redirects_for_views_plus_params_plus_querystring_works(self):
response = requests.get(
Expand All @@ -134,7 +134,7 @@ def test_redirects_for_views_plus_params_plus_querystring_works(self):
self.assertEqual(301, response.status_code)
self.assertEqual(self.portal_url + '/folder-new/@@some-view/param?k=v',
response.headers['Location'])
self.assertEqual(u'', response.text)
self.assertEqual(b'', response.raw.read())

def test_doesnt_cause_redirect_loop_on_bogus_storage_entries(self):
storage = queryUtility(IRedirectionStorage)
Expand Down Expand Up @@ -164,7 +164,7 @@ def test_handles_redirects_that_include_querystring_in_old_path(self):
self.assertEqual(301, response.status_code)
self.assertEqual(self.portal_url + '/new-item',
response.headers['Location'])
self.assertEqual(u'', response.text)
self.assertEqual(b'', response.raw.read())

def test_aborts_redirect_checks_early_for_app_root(self):
error_view = ErrorHandling(self.portal, self.portal.REQUEST)
Expand Down
5 changes: 3 additions & 2 deletions src/plone/rest/tests/test_traversal.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ def traverse(self, path='/plone', accept='application/json', method='GET'):
request.environ['PATH_TRANSLATED'] = path
request.environ['HTTP_ACCEPT'] = accept
request.environ['REQUEST_METHOD'] = method
request._auth = 'Basic %s' % b64encode(
'%s:%s' % (SITE_OWNER_NAME, SITE_OWNER_PASSWORD))
auth = '%s:%s' % (SITE_OWNER_NAME, SITE_OWNER_PASSWORD)
b64auth = b64encode(auth.encode('utf8'))
request._auth = 'Basic %s' % b64auth.decode('utf8')
notify(PubStart(request))
return request.traverse(path)

Expand Down
10 changes: 5 additions & 5 deletions src/plone/rest/traverse.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# -*- coding: utf-8 -*-
from Products.CMFPlone.interfaces.siteroot import IPloneSiteRoot
from Products.SiteAccess.VirtualHostMonster import VirtualHostMonster
from ZPublisher.BaseRequest import DefaultPublishTraverse
from plone.rest.interfaces import IAPIRequest
from plone.rest.interfaces import IService
from zope.component import adapts
from zope.component import adapter
from zope.component import queryMultiAdapter
from zope.interface import implements
from zope.interface import implementer
from zope.publisher.interfaces.browser import IBrowserPublisher
from Products.CMFCore.interfaces import IContentish
from Products.CMFCore.interfaces import ISiteRoot


@adapter(IPloneSiteRoot, IAPIRequest)
class RESTTraverse(DefaultPublishTraverse):
adapts(ISiteRoot, IAPIRequest)

def publishTraverse(self, request, name):
try:
Expand Down Expand Up @@ -51,10 +51,10 @@ def browserDefault(self, request):
return self.context, (request._rest_service_id,)


@implementer(IBrowserPublisher)
class RESTWrapper(object):
"""A wrapper for objects traversed during a REST request.
"""
implements(IBrowserPublisher)

def __init__(self, context, request):
self.context = context
Expand Down

0 comments on commit c30de1e

Please sign in to comment.