diff options
-rw-r--r-- | README.rst | 4 | ||||
-rw-r--r-- | doc/source/conf.py | 68 | ||||
-rw-r--r-- | setup.cfg | 5 | ||||
-rw-r--r-- | swiftclient/__init__.py | 4 | ||||
-rw-r--r-- | swiftclient/client.py | 29 | ||||
-rwxr-xr-x | swiftclient/shell.py | 4 | ||||
-rw-r--r-- | tests/functional/test_swiftclient.py | 7 | ||||
-rw-r--r-- | tests/unit/test_multithreading.py | 4 | ||||
-rw-r--r-- | tests/unit/test_shell.py (renamed from tests/test_shell.py) | 4 | ||||
-rw-r--r-- | tests/unit/test_swiftclient.py | 30 |
10 files changed, 88 insertions, 71 deletions
@@ -4,6 +4,10 @@ Python bindings to the OpenStack Object Storage API This is a python client for the Swift API. There's a Python API (the ``swiftclient`` module), and a command-line script (``swift``). +You can find the `documentation online`__. + +__ http://docs.openstack.org/developer/python-swiftclient/ + Development takes place via the usual OpenStack processes as outlined in the `OpenStack wiki`__. The master repository is on GitHub__. diff --git a/doc/source/conf.py b/doc/source/conf.py index 19bc85c..42ae383 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -19,7 +19,7 @@ import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) +# sys.path.append(os.path.abspath('.')) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", "..")) @@ -44,7 +44,7 @@ templates_path = ['_templates'] source_suffix = '.rst' # The encoding of source files. -#source_encoding = 'utf-8' +# source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' @@ -63,16 +63,16 @@ version = swiftclient.version.version_string # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. -#unused_docs = [] +# unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. @@ -80,24 +80,24 @@ exclude_trees = [] # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output -------------------------------------------------- @@ -109,26 +109,26 @@ html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # "<project> v<release> documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -137,38 +137,38 @@ html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_use_modindex = True +# html_use_modindex = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a <link> tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' +# html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'SwiftClientwebdoc' @@ -177,10 +177,10 @@ htmlhelp_basename = 'SwiftClientwebdoc' # -- Options for LaTeX output ------------------------------------------------- # The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +# latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' +# latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]) @@ -191,17 +191,17 @@ latex_documents = [ # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +# latex_preamble = '' # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_use_modindex = True +# latex_use_modindex = True @@ -16,6 +16,8 @@ classifier = Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 2.6 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.3 [global] setup-hooks = @@ -36,3 +38,6 @@ all_files = 1 [upload_sphinx] upload-dir = doc/build/html + +[wheel] +universal = 1 diff --git a/swiftclient/__init__.py b/swiftclient/__init__.py index c4b7d45..b412f13 100644 --- a/swiftclient/__init__.py +++ b/swiftclient/__init__.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- # Copyright (c) 2012 Rackspace # flake8: noqa # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""" +""" OpenStack Swift Python client binding. """ from .client import * diff --git a/swiftclient/client.py b/swiftclient/client.py index 3fb615a..f13e829 100644 --- a/swiftclient/client.py +++ b/swiftclient/client.py @@ -186,10 +186,12 @@ class HTTPConnection: for header, value in items: value = encode_utf8(value) header = header.lower() - for target_type in 'container', 'account', 'object': - prefix = 'x-%s-meta-' % target_type - if header.startswith(prefix): - header = encode_utf8(header) + if isinstance(header, six.string_types): + for target_type in 'container', 'account', 'object': + prefix = 'x-%s-meta-' % target_type + if header.startswith(prefix): + header = encode_utf8(header) + break ret[header] = value return ret @@ -274,7 +276,7 @@ def get_auth_1_0(url, user, key, snet, **kwargs): def get_keystoneclient_2_0(auth_url, user, key, os_options, **kwargs): """ - Authenticate against a auth 2.0 server. + Authenticate against an auth 2.0 server. We are using the keystoneclient library for our 2.0 authentication. """ @@ -359,7 +361,7 @@ def get_auth(auth_url, user, key, **kwargs): if kwargs.get('tenant_name'): os_options['tenant_name'] = kwargs['tenant_name'] - if (not 'tenant_name' in os_options): + if ('tenant_name' not in os_options): raise ClientException('No tenant specified') cacert = kwargs.get('cacert', None) @@ -676,7 +678,7 @@ def put_container(url, token, container, headers=None, http_conn=None, if not headers: headers = {} headers['X-Auth-Token'] = token - if not 'content-length' in (k.lower() for k in headers): + if 'content-length' not in (k.lower() for k in headers): headers['Content-Length'] = '0' conn.request(method, path, '', headers) resp = conn.getresponse() @@ -716,7 +718,7 @@ def post_container(url, token, container, headers, http_conn=None, path = '%s/%s' % (parsed.path, quote(container)) method = 'POST' headers['X-Auth-Token'] = token - if not 'content-length' in (k.lower() for k in headers): + if 'content-length' not in (k.lower() for k in headers): headers['Content-Length'] = '0' conn.request(method, path, '', headers) resp = conn.getresponse() @@ -898,10 +900,9 @@ def put_object(url, token=None, container=None, name=None, contents=None, :param etag: etag of contents; if None, no etag will be sent :param chunk_size: chunk size of data to write; it defaults to 65536; used only if the contents object has a 'read' - method, eg. file-like objects, ignored otherwise - :param content_type: value to send as content-type header; if None, no - content-type will be set (remote end will likely try - to auto-detect it) + method, e.g. file-like objects, ignored otherwise + :param content_type: value to send as content-type header; if None, an + empty string value will be sent :param headers: additional headers to include in the request, if any :param http_conn: HTTP connection object (If None, it will create the conn object) @@ -940,6 +941,8 @@ def put_object(url, token=None, container=None, name=None, contents=None, content_length = int(v) if content_type is not None: headers['Content-Type'] = content_type + else: # python-requests sets application/x-www-form-urlencoded otherwise + headers['Content-Type'] = '' if not contents: headers['Content-Length'] = '0' if hasattr(contents, 'read'): @@ -1117,7 +1120,7 @@ class Connection(object): :param max_backoff: maximum delay between retries (seconds) :param auth_version: OpenStack auth version, default is 1.0 :param tenant_name: The tenant/account name, required when connecting - to a auth 2.0 system. + to an auth 2.0 system. :param os_options: The OpenStack options which can have tenant_id, auth_token, service_type, endpoint_type, tenant_name, object_storage_url, region_name diff --git a/swiftclient/shell.py b/swiftclient/shell.py index 9e12b2a..d035388 100755 --- a/swiftclient/shell.py +++ b/swiftclient/shell.py @@ -831,7 +831,7 @@ Optional arguments: --segment-container <container> Upload the segments into the specified container. If not specified, the segments will be uploaded to a - <container>_segments container so as to not pollute the + <container>_segments container to not pollute the main <container> listings. --leave-segments Indicates that you want the older segments of manifest objects left alone (in the case of overwrites). @@ -872,7 +872,7 @@ def st_upload(parser, args, thread_manager): '-C', '--segment-container', dest='segment_container', help='Upload the segments into the specified container. ' 'If not specified, the segments will be uploaded to a ' - '<container>_segments container so as to not pollute the main ' + '<container>_segments container to not pollute the main ' '<container> listings.') parser.add_option( '', '--leave-segments', action='store_true', diff --git a/tests/functional/test_swiftclient.py b/tests/functional/test_swiftclient.py index 6631d36..f58009a 100644 --- a/tests/functional/test_swiftclient.py +++ b/tests/functional/test_swiftclient.py @@ -17,7 +17,6 @@ import os import testtools import time import types -import unittest from io import BytesIO from six.moves import configparser @@ -259,7 +258,7 @@ class TestFunctional(testtools.TestCase): self.containername, self.objectname, resp_chunk_size=10) self.assertTrue(isinstance(body, types.GeneratorType)) - self.assertEqual(self.test_data, ''.join(body)) + self.assertEqual(self.test_data, b''.join(body)) def test_post_account(self): self.conn.post_account({'x-account-meta-data': 'Something'}) @@ -284,7 +283,3 @@ class TestFunctional(testtools.TestCase): def test_get_capabilities(self): resp = self.conn.get_capabilities() self.assertTrue(resp.get('swift')) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/unit/test_multithreading.py b/tests/unit/test_multithreading.py index 875e43a..1df0d4f 100644 --- a/tests/unit/test_multithreading.py +++ b/tests/unit/test_multithreading.py @@ -341,7 +341,3 @@ class TestMultiThreadingManager(ThreadTestCase): ], list(err_stream.readlines())) self.assertEqual(3, thread_manager.error_count) - - -if __name__ == '__main__': - testtools.main() diff --git a/tests/test_shell.py b/tests/unit/test_shell.py index 61d970d..0c28297 100644 --- a/tests/test_shell.py +++ b/tests/unit/test_shell.py @@ -217,7 +217,7 @@ class TestShell(unittest.TestCase): content_length=0, headers={'x-object-meta-mtime': mock.ANY}) - # Upload whole directory + # Upload whole directory argv = ["", "upload", "container", "/tmp"] listdir.return_value = [self.tmpfile] swiftclient.shell.main(argv) @@ -239,7 +239,7 @@ class TestShell(unittest.TestCase): '', content_length=0, headers={'x-object-manifest': mock.ANY, - 'x-object-meta-mtime': mock.ANY}) + 'x-object-meta-mtime': mock.ANY}) @mock.patch('swiftclient.shell.Connection') def test_delete_account(self, connection): diff --git a/tests/unit/test_swiftclient.py b/tests/unit/test_swiftclient.py index 4b002f5..283400e 100644 --- a/tests/unit/test_swiftclient.py +++ b/tests/unit/test_swiftclient.py @@ -216,7 +216,7 @@ class TestHttpHelpers(MockHttpTest): # test the default conn.request('GET', '/') ua = req_headers.get('user-agent', 'XXX-MISSING-XXX') - self.assert_(ua.startswith('python-swiftclient-')) + self.assertTrue(ua.startswith('python-swiftclient-')) def test_set_user_agent_per_request_override(self): _junk, conn = c.http_connection('http://www.example.com') @@ -727,8 +727,18 @@ class TestPutObject(MockHttpTest): c.put_object(url='http://www.test.com', http_conn=conn, etag='1234-5678', content_type='text/plain') request_header = resp.requests_params['headers'] - self.assertTrue(request_header['etag'], '1234-5678') - self.assertTrue(request_header['content-type'], 'text/plain') + self.assertEqual(request_header['etag'], b'1234-5678') + self.assertEqual(request_header['content-type'], b'text/plain') + + def test_no_content_type(self): + conn = c.http_connection(u'http://www.test.com/') + resp = MockHttpResponse(status=200) + conn[1].getresponse = resp.fake_response + conn[1]._request = resp._fake_request + + c.put_object(url='http://www.test.com', http_conn=conn) + request_header = resp.requests_params['headers'] + self.assertEqual(request_header['content-type'], b'') class TestPostObject(MockHttpTest): @@ -746,7 +756,10 @@ class TestPostObject(MockHttpTest): u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91') text = u'\u5929\u7a7a\u4e2d\u7684\u4e4c\u4e91' headers = {'X-Header1': text, - 'X-2': '1', 'X-3': {'a': 'b'}, 'a-b': '.x:yz mn:kl:qr'} + b'X-Header2': 'value', + 'X-2': '1', 'X-3': {'a': 'b'}, 'a-b': '.x:yz mn:kl:qr', + 'X-Object-Meta-Header-not-encoded': text, + b'X-Object-Meta-Header-encoded': 'value'} resp = MockHttpResponse() conn[1].getresponse = resp.fake_response @@ -757,6 +770,11 @@ class TestPostObject(MockHttpTest): # Test unicode header self.assertIn(('x-header1', text.encode('utf8')), resp.buffer) + self.assertIn((b'x-object-meta-header-not-encoded', + text.encode('utf8')), resp.buffer) + self.assertIn((b'x-object-meta-header-encoded', b'value'), + resp.buffer) + self.assertIn((b'x-header2', b'value'), resp.buffer) def test_server_error(self): body = 'c' * 60 @@ -1148,7 +1166,3 @@ class TestCloseConnection(MockHttpTest): conn.close() self.assertEqual(http_conn_obj.isclosed(), True) self.assertEqual(conn.http_conn, None) - - -if __name__ == '__main__': - testtools.main() |