summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.rst4
-rw-r--r--doc/source/conf.py68
-rw-r--r--setup.cfg5
-rw-r--r--swiftclient/__init__.py4
-rw-r--r--swiftclient/client.py29
-rwxr-xr-xswiftclient/shell.py4
-rw-r--r--tests/functional/test_swiftclient.py7
-rw-r--r--tests/unit/test_multithreading.py4
-rw-r--r--tests/unit/test_shell.py (renamed from tests/test_shell.py)4
-rw-r--r--tests/unit/test_swiftclient.py30
10 files changed, 88 insertions, 71 deletions
diff --git a/README.rst b/README.rst
index 70abca8..1588564 100644
--- a/README.rst
+++ b/README.rst
@@ -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
diff --git a/setup.cfg b/setup.cfg
index a7f8c2d..f962237 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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()