Finalize Python3 support

Set the environment variable PYTHONHASHSEED to 0 to have
predictive tests under Python 3.

Change-Id: Ia15a9383e0f20bd0e4572e9f9b9772f1704dff86
This commit is contained in:
Frederic Lepied 2014-04-26 23:34:58 +02:00
parent f2e0610628
commit 628c541a69
10 changed files with 86 additions and 38 deletions

View File

@ -19,6 +19,7 @@ import hashlib
import logging import logging
import posixpath import posixpath
import socket import socket
import ssl
import struct import struct
import six import six
@ -63,6 +64,13 @@ USER_AGENT = 'python-glanceclient'
CHUNKSIZE = 1024 * 64 # 64kB CHUNKSIZE = 1024 * 64 # 64kB
def to_bytes(s):
if isinstance(s, six.string_types):
return six.b(s)
else:
return s
class HTTPClient(object): class HTTPClient(object):
def __init__(self, endpoint, **kwargs): def __init__(self, endpoint, **kwargs):
@ -149,8 +157,9 @@ class HTTPClient(object):
dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()]) dump.extend(['%s: %s' % (k, v) for k, v in resp.getheaders()])
dump.append('') dump.append('')
if body: if body:
body = strutils.safe_decode(body)
dump.extend([body, '']) dump.extend([body, ''])
LOG.debug(strutils.safe_encode('\n'.join(dump))) LOG.debug('\n'.join([strutils.safe_encode(x) for x in dump]))
@staticmethod @staticmethod
def encode_headers(headers): def encode_headers(headers):
@ -239,9 +248,9 @@ class HTTPClient(object):
# Read body into string if it isn't obviously image data # Read body into string if it isn't obviously image data
if resp.getheader('content-type', None) != 'application/octet-stream': if resp.getheader('content-type', None) != 'application/octet-stream':
body_str = ''.join([chunk for chunk in body_iter]) body_str = b''.join([to_bytes(chunk) for chunk in body_iter])
self.log_http_response(resp, body_str) self.log_http_response(resp, body_str)
body_iter = six.StringIO(body_str) body_iter = six.BytesIO(body_str)
else: else:
self.log_http_response(resp) self.log_http_response(resp)
@ -349,16 +358,28 @@ class VerifiedHTTPSConnection(HTTPSConnection):
def __init__(self, host, port=None, key_file=None, cert_file=None, def __init__(self, host, port=None, key_file=None, cert_file=None,
cacert=None, timeout=None, insecure=False, cacert=None, timeout=None, insecure=False,
ssl_compression=True): ssl_compression=True):
HTTPSConnection.__init__(self, host, port, # List of exceptions reported by Python3 instead of
key_file=key_file, # SSLConfigurationError
cert_file=cert_file) if six.PY3:
self.key_file = key_file excp_lst = (TypeError, FileNotFoundError, ssl.SSLError)
self.cert_file = cert_file else:
self.timeout = timeout excp_lst = ()
self.insecure = insecure try:
self.ssl_compression = ssl_compression HTTPSConnection.__init__(self, host, port,
self.cacert = cacert key_file=key_file,
self.setcontext() cert_file=cert_file)
self.key_file = key_file
self.cert_file = cert_file
self.timeout = timeout
self.insecure = insecure
self.ssl_compression = ssl_compression
self.cacert = cacert
self.setcontext()
# ssl exceptions are reported in various form in Python 3
# so to be compatible, we report the same kind as under
# Python2
except excp_lst as e:
raise exc.SSLConfigurationError(str(e))
@staticmethod @staticmethod
def host_matches_cert(host, x509): def host_matches_cert(host, x509):
@ -388,7 +409,7 @@ class VerifiedHTTPSConnection(HTTPSConnection):
san_list = None san_list = None
for i in range(x509.get_extension_count()): for i in range(x509.get_extension_count()):
ext = x509.get_extension(i) ext = x509.get_extension(i)
if ext.get_short_name() == 'subjectAltName': if ext.get_short_name() == b'subjectAltName':
san_list = str(ext) san_list = str(ext)
for san in ''.join(san_list.split()).split(','): for san in ''.join(san_list.split()).split(','):
if san.startswith('DNS:'): if san.startswith('DNS:'):
@ -458,7 +479,7 @@ class VerifiedHTTPSConnection(HTTPSConnection):
if self.cacert: if self.cacert:
try: try:
self.context.load_verify_locations(self.cacert) self.context.load_verify_locations(to_bytes(self.cacert))
except Exception as e: except Exception as e:
msg = ('Unable to load CA from "%(cacert)s" %(exc)s' % msg = ('Unable to load CA from "%(cacert)s" %(exc)s' %
dict(cacert=self.cacert, exc=e)) dict(cacert=self.cacert, exc=e))
@ -537,6 +558,8 @@ class ResponseBodyIterator(object):
raise raise
else: else:
yield chunk yield chunk
if isinstance(chunk, six.string_types):
chunk = six.b(chunk)
md5sum.update(chunk) md5sum.update(chunk)
def next(self): def next(self):

View File

@ -267,7 +267,8 @@ def get_file_size(file_obj):
:param file_obj: file-like object. :param file_obj: file-like object.
:retval The file's size or None if it cannot be determined. :retval The file's size or None if it cannot be determined.
""" """
if hasattr(file_obj, 'seek') and hasattr(file_obj, 'tell'): if (hasattr(file_obj, 'seek') and hasattr(file_obj, 'tell') and
(six.PY2 or six.PY3 and file_obj.seekable())):
try: try:
curr = file_obj.tell() curr = file_obj.tell()
file_obj.seek(0, os.SEEK_END) file_obj.seek(0, os.SEEK_END)

View File

@ -20,6 +20,7 @@ Command-line interface to the OpenStack Images API.
from __future__ import print_function from __future__ import print_function
import argparse import argparse
import copy
import json import json
import logging import logging
import os import os
@ -440,8 +441,12 @@ class OpenStackImagesShell(object):
def main(self, argv): def main(self, argv):
# Parse args once to find version # Parse args once to find version
#NOTE(flepied) Under Python3, parsed arguments are removed
# from the list so make a copy for the first parsing
base_argv = copy.deepcopy(argv)
parser = self.get_base_parser() parser = self.get_base_parser()
(options, args) = parser.parse_known_args(argv) (options, args) = parser.parse_known_args(base_argv)
# build available subcommands based on version # build available subcommands based on version
api_version = options.os_image_api_version api_version = options.os_image_api_version

View File

@ -16,6 +16,8 @@ classifier =
Programming Language :: Python :: 2 Programming Language :: Python :: 2
Programming Language :: Python :: 2.7 Programming Language :: Python :: 2.7
Programming Language :: Python :: 2.6 Programming Language :: Python :: 2.6
Programming Language :: Python :: 3
Programming Language :: Python :: 3.3
[files] [files]
packages = packages =

View File

@ -105,7 +105,7 @@ class TestClient(testtools.TestCase):
def test_request_redirected(self): def test_request_redirected(self):
resp = utils.FakeResponse({'location': 'http://www.example.com'}, resp = utils.FakeResponse({'location': 'http://www.example.com'},
status=302, body=six.StringIO()) status=302, body=six.BytesIO())
http_client.HTTPConnection.request( http_client.HTTPConnection.request(
mox.IgnoreArg(), mox.IgnoreArg(),
mox.IgnoreArg(), mox.IgnoreArg(),
@ -114,8 +114,8 @@ class TestClient(testtools.TestCase):
http_client.HTTPConnection.getresponse().AndReturn(resp) http_client.HTTPConnection.getresponse().AndReturn(resp)
# The second request should be to the redirected location # The second request should be to the redirected location
expected_response = 'Ok' expected_response = b'Ok'
resp2 = utils.FakeResponse({}, six.StringIO(expected_response)) resp2 = utils.FakeResponse({}, six.BytesIO(expected_response))
http_client.HTTPConnection.request( http_client.HTTPConnection.request(
'GET', 'GET',
'http://www.example.com', 'http://www.example.com',
@ -135,8 +135,8 @@ class TestClient(testtools.TestCase):
# Lets fake the response # Lets fake the response
# returned by httplib # returned by httplib
expected_response = 'Ok' expected_response = b'Ok'
fake = utils.FakeResponse({}, six.StringIO(expected_response)) fake = utils.FakeResponse({}, six.BytesIO(expected_response))
http_client.HTTPConnection.getresponse().AndReturn(fake) http_client.HTTPConnection.getresponse().AndReturn(fake)
self.mock.ReplayAll() self.mock.ReplayAll()
@ -146,9 +146,13 @@ class TestClient(testtools.TestCase):
self.assertEqual(fake, resp) self.assertEqual(fake, resp)
def test_headers_encoding(self): def test_headers_encoding(self):
headers = {"test": u'ni\xf1o'} value = u'ni\xf1o'
headers = {"test": value}
encoded = self.client.encode_headers(headers) encoded = self.client.encode_headers(headers)
self.assertEqual("ni\xc3\xb1o", encoded["test"]) if six.PY2:
self.assertEqual("ni\xc3\xb1o", encoded["test"])
else:
self.assertEqual(value, encoded["test"])
def test_raw_request(self): def test_raw_request(self):
" Verify the path being used for HTTP requests reflects accurately. " " Verify the path being used for HTTP requests reflects accurately. "
@ -164,7 +168,7 @@ class TestClient(testtools.TestCase):
headers=mox.IgnoreArg()).WithSideEffects(check_request) headers=mox.IgnoreArg()).WithSideEffects(check_request)
# fake the response returned by httplib # fake the response returned by httplib
fake = utils.FakeResponse({}, six.StringIO('Ok')) fake = utils.FakeResponse({}, six.BytesIO(b'Ok'))
http_client.HTTPConnection.getresponse().AndReturn(fake) http_client.HTTPConnection.getresponse().AndReturn(fake)
self.mock.ReplayAll() self.mock.ReplayAll()
@ -192,7 +196,7 @@ class TestClient(testtools.TestCase):
headers=mox.IgnoreArg()).WithSideEffects(check_request) headers=mox.IgnoreArg()).WithSideEffects(check_request)
# fake the response returned by httplib # fake the response returned by httplib
fake = utils.FakeResponse({}, six.StringIO('Ok')) fake = utils.FakeResponse({}, six.BytesIO(b'Ok'))
http_client.HTTPConnection.getresponse().AndReturn(fake) http_client.HTTPConnection.getresponse().AndReturn(fake)
self.mock.ReplayAll() self.mock.ReplayAll()
@ -396,19 +400,19 @@ class TestVerifiedHTTPSConnection(testtools.TestCase):
class TestResponseBodyIterator(testtools.TestCase): class TestResponseBodyIterator(testtools.TestCase):
def test_iter_default_chunk_size_64k(self): def test_iter_default_chunk_size_64k(self):
resp = utils.FakeResponse({}, six.StringIO('X' * 98304)) resp = utils.FakeResponse({}, six.BytesIO(b'X' * 98304))
iterator = http.ResponseBodyIterator(resp) iterator = http.ResponseBodyIterator(resp)
chunks = list(iterator) chunks = list(iterator)
self.assertEqual(['X' * 65536, 'X' * 32768], chunks) self.assertEqual([b'X' * 65536, b'X' * 32768], chunks)
def test_integrity_check_with_correct_checksum(self): def test_integrity_check_with_correct_checksum(self):
resp = utils.FakeResponse({}, six.StringIO('CCC')) resp = utils.FakeResponse({}, six.BytesIO(b'CCC'))
body = http.ResponseBodyIterator(resp) body = http.ResponseBodyIterator(resp)
body.set_checksum('defb99e69a9f1f6e06f15006b1f166ae') body.set_checksum('defb99e69a9f1f6e06f15006b1f166ae')
list(body) list(body)
def test_integrity_check_with_wrong_checksum(self): def test_integrity_check_with_wrong_checksum(self):
resp = utils.FakeResponse({}, six.StringIO('BB')) resp = utils.FakeResponse({}, six.BytesIO(b'BB'))
body = http.ResponseBodyIterator(resp) body = http.ResponseBodyIterator(resp)
body.set_checksum('wrong') body.set_checksum('wrong')
try: try:
@ -418,7 +422,7 @@ class TestResponseBodyIterator(testtools.TestCase):
self.assertEqual(errno.EPIPE, e.errno) self.assertEqual(errno.EPIPE, e.errno)
def test_set_checksum_in_consumed_iterator(self): def test_set_checksum_in_consumed_iterator(self):
resp = utils.FakeResponse({}, six.StringIO('CCC')) resp = utils.FakeResponse({}, six.BytesIO(b'CCC'))
body = http.ResponseBodyIterator(resp) body = http.ResponseBodyIterator(resp)
list(body) list(body)
# Setting checksum for an already consumed iterator should raise an # Setting checksum for an already consumed iterator should raise an
@ -430,6 +434,6 @@ class TestResponseBodyIterator(testtools.TestCase):
def test_body_size(self): def test_body_size(self):
size = 1000000007 size = 1000000007
resp = utils.FakeResponse( resp = utils.FakeResponse(
{'content-length': str(size)}, six.StringIO('BB')) {'content-length': str(size)}, six.BytesIO(b'BB'))
body = http.ResponseBodyIterator(resp) body = http.ResponseBodyIterator(resp)
self.assertEqual(size, len(body)) self.assertEqual(size, len(body))

View File

@ -66,6 +66,8 @@ class TestVerifiedHTTPSConnection(testtools.TestCase):
conn = http.VerifiedHTTPSConnection('127.0.0.1', 0, conn = http.VerifiedHTTPSConnection('127.0.0.1', 0,
key_file=key_file, key_file=key_file,
cacert=cacert) cacert=cacert)
except exc.SSLConfigurationError:
pass
except Exception: except Exception:
self.fail('Failed to init VerifiedHTTPSConnection.') self.fail('Failed to init VerifiedHTTPSConnection.')

View File

@ -109,7 +109,10 @@ class TestUtils(testtools.TestCase):
self.assertEqual('error message', ret) self.assertEqual('error message', ret)
ret = utils.exception_to_str(Exception('\xa5 error message')) ret = utils.exception_to_str(Exception('\xa5 error message'))
self.assertEqual(' error message', ret) if six.PY2:
self.assertEqual(' error message', ret)
else:
self.assertEqual('\xa5 error message', ret)
ret = utils.exception_to_str(FakeException('\xa5 error message')) ret = utils.exception_to_str(FakeException('\xa5 error message'))
self.assertEqual("Caught '%(exception)s' exception." % self.assertEqual("Caught '%(exception)s' exception." %

View File

@ -347,7 +347,7 @@ fixtures = {
'HEAD': ( 'HEAD': (
{ {
'x-image-meta-id': '3', 'x-image-meta-id': '3',
'x-image-meta-name': "ni\xc3\xb1o" 'x-image-meta-name': u"ni\xf1o"
}, },
None, None,
), ),
@ -622,9 +622,13 @@ class ImageManagerTest(testtools.TestCase):
self.assertEqual(expect, self.api.calls) self.assertEqual(expect, self.api.calls)
def test_image_meta_from_headers_encoding(self): def test_image_meta_from_headers_encoding(self):
fields = {"x-image-meta-name": "ni\xc3\xb1o"} value = u"ni\xf1o"
if six.PY2:
fields = {"x-image-meta-name": "ni\xc3\xb1o"}
else:
fields = {"x-image-meta-name": value}
headers = self.mgr._image_meta_from_headers(fields) headers = self.mgr._image_meta_from_headers(fields)
self.assertEqual(u"ni\xf1o", headers["name"]) self.assertEqual(value, headers["name"])
def test_image_list_with_owner(self): def test_image_list_with_owner(self):
images = self.mgr.list(owner='A', page_size=20) images = self.mgr.list(owner='A', page_size=20)

View File

@ -16,6 +16,7 @@
import errno import errno
import testtools import testtools
import six
import warlock import warlock
from glanceclient.v2 import images from glanceclient.v2 import images
@ -403,8 +404,10 @@ class TestController(testtools.TestCase):
# /v2/images?owner=ni%C3%B1o&limit=20 # /v2/images?owner=ni%C3%B1o&limit=20
# We just want to make sure filters are correctly encoded. # We just want to make sure filters are correctly encoded.
pass pass
if six.PY2:
self.assertEqual("ni\xc3\xb1o", filters["owner"]) self.assertEqual("ni\xc3\xb1o", filters["owner"])
else:
self.assertEqual("ni\xf1o", filters["owner"])
def test_list_images_for_tag_single_image(self): def test_list_images_for_tag_single_image(self):
img_id = '3a4560a1-e585-443e-9b39-553b46ec92d1' img_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'

View File

@ -9,6 +9,7 @@ install_command = pip install -U {opts} {packages}
setenv = VIRTUAL_ENV={envdir} setenv = VIRTUAL_ENV={envdir}
OS_STDOUT_NOCAPTURE=False OS_STDOUT_NOCAPTURE=False
OS_STDERR_NOCAPTURE=False OS_STDERR_NOCAPTURE=False
PYTHONHASHSEED=0
deps = -r{toxinidir}/requirements.txt deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt -r{toxinidir}/test-requirements.txt