diff options
-rw-r--r-- | lower-constraints.txt | 6 | ||||
-rw-r--r-- | requirements.txt | 7 | ||||
-rw-r--r-- | setup.cfg | 2 | ||||
-rw-r--r-- | setup.py | 14 | ||||
-rw-r--r-- | swiftclient/client.py | 32 | ||||
-rwxr-xr-x | swiftclient/shell.py | 11 | ||||
-rw-r--r-- | tests/unit/test_shell.py | 35 | ||||
-rw-r--r-- | tests/unit/test_swiftclient.py | 61 |
8 files changed, 132 insertions, 36 deletions
diff --git a/lower-constraints.txt b/lower-constraints.txt index 6488b28..9aae792 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -28,14 +28,14 @@ pep8==1.5.7 PrettyTable==0.7 pyflakes==0.8.1 Pygments==2.2.0 -python-keystoneclient==3.8.0 +python-keystoneclient==0.7.0 python-mimeparse==1.6.0 python-subunit==1.0.0 pytz==2013.6 PyYAML==3.12 reno==2.5.0 -requests==2.14.2 -six==1.10.0 +requests==1.1.0 +six==1.9.0 snowballstemmer==1.2.1 sphinx==1.6.2 sphinxcontrib-websupport==1.0.1 diff --git a/requirements.txt b/requirements.txt index 6b52791..1c2ce33 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,3 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. futures>=3.0.0;python_version=='2.7' or python_version=='2.6' # BSD -requests>=2.14.2 # Apache-2.0 -six>=1.10.0 # MIT +requests>=1.1.0 +six>=1.9.0 @@ -33,7 +33,7 @@ data_files = [extras] keystone = - python-keystoneclient>=3.8.0 # Apache-2.0 + python-keystoneclient>=0.7.0 [entry_points] console_scripts = @@ -17,16 +17,10 @@ # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools, sys -import setuptools - -# In python < 2.7.4, a lazy loading of package `pbr` will break -# setuptools if some other modules registered functions in `atexit`. -# solution from: http://bugs.python.org/issue15881#msg170215 -try: - import multiprocessing # noqa -except ImportError: - pass +if sys.version_info < (2, 7): + sys.exit('Sorry, Python < 2.7 is not supported for' + ' python-swiftclient>=3.0') setuptools.setup( - setup_requires=['pbr>=2.0.0'], + setup_requires=['pbr'], pbr=True) diff --git a/swiftclient/client.py b/swiftclient/client.py index 8cbdf45..410a001 100644 --- a/swiftclient/client.py +++ b/swiftclient/client.py @@ -151,7 +151,7 @@ def http_log(args, kwargs, resp, body): elif element in ('GET', 'POST', 'PUT'): string_parts.append(' -X %s' % element) else: - string_parts.append(' %s' % element) + string_parts.append(' %s' % parse_header_string(element)) if 'headers' in kwargs: headers = scrub_headers(kwargs['headers']) for element in headers: @@ -273,6 +273,9 @@ class _ObjectBody(object): def __next__(self): return self.next() + def close(self): + self.resp.close() + class _RetryBody(_ObjectBody): """ @@ -455,11 +458,23 @@ class HTTPConnection(object): self.resp.status = self.resp.status_code old_getheader = self.resp.raw.getheader + def _decode_header(string): + if string is None or six.PY2: + return string + return string.encode('iso-8859-1').decode('utf-8') + + def _encode_header(string): + if string is None or six.PY2: + return string + return string.encode('utf-8').decode('iso-8859-1') + def getheaders(): - return self.resp.headers.items() + return [(_decode_header(k), _decode_header(v)) + for k, v in self.resp.headers.items()] def getheader(k, v=None): - return old_getheader(k.lower(), v) + return _decode_header(old_getheader( + _encode_header(k.lower()), _encode_header(v))) def releasing_read(*args, **kwargs): chunk = self.resp.raw.read(*args, **kwargs) @@ -513,8 +528,11 @@ def get_auth_1_0(url, user, key, snet, **kwargs): netloc = parsed[1] parsed[1] = 'snet-' + netloc url = urlunparse(parsed) - return url, resp.getheader('x-storage-token', - resp.getheader('x-auth-token')) + + auth_token = resp.getheader('x-auth-token') + if auth_token is not None: + auth_token = parse_header_string(auth_token) + return url, resp.getheader('x-storage-token', auth_token) def get_keystoneclient_2_0(auth_url, user, key, os_options, **kwargs): @@ -694,10 +712,14 @@ def get_auth(auth_url, user, key, **kwargs): raise ClientException('Unknown auth_version %s specified and no ' 'session found.' % auth_version) + if token is not None: + token = parse_header_string(token) # Override storage url, if necessary if os_options.get('object_storage_url'): return os_options['object_storage_url'], token else: + if storage_url is not None: + return parse_header_string(storage_url), token return storage_url, token diff --git a/swiftclient/shell.py b/swiftclient/shell.py index ff5b2be..9ca28b1 100755 --- a/swiftclient/shell.py +++ b/swiftclient/shell.py @@ -1942,9 +1942,14 @@ Examples: parser.usage = globals()['st_%s_help' % args[0]] if options['insecure']: import requests - from requests.packages.urllib3.exceptions import \ - InsecureRequestWarning - requests.packages.urllib3.disable_warnings(InsecureRequestWarning) + try: + from requests.packages.urllib3.exceptions import \ + InsecureRequestWarning + except ImportError: + pass + else: + requests.packages.urllib3.disable_warnings( + InsecureRequestWarning) try: globals()['st_%s' % args[0]](parser, argv[1:], output) except ClientException as err: diff --git a/tests/unit/test_shell.py b/tests/unit/test_shell.py index 8c995e5..91496b8 100644 --- a/tests/unit/test_shell.py +++ b/tests/unit/test_shell.py @@ -14,8 +14,8 @@ # limitations under the License. from __future__ import unicode_literals +import contextlib from genericpath import getmtime - import getpass import hashlib import json @@ -27,7 +27,6 @@ import unittest import textwrap from time import localtime, mktime, strftime, strptime -from requests.packages.urllib3.exceptions import InsecureRequestWarning import six import sys @@ -44,6 +43,10 @@ from swiftclient.utils import ( EMPTY_ETAG, EXPIRES_ISO8601_FORMAT, SHORT_EXPIRES_ISO8601_FORMAT, TIME_ERRMSG) +try: + from requests.packages.urllib3.exceptions import InsecureRequestWarning +except ImportError: + InsecureRequestWarning = None if six.PY2: BUILTIN_OPEN = '__builtin__.open' @@ -114,6 +117,20 @@ def _make_cmd(cmd, opts, os_opts, use_env=False, flags=None, cmd_args=None): return args, env +@contextlib.contextmanager +def patch_disable_warnings(): + if InsecureRequestWarning is None: + # If InsecureRequestWarning isn't available, disbale_warnings won't + # be either; they both came in with + # https://github.com/requests/requests/commit/811ee4e and left again + # in https://github.com/requests/requests/commit/8e17600 + yield None + else: + with mock.patch('requests.packages.urllib3.disable_warnings') \ + as patched: + yield patched + + @mock.patch.dict(os.environ, mocked_os_environ) class TestShell(unittest.TestCase): def setUp(self): @@ -2529,8 +2546,7 @@ class TestKeystoneOptions(MockHttpTest): _make_fake_import_keystone_client(fake_ks)), \ mock.patch('swiftclient.client.http_connection', fake_conn), \ mock.patch.dict(os.environ, env, clear=True), \ - mock.patch('requests.packages.urllib3.disable_warnings') as \ - mock_disable_warnings: + patch_disable_warnings() as mock_disable_warnings: try: swiftclient.shell.main(args) except SystemExit as e: @@ -2538,11 +2554,12 @@ class TestKeystoneOptions(MockHttpTest): except SwiftError as err: self.fail('Unexpected SwiftError: %s' % err) - if 'insecure' in flags: - self.assertEqual([mock.call(InsecureRequestWarning)], - mock_disable_warnings.mock_calls) - else: - self.assertEqual([], mock_disable_warnings.mock_calls) + if InsecureRequestWarning is not None: + if 'insecure' in flags: + self.assertEqual([mock.call(InsecureRequestWarning)], + mock_disable_warnings.mock_calls) + else: + self.assertEqual([], mock_disable_warnings.mock_calls) if no_auth: # check that keystone client was not used and terminate tests diff --git a/tests/unit/test_swiftclient.py b/tests/unit/test_swiftclient.py index b6d6856..3303372 100644 --- a/tests/unit/test_swiftclient.py +++ b/tests/unit/test_swiftclient.py @@ -1896,6 +1896,57 @@ class TestHTTPConnection(MockHttpTest): self.assertFalse(resp.read()) self.assertTrue(resp.closed) + @unittest.skipIf(six.PY3, 'python2 specific test') + def test_response_python2_headers(self): + '''Test utf-8 headers in Python 2. + ''' + _, conn = c.http_connection(u'http://www.test.com/') + conn.resp = MockHttpResponse( + status=200, + headers={ + '\xd8\xaa-unicode': '\xd8\xaa-value', + 'empty-header': '' + } + ) + + resp = conn.getresponse() + self.assertEqual( + '\xd8\xaa-value', resp.getheader('\xd8\xaa-unicode')) + self.assertEqual( + '\xd8\xaa-value', resp.getheader('\xd8\xaa-UNICODE')) + self.assertEqual('', resp.getheader('empty-header')) + self.assertEqual( + dict([('\xd8\xaa-unicode', '\xd8\xaa-value'), + ('empty-header', ''), + ('etag', '"%s"' % EMPTY_ETAG)]), + dict(resp.getheaders())) + + @unittest.skipIf(six.PY2, 'python3 specific test') + def test_response_python3_headers(self): + '''Test latin1-encoded headers in Python 3. + ''' + _, conn = c.http_connection(u'http://www.test.com/') + conn.resp = MockHttpResponse( + status=200, + headers={ + b'\xd8\xaa-unicode'.decode('iso-8859-1'): + b'\xd8\xaa-value'.decode('iso-8859-1'), + 'empty-header': '' + } + ) + + resp = conn.getresponse() + self.assertEqual( + '\u062a-value', resp.getheader('\u062a-unicode')) + self.assertEqual( + '\u062a-value', resp.getheader('\u062a-UNICODE')) + self.assertEqual('', resp.getheader('empty-header')) + self.assertEqual( + dict([('\u062a-unicode', '\u062a-value'), + ('empty-header', ''), + ('etag', ('"%s"' % EMPTY_ETAG))]), + dict(resp.getheaders())) + class TestConnection(MockHttpTest): @@ -2839,6 +2890,16 @@ class TestLogging(MockHttpTest): self.assertIn('X-Storage-Token', output) self.assertIn(unicode_token_value, output) + @mock.patch('swiftclient.client.logger.debug') + def test_unicode_path(self, mock_log): + path = u'http://swift/v1/AUTH_account-\u062a'.encode('utf-8') + c.http_log(['GET', path], {}, + MockHttpResponse(status=200, headers=[]), '') + request_log_line = mock_log.mock_calls[0] + self.assertEqual('REQ: %s', request_log_line[1][0]) + self.assertEqual(u'curl -i -X GET %s' % path.decode('utf-8'), + request_log_line[1][1]) + class TestCloseConnection(MockHttpTest): |