Skip to content

Commit 2759f04

Browse files
committed
Fix string/bytes issues under python 3.
1) cgi/client.py: override cgi.FieldStorage's make_file so that file is always created in binary/byte mode. This means that json (and xml) are bytes not strings. 2) rest.py: try harder to find dicttoxml in roundup directory or on sys.path. This just worked under python 2 but python 3 only searches sys.path by default and does not search relative like python 2. 3) rest.py: replace headers.getheader call removed from python 3 with equivalent code. 4) rest.py: make value returned from dispatch into bytes not string. 5) test/caseinsensitivedict.py, test/test_CaseInsensitiveDict.py: get code from stackoverflow that implements a case insensitive key dict. So dict['foo'], dict['Foo'] are the same entry. Used for looking up headers in mocked http rewuset header array. 6) test/rest_common.py: rework tests for etags and rest to properly supply bytes to the called routines. Calls to s2b and b2s and use of BytesIO and overriding make_file in cgi.FieldStorage to try to make sure it works under python 3.
1 parent 67f82b5 commit 2759f04

File tree

4 files changed

+168
-66
lines changed

4 files changed

+168
-66
lines changed

roundup/cgi/client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,18 @@ def __init__(self, instance, request, env, form=None, translator=None):
375375
logger.debug("Setting CONTENT_LENGTH to 0 for method: %s",
376376
self.env['REQUEST_METHOD'])
377377

378+
# cgi.FieldStorage must save all data as
379+
# binary/bytes. This is needed for handling json and xml
380+
# data blobs under python 3. Under python 2, str and binary
381+
# are interchangable, not so under 3.
382+
def make_file(self):
383+
import tempfile
384+
return tempfile.TemporaryFile("wb+")
385+
386+
saved_make_file = cgi.FieldStorage.make_file
387+
cgi.FieldStorage.make_file = make_file
378388
self.form = cgi.FieldStorage(fp=request.rfile, environ=env)
389+
cgi.FieldStorage.make_file = saved_make_file
379390
# In some case (e.g. content-type application/xml), cgi
380391
# will not parse anything. Fake a list property in this case
381392
if self.form.list is None:

roundup/rest.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,15 @@
2020
import re
2121

2222
try:
23-
from dicttoxml import dicttoxml
23+
# if dicttoxml installed in roundup directory, use it
24+
from .dicttoxml import dicttoxml
2425
except ImportError:
25-
dicttoxml = None
26+
try:
27+
# else look in sys.path
28+
from dicttoxml import dicttoxml
29+
except ImportError:
30+
# else not supported
31+
dicttoxml = None
2632

2733
from roundup import hyperdb
2834
from roundup import date
@@ -160,7 +166,8 @@ def obtain_etags(headers,input):
160166
etags = []
161167
if '@etag' in input:
162168
etags.append(input['@etag'].value);
163-
etags.append(headers.getheader("ETag", None))
169+
if "ETag" in headers:
170+
etags.append(headers["ETag"])
164171
return etags
165172

166173
def parse_accept_header(accept):
@@ -220,7 +227,9 @@ def parse_accept_header(accept):
220227
else:
221228
media_params.append((key, value))
222229
result.append((media_type, dict(media_params), q))
223-
result.sort(lambda x, y: -cmp(x[2], y[2]))
230+
# was: result.sort(lambda x, y: -cmp(x[2], y[2]))
231+
# change for python 3 support
232+
result.sort(key=lambda x: x[2], reverse=True)
224233
return result
225234

226235

@@ -1299,7 +1308,9 @@ def dispatch(self, method, uri, input):
12991308
headers = self.client.request.headers
13001309
# Never allow GET to be an unsafe operation (i.e. data changing).
13011310
# User must use POST to "tunnel" DELETE, PUT, OPTIONS etc.
1302-
override = headers.getheader('X-HTTP-Method-Override')
1311+
override = None
1312+
if 'X-HTTP-Method-Override' in headers:
1313+
override = headers['X-HTTP-Method-Override']
13031314
output = None
13041315
if override:
13051316
if method.upper() != 'GET':
@@ -1314,7 +1325,9 @@ def dispatch(self, method, uri, input):
13141325
uri)
13151326

13161327
# parse Accept header and get the content type
1317-
accept_header = parse_accept_header(headers.getheader('Accept'))
1328+
accept_header = []
1329+
if 'Accept' in headers:
1330+
accept_header = parse_accept_header(headers['Accept'])
13181331
accept_type = "invalid"
13191332
for part in accept_header:
13201333
if part[0] in self.__accepted_content_type:
@@ -1350,7 +1363,9 @@ def dispatch(self, method, uri, input):
13501363
# Is there an input.value with format json data?
13511364
# If so turn it into an object that emulates enough
13521365
# of the FieldStorge methods/props to allow a response.
1353-
content_type_header = headers.getheader('Content-Type', None)
1366+
content_type_header = None
1367+
if 'Content-Type' in headers:
1368+
content_type_header = headers['Content-Type']
13541369
if type(input.value) == str and content_type_header:
13551370
parsed_content_type_header = content_type_header
13561371
# the structure of a content-type header
@@ -1404,7 +1419,7 @@ def dispatch(self, method, uri, input):
14041419

14051420
# Make output json end in a newline to
14061421
# separate from following text in logs etc..
1407-
return output + "\n"
1422+
return bs2b(output + "\n")
14081423

14091424

14101425
class RoundupJSONEncoder(json.JSONEncoder):

0 commit comments

Comments
 (0)