Hello community,

here is the log from the commit of package python-swiftclient for 
openSUSE:Factory checked in at 2018-03-19 23:36:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-swiftclient (Old)
 and      /work/SRC/openSUSE:Factory/.python-swiftclient.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-swiftclient"

Mon Mar 19 23:36:58 2018 rev:24 rq:583359 version:3.5.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-swiftclient/python-swiftclient.changes    
2018-01-24 15:28:24.352251080 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-swiftclient.new/python-swiftclient.changes   
    2018-03-19 23:36:59.848341900 +0100
@@ -1,0 +2,21 @@
+Fri Feb 23 13:45:16 UTC 2018 - [email protected]
+
+- Switch to stable/queens spec template
+
+-------------------------------------------------------------------
+Mon Feb 12 10:11:31 UTC 2018 - [email protected]
+
+- update to version 3.5.0 (bsc#1078607)
+  - Add support for versionless endpoints
+  - Update tox_install.sh to align for sphinx jobs
+  - Remove setting of version/release from releasenotes
+  - authors/changelog updates for 3.5.0 release
+  - Allow for object uploads > 5GB from stdin.
+  - Trying out the new releasenotes jobs
+  - Make tox runnable in a directory with spaces
+  - Add pypy-devel for RPM-based systems
+  - Add releasenotes tox env
+  - Revert "Add Constraints support"
+  - Allow --meta on upload
+
+-------------------------------------------------------------------

Old:
----
  python-swiftclient-3.4.0.tar.gz

New:
----
  python-swiftclient-3.5.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-swiftclient.spec ++++++
--- /var/tmp/diff_new_pack.cUQOWB/_old  2018-03-19 23:37:00.444320400 +0100
+++ /var/tmp/diff_new_pack.cUQOWB/_new  2018-03-19 23:37:00.448320256 +0100
@@ -17,33 +17,33 @@
 
 
 Name:           python-swiftclient
-Version:        3.4.0
+Version:        3.5.0
 Release:        0
 Summary:        OpenStack Object Storage API Client Library
 License:        Apache-2.0
 Group:          Development/Languages/Python
 Url:            https://launchpad.net/python-swiftclient
-Source0:        
https://files.pythonhosted.org/packages/source/p/python-swiftclient/python-swiftclient-3.4.0.tar.gz
+Source0:        
https://files.pythonhosted.org/packages/source/p/python-swiftclient/python-swiftclient-3.5.0.tar.gz
 BuildRequires:  openstack-macros
 BuildRequires:  python-devel
-BuildRequires:  python2-keystoneclient
-BuildRequires:  python2-mock
-BuildRequires:  python2-pbr
-BuildRequires:  python2-setuptools
-BuildRequires:  python2-testrepository
-BuildRequires:  python2-testscenarios
+BuildRequires:  python2-keystoneclient >= 3.8.0
+BuildRequires:  python2-mock >= 2.0.0
+BuildRequires:  python2-pbr >= 2.0.0
+BuildRequires:  python2-setuptools >= 16.0
+BuildRequires:  python2-testrepository >= 0.0.18
+BuildRequires:  python2-testscenarios >= 0.4
 BuildRequires:  python3-devel
-BuildRequires:  python3-keystoneclient
-BuildRequires:  python3-mock
-BuildRequires:  python3-pbr
-BuildRequires:  python3-setuptools
-BuildRequires:  python3-testrepository
-BuildRequires:  python3-testscenarios
-Requires:       python-requests
-Requires:       python-six
+BuildRequires:  python3-keystoneclient >= 3.8.0
+BuildRequires:  python3-mock >= 2.0.0
+BuildRequires:  python3-pbr >= 2.0.0
+BuildRequires:  python3-setuptools >= 16.0
+BuildRequires:  python3-testrepository >= 0.0.18
+BuildRequires:  python3-testscenarios >= 0.4
+Requires:       python-requests >= 2.14.2
+Requires:       python-six >= 1.10.0
 BuildArch:      noarch
 %ifpython2
-Requires:       python-futures
+Requires:       python-futures >= 3.0.0
 %endif
 %if 0%{?suse_version}
 Requires(post): update-alternatives
@@ -63,8 +63,8 @@
 Summary:        %{summary} - Documentation
 Group:          Documentation/HTML
 BuildRequires:  python-Sphinx
-BuildRequires:  python-futures
-BuildRequires:  python-oslosphinx
+BuildRequires:  python-futures >= 3.0.0
+BuildRequires:  python-oslosphinx >= 4.7.0
 Requires:       %{name} = %{version}
 
 %description -n python-swiftclient-doc
@@ -74,7 +74,7 @@
 This package contains documentation files for %{name}.
 
 %prep
-%autosetup -p1 -n python-swiftclient-3.4.0
+%autosetup -p1 -n python-swiftclient-3.5.0
 %py_req_cleanup
 
 %build

++++++ _service ++++++
--- /var/tmp/diff_new_pack.cUQOWB/_old  2018-03-19 23:37:00.488318813 +0100
+++ /var/tmp/diff_new_pack.cUQOWB/_new  2018-03-19 23:37:00.488318813 +0100
@@ -1,8 +1,8 @@
 <services>
   <service mode="disabled" name="renderspec">
-    <param 
name="input-template">https://raw.githubusercontent.com/openstack/rpm-packaging/master/openstack/python-swiftclient/python-swiftclient.spec.j2</param>
+    <param 
name="input-template">https://raw.githubusercontent.com/openstack/rpm-packaging/stable/queens/openstack/python-swiftclient/python-swiftclient.spec.j2</param>
     <param name="output-name">python-swiftclient.spec</param>
-    <param 
name="requirements">https://raw.githubusercontent.com/openstack/rpm-packaging/master/global-requirements.txt</param>
+    <param 
name="requirements">https://raw.githubusercontent.com/openstack/rpm-packaging/stable/queens/requirements.txt</param>
     <param name="changelog-email">[email protected]</param>
     <param name="changelog-provider">gh,openstack,python-swiftclient</param>
   </service>

++++++ python-swiftclient-3.4.0.tar.gz -> python-swiftclient-3.5.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-swiftclient-3.4.0/AUTHORS 
new/python-swiftclient-3.5.0/AUTHORS
--- old/python-swiftclient-3.4.0/AUTHORS        2017-07-27 09:59:13.000000000 
+0200
+++ new/python-swiftclient-3.5.0/AUTHORS        2018-01-29 18:15:49.000000000 
+0100
@@ -42,6 +42,7 @@
 Greg Holt ([email protected])
 Greg Lange ([email protected])
 groqez ([email protected])
+Hangdong Zhang ([email protected])
 Hemanth Makkapati ([email protected])
 hgangwx ([email protected])
 Hirokazu Sakata ([email protected])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-swiftclient-3.4.0/ChangeLog 
new/python-swiftclient-3.5.0/ChangeLog
--- old/python-swiftclient-3.4.0/ChangeLog      2017-07-27 09:59:13.000000000 
+0200
+++ new/python-swiftclient-3.5.0/ChangeLog      2018-01-29 18:15:49.000000000 
+0100
@@ -1,3 +1,22 @@
+3.5.0
+-----
+
+* Allow for object uploads > 5GB from stdin.
+
+  When uploading from standard input, swiftclient will turn the upload
+  into an SLO in the case of large objects. By default, input larger
+  than 10MB will be uploaded as an SLO with 10MB segment sizes. Users
+  can also supply the ``--segment-size`` option to alter that
+  threshold and the SLO segment size. One segment is buffered in
+  memory (which is why 10MB default was chosen).
+
+* The ``--meta`` option can now be set on the upload command.
+
+* Updated PyPy test dependency references to be more accurate
+  on different distros.
+
+* Various other minor bug fixes and improvements.
+
 3.4.0
 -----
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-swiftclient-3.4.0/PKG-INFO 
new/python-swiftclient-3.5.0/PKG-INFO
--- old/python-swiftclient-3.4.0/PKG-INFO       2017-07-27 10:00:29.000000000 
+0200
+++ new/python-swiftclient-3.5.0/PKG-INFO       2018-01-29 18:17:31.000000000 
+0100
@@ -1,11 +1,12 @@
 Metadata-Version: 1.1
 Name: python-swiftclient
-Version: 3.4.0
+Version: 3.5.0
 Summary: OpenStack Object Storage API Client Library
 Home-page: https://docs.openstack.org/python-swiftclient/latest/
 Author: OpenStack
 Author-email: [email protected]
 License: UNKNOWN
+Description-Content-Type: UNKNOWN
 Description: ========================
         Team and repository tags
         ========================
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-swiftclient-3.4.0/bindep.txt 
new/python-swiftclient-3.5.0/bindep.txt
--- old/python-swiftclient-3.4.0/bindep.txt     2017-07-27 09:59:13.000000000 
+0200
+++ new/python-swiftclient-3.5.0/bindep.txt     2018-01-29 18:15:49.000000000 
+0100
@@ -1,6 +1,6 @@
 # This is a cross-platform list tracking distribution packages needed by tests;
 # see http://docs.openstack.org/infra/bindep/ for additional information.
 
-curl
-pypy [test]
-pypy-dev [test]
+pypy [test !platform:fedora]
+pypy-dev [test platform:dpkg]
+pypy-devel [test platform:rpm !platform:fedora]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-swiftclient-3.4.0/python_swiftclient.egg-info/PKG-INFO 
new/python-swiftclient-3.5.0/python_swiftclient.egg-info/PKG-INFO
--- old/python-swiftclient-3.4.0/python_swiftclient.egg-info/PKG-INFO   
2017-07-27 10:00:28.000000000 +0200
+++ new/python-swiftclient-3.5.0/python_swiftclient.egg-info/PKG-INFO   
2018-01-29 18:17:30.000000000 +0100
@@ -1,11 +1,12 @@
 Metadata-Version: 1.1
 Name: python-swiftclient
-Version: 3.4.0
+Version: 3.5.0
 Summary: OpenStack Object Storage API Client Library
 Home-page: https://docs.openstack.org/python-swiftclient/latest/
 Author: OpenStack
 Author-email: [email protected]
 License: UNKNOWN
+Description-Content-Type: UNKNOWN
 Description: ========================
         Team and repository tags
         ========================
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-swiftclient-3.4.0/python_swiftclient.egg-info/SOURCES.txt 
new/python-swiftclient-3.5.0/python_swiftclient.egg-info/SOURCES.txt
--- old/python-swiftclient-3.4.0/python_swiftclient.egg-info/SOURCES.txt        
2017-07-27 10:00:29.000000000 +0200
+++ new/python-swiftclient-3.5.0/python_swiftclient.egg-info/SOURCES.txt        
2018-01-29 18:17:31.000000000 +0100
@@ -47,6 +47,13 @@
 releasenotes/notes/310-notes-03040158a8683dd8.yaml
 releasenotes/notes/320_notes-bb367dba1053d34c.yaml
 releasenotes/notes/340_notes-1777780bbfdb4d96.yaml
+releasenotes/notes/350_notes-ad0ae19704b2eb88.yaml
+releasenotes/source/conf.py
+releasenotes/source/current.rst
+releasenotes/source/index.rst
+releasenotes/source/newton.rst
+releasenotes/source/ocata.rst
+releasenotes/source/pike.rst
 swiftclient/__init__.py
 swiftclient/authv1.py
 swiftclient/client.py
@@ -69,5 +76,4 @@
 tests/unit/test_shell.py
 tests/unit/test_swiftclient.py
 tests/unit/test_utils.py
-tests/unit/utils.py
-tools/tox_install.sh
\ No newline at end of file
+tests/unit/utils.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-swiftclient-3.4.0/python_swiftclient.egg-info/pbr.json 
new/python-swiftclient-3.5.0/python_swiftclient.egg-info/pbr.json
--- old/python-swiftclient-3.4.0/python_swiftclient.egg-info/pbr.json   
2017-07-27 10:00:28.000000000 +0200
+++ new/python-swiftclient-3.5.0/python_swiftclient.egg-info/pbr.json   
2018-01-29 18:17:30.000000000 +0100
@@ -1 +1 @@
-{"git_version": "e1945ea", "is_release": true}
\ No newline at end of file
+{"git_version": "b91651e", "is_release": true}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-swiftclient-3.4.0/releasenotes/notes/350_notes-ad0ae19704b2eb88.yaml 
new/python-swiftclient-3.5.0/releasenotes/notes/350_notes-ad0ae19704b2eb88.yaml
--- 
old/python-swiftclient-3.4.0/releasenotes/notes/350_notes-ad0ae19704b2eb88.yaml 
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/python-swiftclient-3.5.0/releasenotes/notes/350_notes-ad0ae19704b2eb88.yaml 
    2018-01-29 18:15:49.000000000 +0100
@@ -0,0 +1,18 @@
+---
+features:
+  - |
+    Allow for object uploads > 5GB from stdin.
+
+    When uploading from standard input, swiftclient will turn the upload
+    into an SLO in the case of large objects. By default, input larger
+    than 10MB will be uploaded as an SLO with 10MB segment sizes. Users
+    can also supply the ``--segment-size`` option to alter that
+    threshold and the SLO segment size. One segment is buffered in
+    memory (which is why 10MB default was chosen).
+
+  - |
+    The ``--meta`` option can now be set on the upload command.
+
+  - |
+    Updated PyPy test dependency references to be more accurate
+    on different distros.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-swiftclient-3.4.0/releasenotes/source/conf.py 
new/python-swiftclient-3.5.0/releasenotes/source/conf.py
--- old/python-swiftclient-3.4.0/releasenotes/source/conf.py    1970-01-01 
01:00:00.000000000 +0100
+++ new/python-swiftclient-3.5.0/releasenotes/source/conf.py    2018-01-29 
18:15:49.000000000 +0100
@@ -0,0 +1,356 @@
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# swift documentation build configuration file, created by
+# sphinx-quickstart on Mon Oct  3 17:01:55 2016.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# 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.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+import datetime
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    'reno.sphinxext',
+    'openstackdocstheme',
+]
+
+# Add any paths that contain templates here, relative to this directory.
+# templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The encoding of source files.
+#
+# source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Swift Client Release Notes'
+copyright = u'%d, OpenStack Foundation' % datetime.datetime.now().year
+
+# Release notes are version independent.
+# The short X.Y version.
+version = ''
+# The full version, including alpha/beta/rc tags.
+release = ''
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#
+# today = ''
+#
+# Else, today_fmt is used as the format for a strftime call.
+#
+# today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This patterns also effect to html_static_path and html_extra_path
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+#
+# default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#
+# 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
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#
+# 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 = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+# keep_warnings = False
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+# todo_include_todos = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'openstackdocs'
+
+# 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 = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+# html_theme_path = []
+
+# The name for this set of Sphinx documents.
+# "<project> v<release> documentation" by default.
+#
+# html_title = u'swift v2.10.0'
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#
+# 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
+
+# The name of an image file (relative to this directory) to use as a favicon of
+# the docs.  This file should be a Windows icon file (.ico) being 16x16 or 
32x32
+# pixels large.
+#
+# 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,
+# so a file named "default.css" will overwrite the builtin "default.css".
+# html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+#
+# html_extra_path = []
+
+# 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 = '%Y-%m-%d %H:%M'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#
+# html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+#
+# html_domain_indices = True
+
+# If false, no index is generated.
+#
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#
+# html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#
+# html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#
+# html_show_copyright = 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 = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+# html_file_suffix = None
+
+# Language to be used for generating the HTML full-text search index.
+# Sphinx supports the following languages:
+#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
+#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
+#
+# html_search_language = 'en'
+
+# A dictionary with options for the search language support, empty by default.
+# 'ja' uses this config value.
+# 'zh' user can custom change `jieba` dictionary path.
+#
+# html_search_options = {'type': 'default'}
+
+# The name of a javascript file (relative to the configuration directory) that
+# implements a search results scorer. If empty, the default will be used.
+#
+# html_search_scorer = 'scorer.js'
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'SwiftClientReleaseNotesdoc'
+
+# -- Options for LaTeX output ---------------------------------------------
+
+# latex_elements = {
+#      # The paper size ('letterpaper' or 'a4paper').
+#      #
+#      # 'papersize': 'letterpaper',
+
+#      # The font size ('10pt', '11pt' or '12pt').
+#      #
+#      # 'pointsize': '10pt',
+
+#      # Additional stuff for the LaTeX preamble.
+#      #
+#      # 'preamble': '',
+
+#      # Latex figure (float) alignment
+#      #
+#      # 'figure_align': 'htbp',
+# }
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+# latex_documents = [
+#     (master_doc, 'swift.tex', u'swift Documentation',
+#      u'swift', 'manual'),
+# ]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#
+# latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#
+# latex_use_parts = False
+
+# If true, show page references after internal links.
+#
+# latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#
+# latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#
+# latex_appendices = []
+
+# It false, will not define \strong, \code,    itleref, \crossref ... but only
+# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added
+# packages.
+#
+# latex_keep_old_macro_names = True
+
+# If false, no module index is generated.
+#
+# latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+# man_pages = [
+#     (master_doc, 'swift', u'swift Documentation',
+#      [author], 1)
+# ]
+
+# If true, show URL addresses after external links.
+#
+# man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+# texinfo_documents = [
+#     (master_doc, 'swift', u'swift Documentation',
+#      author, 'swift', 'One line description of project.',
+#      'Miscellaneous'),
+# ]
+
+# Documents to append as an appendix to all manuals.
+#
+# texinfo_appendices = []
+
+# If false, no module index is generated.
+#
+# texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#
+# texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+#
+# texinfo_no_detailmenu = False
+
+locale_dirs = ['locale/']
+
+# -- Options for openstackdocstheme -------------------------------------------
+repository_name = 'openstack/python-swiftclient'
+bug_project = 'python-swiftclient'
+bug_tag = ''
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-swiftclient-3.4.0/releasenotes/source/current.rst 
new/python-swiftclient-3.5.0/releasenotes/source/current.rst
--- old/python-swiftclient-3.4.0/releasenotes/source/current.rst        
1970-01-01 01:00:00.000000000 +0100
+++ new/python-swiftclient-3.5.0/releasenotes/source/current.rst        
2018-01-29 18:15:49.000000000 +0100
@@ -0,0 +1,5 @@
+====================================
+ Current (Unreleased) Release Notes
+====================================
+
+.. release-notes::
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-swiftclient-3.4.0/releasenotes/source/index.rst 
new/python-swiftclient-3.5.0/releasenotes/source/index.rst
--- old/python-swiftclient-3.4.0/releasenotes/source/index.rst  1970-01-01 
01:00:00.000000000 +0100
+++ new/python-swiftclient-3.5.0/releasenotes/source/index.rst  2018-01-29 
18:15:49.000000000 +0100
@@ -0,0 +1,11 @@
+============================
+ Swift Client Release Notes
+============================
+
+.. toctree::
+   :maxdepth: 1
+
+   current
+   pike
+   ocata
+   newton
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-swiftclient-3.4.0/releasenotes/source/newton.rst 
new/python-swiftclient-3.5.0/releasenotes/source/newton.rst
--- old/python-swiftclient-3.4.0/releasenotes/source/newton.rst 1970-01-01 
01:00:00.000000000 +0100
+++ new/python-swiftclient-3.5.0/releasenotes/source/newton.rst 2018-01-29 
18:15:49.000000000 +0100
@@ -0,0 +1,6 @@
+=============================
+ Newton Series Release Notes
+=============================
+
+.. release-notes::
+   :branch: stable/newton
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-swiftclient-3.4.0/releasenotes/source/ocata.rst 
new/python-swiftclient-3.5.0/releasenotes/source/ocata.rst
--- old/python-swiftclient-3.4.0/releasenotes/source/ocata.rst  1970-01-01 
01:00:00.000000000 +0100
+++ new/python-swiftclient-3.5.0/releasenotes/source/ocata.rst  2018-01-29 
18:15:49.000000000 +0100
@@ -0,0 +1,6 @@
+============================
+ Ocata Series Release Notes
+============================
+
+.. release-notes::
+   :branch: stable/ocata
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-swiftclient-3.4.0/releasenotes/source/pike.rst 
new/python-swiftclient-3.5.0/releasenotes/source/pike.rst
--- old/python-swiftclient-3.4.0/releasenotes/source/pike.rst   1970-01-01 
01:00:00.000000000 +0100
+++ new/python-swiftclient-3.5.0/releasenotes/source/pike.rst   2018-01-29 
18:15:49.000000000 +0100
@@ -0,0 +1,6 @@
+===========================
+ Pike Series Release Notes
+===========================
+
+.. release-notes::
+   :branch: stable/pike
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-swiftclient-3.4.0/swiftclient/client.py 
new/python-swiftclient-3.5.0/swiftclient/client.py
--- old/python-swiftclient-3.4.0/swiftclient/client.py  2017-07-27 
09:59:13.000000000 +0200
+++ new/python-swiftclient-3.5.0/swiftclient/client.py  2018-01-29 
18:15:49.000000000 +0100
@@ -25,7 +25,7 @@
 from requests.exceptions import RequestException, SSLError
 from six.moves import http_client
 from six.moves.urllib.parse import quote as _quote, unquote
-from six.moves.urllib.parse import urlparse, urlunparse
+from six.moves.urllib.parse import urljoin, urlparse, urlunparse
 from time import sleep, time
 import six
 
@@ -550,9 +550,22 @@
 
     insecure = kwargs.get('insecure', False)
     timeout = kwargs.get('timeout', None)
-    auth_version = kwargs.get('auth_version', '2.0')
+    auth_version = kwargs.get('auth_version', None)
     debug = logger.isEnabledFor(logging.DEBUG)
 
+    # Add the version suffix in case of versionless Keystone endpoints. If
+    # auth_version is also unset it is likely that it is v3
+    if len(urlparse(auth_url).path) <= 1:
+        if auth_version and auth_version in AUTH_VERSIONS_V2:
+            auth_url = urljoin(auth_url, "v2.0")
+        else:
+            auth_url = urljoin(auth_url, "v3")
+            auth_version = '3'
+        logger.debug("Versionless auth_url - using %s as endpoint" % auth_url)
+
+    # Legacy default if not set
+    if auth_version is None:
+        auth_version = 'v2.0'
     ksclient, exceptions = _import_keystone_client(auth_version)
 
     try:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-swiftclient-3.4.0/swiftclient/service.py 
new/python-swiftclient-3.5.0/swiftclient/service.py
--- old/python-swiftclient-3.4.0/swiftclient/service.py 2017-07-27 
09:59:13.000000000 +0200
+++ new/python-swiftclient-3.5.0/swiftclient/service.py 2018-01-29 
18:15:49.000000000 +0100
@@ -1502,7 +1502,8 @@
             if hasattr(s, 'read'):
                 # We've got a file like object to upload to o
                 file_future = self.thread_manager.object_uu_pool.submit(
-                    self._upload_object_job, container, s, o, object_options
+                    self._upload_object_job, container, s, o, object_options,
+                    results_queue=rq
                 )
                 details['file'] = s
                 details['object'] = o
@@ -1784,6 +1785,132 @@
             if fp is not None:
                 fp.close()
 
+    @staticmethod
+    def _put_object(conn, container, name, content, headers=None, md5=None):
+        """
+        Upload object into a given container and verify the resulting ETag, if
+        the md5 optional parameter is passed.
+
+        :param conn: The Swift connection to use for uploads.
+        :param container: The container to put the object into.
+        :param name: The name of the object.
+        :param content: Object content.
+        :param headers: Headers (optional) to associate with the object.
+        :param md5: MD5 sum of the content. If passed in, will be used to
+                    verify the returned ETag.
+
+        :returns: A dictionary as the response from calling put_object.
+                  The keys are:
+                    - status
+                    - reason
+                    - headers
+                  On error, the dictionary contains the following keys:
+                    - success (with value False)
+                    - error - the encountered exception (object)
+                    - error_timestamp
+                    - response_dict - results from the put_object call, as
+                      documented above
+                    - attempts - number of attempts made
+        """
+        if headers is None:
+            headers = {}
+        else:
+            headers = dict(headers)
+        if md5 is not None:
+            headers['etag'] = md5
+        results = {}
+        try:
+            etag = conn.put_object(
+                container, name, content, content_length=len(content),
+                headers=headers, response_dict=results)
+            if md5 is not None and etag != md5:
+                raise SwiftError('Upload verification failed for {0}: md5 '
+                                 'mismatch {1} != {2}'.format(name, md5, etag))
+            results['success'] = True
+        except Exception as err:
+            traceback, err_time = report_traceback()
+            logger.exception(err)
+            return {
+                'success': False,
+                'error': err,
+                'error_timestamp': err_time,
+                'response_dict': results,
+                'attempts': conn.attempts,
+                'traceback': traceback
+            }
+        return results
+
+    @staticmethod
+    def _upload_stream_segment(conn, container, object_name,
+                               segment_container, segment_name,
+                               segment_size, segment_index,
+                               headers, fd):
+        """
+        Upload a segment from a stream, buffering it in memory first. The
+        resulting object is placed either as a segment in the segment
+        container, or if it is smaller than a single segment, as the given
+        object name.
+
+        :param conn: Swift Connection to use.
+        :param container: Container in which the object would be placed.
+        :param object_name: Name of the final object (used in case the stream
+                            is smaller than the segment_size)
+        :param segment_container: Container to hold the object segments.
+        :param segment_name: The name of the segment.
+        :param segment_size: Minimum segment size.
+        :param segment_index: The segment index.
+        :param headers: Headers to attach to the segment/object.
+        :param fd: File-like handle for the content. Must implement read().
+
+        :returns: Dictionary, containing the following keys:
+                    - complete -- whether the stream is exhausted
+                    - segment_size - the actual size of the segment (may be
+                                     smaller than the passed in segment_size)
+                    - segment_location - path to the segment
+                    - segment_index - index of the segment
+                    - segment_etag - the ETag for the segment
+        """
+        buf = []
+        dgst = md5()
+        bytes_read = 0
+        while bytes_read < segment_size:
+            data = fd.read(segment_size - bytes_read)
+            if not data:
+                break
+            bytes_read += len(data)
+            dgst.update(data)
+            buf.append(data)
+        buf = b''.join(buf)
+        segment_hash = dgst.hexdigest()
+
+        if not buf and segment_index > 0:
+            # Happens if the segment size aligns with the object size
+            return {'complete': True,
+                    'segment_size': 0,
+                    'segment_index': None,
+                    'segment_etag': None,
+                    'segment_location': None,
+                    'success': True}
+
+        if segment_index == 0 and len(buf) < segment_size:
+            ret = SwiftService._put_object(
+                conn, container, object_name, buf, headers, segment_hash)
+            ret['segment_location'] = '/%s/%s' % (container, object_name)
+        else:
+            ret = SwiftService._put_object(
+                conn, segment_container, segment_name, buf, headers,
+                segment_hash)
+            ret['segment_location'] = '/%s/%s' % (
+                segment_container, segment_name)
+
+        ret.update(
+            dict(complete=len(buf) < segment_size,
+                 segment_size=len(buf),
+                 segment_index=segment_index,
+                 segment_etag=segment_hash,
+                 for_object=object_name))
+        return ret
+
     def _get_chunk_data(self, conn, container, obj, headers, manifest=None):
         chunks = []
         if 'x-object-manifest' in headers:
@@ -1833,6 +1960,47 @@
             # Each chunk is verified; check that we're at the end of the file
             return not fp.read(1)
 
+    @staticmethod
+    def _upload_slo_manifest(conn, segment_results, container, obj, headers):
+        """
+        Upload an SLO manifest, given the results of uploading each segment, to
+        the specified container.
+
+        :param segment_results: List of response_dict structures, as populated
+                                by _upload_segment_job. Specifically, each
+                                entry must container the following keys:
+                                - segment_location
+                                - segment_etag
+                                - segment_size
+                                - segment_index
+        :param container: The container to put the manifest into.
+        :param obj: The name of the manifest object to use.
+        :param headers: Optional set of headers to attach to the manifest.
+        """
+        if headers is None:
+            headers = {}
+        segment_results.sort(key=lambda di: di['segment_index'])
+        for seg in segment_results:
+            seg_loc = seg['segment_location'].lstrip('/')
+            if isinstance(seg_loc, text_type):
+                seg_loc = seg_loc.encode('utf-8')
+
+        manifest_data = json.dumps([
+            {
+                'path': d['segment_location'],
+                'etag': d['segment_etag'],
+                'size_bytes': d['segment_size']
+            } for d in segment_results
+        ])
+
+        response = {}
+        conn.put_object(
+            container, obj, manifest_data,
+            headers=headers,
+            query_string='multipart-manifest=put',
+            response_dict=response)
+        return response
+
     def _upload_object_job(self, conn, container, source, obj, options,
                            results_queue=None):
         if obj.startswith('./') or obj.startswith('.\\'):
@@ -1917,6 +2085,8 @@
                         return res
 
             # Merge the command line header options to the put_headers
+            put_headers.update(split_headers(
+                options['meta'], 'X-Object-Meta-'))
             put_headers.update(split_headers(options['header'], ''))
 
             # Don't do segment job if object is not big enough, and never do
@@ -1988,29 +2158,11 @@
                 res['segment_results'] = segment_results
 
                 if options['use_slo']:
-                    segment_results.sort(key=lambda di: di['segment_index'])
-                    for seg in segment_results:
-                        seg_loc = seg['segment_location'].lstrip('/')
-                        if isinstance(seg_loc, text_type):
-                            seg_loc = seg_loc.encode('utf-8')
-                        new_slo_manifest_paths.add(seg_loc)
-
-                    manifest_data = json.dumps([
-                        {
-                            'path': d['segment_location'],
-                            'etag': d['segment_etag'],
-                            'size_bytes': d['segment_size']
-                        } for d in segment_results
-                    ])
-
-                    mr = {}
-                    conn.put_object(
-                        container, obj, manifest_data,
-                        headers=put_headers,
-                        query_string='multipart-manifest=put',
-                        response_dict=mr
-                    )
-                    res['manifest_response_dict'] = mr
+                    response = self._upload_slo_manifest(
+                        conn, segment_results, container, obj, put_headers)
+                    res['manifest_response_dict'] = response
+                    new_slo_manifest_paths = {
+                        seg['segment_location'] for seg in segment_results}
                 else:
                     new_object_manifest = '%s/%s/%s/%s/%s/' % (
                         quote(seg_container.encode('utf8')),
@@ -2028,6 +2180,51 @@
                         response_dict=mr
                     )
                     res['manifest_response_dict'] = mr
+            elif options['use_slo'] and segment_size and not path:
+                segment = 0
+                results = []
+                while True:
+                    segment_name = '%s/slo/%s/%s/%08d' % (
+                        obj, put_headers['x-object-meta-mtime'],
+                        segment_size, segment
+                    )
+                    seg_container = container + '_segments'
+                    if options['segment_container']:
+                        seg_container = options['segment_container']
+                    ret = self._upload_stream_segment(
+                        conn, container, obj,
+                        seg_container,
+                        segment_name,
+                        segment_size,
+                        segment,
+                        put_headers,
+                        stream
+                    )
+                    if not ret['success']:
+                        return ret
+                    if (ret['complete'] and segment == 0) or\
+                            ret['segment_size'] > 0:
+                        results.append(ret)
+                    if results_queue is not None:
+                        # Don't insert the 0-sized segments or objects
+                        # themselves
+                        if ret['segment_location'] != '/%s/%s' % (
+                                container, obj) and ret['segment_size'] > 0:
+                            results_queue.put(ret)
+                    if ret['complete']:
+                        break
+                    segment += 1
+                if results[0]['segment_location'] != '/%s/%s' % (
+                        container, obj):
+                    response = self._upload_slo_manifest(
+                        conn, results, container, obj, put_headers)
+                    res['manifest_response_dict'] = response
+                    new_slo_manifest_paths = {
+                        r['segment_location'] for r in results}
+                    res['large_object'] = True
+                else:
+                    res['response_dict'] = ret
+                    res['large_object'] = False
             else:
                 res['large_object'] = False
                 obr = {}
@@ -2061,7 +2258,6 @@
                 finally:
                     if fp is not None:
                         fp.close()
-
             if old_manifest or old_slo_manifest_paths:
                 drs = []
                 delobjsmap = {}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-swiftclient-3.4.0/swiftclient/shell.py 
new/python-swiftclient-3.5.0/swiftclient/shell.py
--- old/python-swiftclient-3.4.0/swiftclient/shell.py   2017-07-27 
09:59:13.000000000 +0200
+++ new/python-swiftclient-3.5.0/swiftclient/shell.py   2018-01-29 
18:15:49.000000000 +0100
@@ -891,8 +891,8 @@
 st_upload_options = '''[--changed] [--skip-identical] [--segment-size <size>]
                     [--segment-container <container>] [--leave-segments]
                     [--object-threads <thread>] [--segment-threads <threads>]
-                    [--header <header>] [--use-slo] [--ignore-checksum]
-                    [--object-name <object-name>]
+                    [--meta <name:value>] [--header <header>] [--use-slo]
+                    [--ignore-checksum] [--object-name <object-name>]
                     <container> <file_or_directory> [<file_or_directory>] [...]
 '''
 
@@ -928,6 +928,9 @@
   --segment-threads <threads>
                         Number of threads to use for uploading object segments.
                         Default is 10.
+  -m, --meta <name:value>
+                        Sets a meta data item. This option may be repeated.
+                        Example: -m Color:Blue -m Size:Large
   -H, --header <header:value>
                         Adds a customized request header. This option may be
                         repeated. Example: -H "content-type:text/plain"
@@ -944,6 +947,8 @@
 
 
 def st_upload(parser, args, output_manager):
+    DEFAULT_STDIN_SEGMENT = 10 * 1024 * 1024
+
     parser.add_argument(
         '-c', '--changed', action='store_true', dest='changed',
         default=False, help='Only upload files that have changed since '
@@ -979,6 +984,10 @@
         help='Number of threads to use for uploading object segments. '
         'Its value must be a positive integer. Default is 10.')
     parser.add_argument(
+        '-m', '--meta', action='append', dest='meta', default=[],
+        help='Sets a meta data item. This option may be repeated. '
+        'Example: -m Color:Blue -m Size:Large')
+    parser.add_argument(
         '-H', '--header', action='append', dest='header',
         default=[], help='Set request headers with the syntax header:value. '
         ' This option may be repeated. Example: -H "content-type:text/plain" '
@@ -1053,6 +1062,12 @@
             st_upload_help)
         return
 
+    if from_stdin:
+        if not options['use_slo']:
+            options['use_slo'] = True
+        if not options['segment_size']:
+            options['segment_size'] = DEFAULT_STDIN_SEGMENT
+
     options['object_uu_threads'] = options['object_threads']
     with SwiftService(options=options) as swift:
         try:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-swiftclient-3.4.0/test-requirements.txt 
new/python-swiftclient-3.5.0/test-requirements.txt
--- old/python-swiftclient-3.4.0/test-requirements.txt  2017-07-27 
09:59:13.000000000 +0200
+++ new/python-swiftclient-3.5.0/test-requirements.txt  2018-01-29 
18:15:49.000000000 +0100
@@ -5,3 +5,5 @@
 oslosphinx>=4.7.0  # Apache-2.0
 sphinx>=1.1.2,<1.2
 testrepository>=0.0.18
+reno>=1.8.0,!=2.3.1 # Apache-2.0
+openstackdocstheme>=1.16.0 # Apache-2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-swiftclient-3.4.0/tests/unit/test_service.py 
new/python-swiftclient-3.5.0/tests/unit/test_service.py
--- old/python-swiftclient-3.4.0/tests/unit/test_service.py     2017-07-27 
09:59:13.000000000 +0200
+++ new/python-swiftclient-3.5.0/tests/unit/test_service.py     2018-01-29 
18:15:49.000000000 +0100
@@ -36,6 +36,8 @@
     SwiftService, SwiftError, SwiftUploadObject
 )
 
+from tests.unit import utils as test_utils
+
 
 clean_os_environ = {}
 environ_prefixes = ('ST_', 'OS_')
@@ -1088,6 +1090,83 @@
                 self.assertEqual(upload_obj_resp['path'], obj['path'])
                 self.assertTrue(mock_open.return_value.closed)
 
+    @mock.patch('swiftclient.service.Connection')
+    def test_upload_stream(self, mock_conn):
+        service = SwiftService({})
+
+        stream = test_utils.FakeStream(2048)
+        segment_etag = md5(b'A' * 1024).hexdigest()
+
+        mock_conn.return_value.head_object.side_effect = \
+            ClientException('Not Found', http_status=404)
+        mock_conn.return_value.put_object.return_value = \
+            segment_etag
+        options = {'use_slo': True, 'segment_size': 1024}
+        resp_iter = service.upload(
+            'container',
+            [SwiftUploadObject(stream, object_name='streamed')],
+            options)
+        responses = [x for x in resp_iter]
+        for resp in responses:
+            self.assertFalse('error' in resp)
+            self.assertTrue(resp['success'])
+        self.assertEqual(5, len(responses))
+        container_resp, segment_container_resp = responses[0:2]
+        segment_response = responses[2:4]
+        upload_obj_resp = responses[-1]
+        self.assertEqual(container_resp['action'],
+                         'create_container')
+        self.assertEqual(upload_obj_resp['action'],
+                         'upload_object')
+        self.assertEqual(upload_obj_resp['object'],
+                         'streamed')
+        self.assertTrue(upload_obj_resp['path'] is None)
+        self.assertTrue(upload_obj_resp['large_object'])
+        self.assertIn('manifest_response_dict', upload_obj_resp)
+        self.assertEqual(upload_obj_resp['manifest_response_dict'], {})
+        for i, resp in enumerate(segment_response):
+            self.assertEqual(i, resp['segment_index'])
+            self.assertEqual(1024, resp['segment_size'])
+            self.assertEqual('d47b127bc2de2d687ddc82dac354c415',
+                             resp['segment_etag'])
+            self.assertTrue(resp['segment_location'].endswith(
+                '/0000000%d' % i))
+            self.assertTrue(resp['segment_location'].startswith(
+                '/container_segments/streamed'))
+
+    @mock.patch('swiftclient.service.Connection')
+    def test_upload_stream_fits_in_one_segment(self, mock_conn):
+        service = SwiftService({})
+
+        stream = test_utils.FakeStream(2048)
+        whole_etag = md5(b'A' * 2048).hexdigest()
+
+        mock_conn.return_value.head_object.side_effect = \
+            ClientException('Not Found', http_status=404)
+        mock_conn.return_value.put_object.return_value = \
+            whole_etag
+        options = {'use_slo': True, 'segment_size': 10240}
+        resp_iter = service.upload(
+            'container',
+            [SwiftUploadObject(stream, object_name='streamed')],
+            options)
+        responses = [x for x in resp_iter]
+        for resp in responses:
+            self.assertNotIn('error', resp)
+            self.assertTrue(resp['success'])
+        self.assertEqual(3, len(responses))
+        container_resp, segment_container_resp = responses[0:2]
+        upload_obj_resp = responses[-1]
+        self.assertEqual(container_resp['action'],
+                         'create_container')
+        self.assertEqual(upload_obj_resp['action'],
+                         'upload_object')
+        self.assertEqual(upload_obj_resp['object'],
+                         'streamed')
+        self.assertTrue(upload_obj_resp['path'] is None)
+        self.assertFalse(upload_obj_resp['large_object'])
+        self.assertNotIn('manifest_response_dict', upload_obj_resp)
+
 
 class TestServiceUpload(_TestServiceBase):
 
@@ -1146,14 +1225,9 @@
                                          container='test_c',
                                          source=f.name,
                                          obj='ใƒ†ใ‚นใƒˆ/dummy.dat',
-                                         options={'changed': False,
-                                                  'skip_identical': False,
-                                                  'leave_segments': True,
-                                                  'header': '',
-                                                  'segment_size': 10,
-                                                  'segment_container': None,
-                                                  'use_slo': False,
-                                                  'checksum': True})
+                                         options=dict(s._options,
+                                                      segment_size=10,
+                                                      leave_segments=True))
 
             mtime = r['headers']['x-object-meta-mtime']
             self.assertEqual(expected_mtime, mtime)
@@ -1231,6 +1305,141 @@
             self.assertIsInstance(contents, utils.LengthWrapper)
             self.assertEqual(len(contents), 10)
 
+    def test_upload_stream_segment(self):
+        common_params = {
+            'segment_container': 'segments',
+            'segment_name': 'test_stream_2',
+            'container': 'test_stream',
+            'object': 'stream_object',
+        }
+        tests = [
+            {'test_params': {
+                'segment_size': 1024,
+                'segment_index': 2,
+                'content_size': 1024},
+             'put_object_args': {
+                'container': 'segments',
+                'object': 'test_stream_2'},
+             'expected': {
+                'complete': False,
+                'segment_etag': md5(b'A' * 1024).hexdigest()}},
+            {'test_params': {
+                'segment_size': 2048,
+                'segment_index': 0,
+                'content_size': 512},
+             'put_object_args': {
+                'container': 'test_stream',
+                'object': 'stream_object'},
+             'expected': {
+                'complete': True,
+                'segment_etag': md5(b'A' * 512).hexdigest()}},
+            # 0-sized segment should not be uploaded
+            {'test_params': {
+                'segment_size': 1024,
+                'segment_index': 1,
+                'content_size': 0},
+             'put_object_args': {},
+             'expected': {
+                'complete': True}},
+            # 0-sized objects should be uploaded
+            {'test_params': {
+                'segment_size': 1024,
+                'segment_index': 0,
+                'content_size': 0},
+             'put_object_args': {
+                'container': 'test_stream',
+                'object': 'stream_object'},
+             'expected': {
+                'complete': True,
+                'segment_etag': md5(b'').hexdigest()}},
+            # Test boundary conditions
+            {'test_params': {
+                'segment_size': 1024,
+                'segment_index': 1,
+                'content_size': 1023},
+             'put_object_args': {
+                'container': 'segments',
+                'object': 'test_stream_2'},
+             'expected': {
+                'complete': True,
+                'segment_etag': md5(b'A' * 1023).hexdigest()}},
+            {'test_params': {
+                'segment_size': 2048,
+                'segment_index': 0,
+                'content_size': 2047},
+             'put_object_args': {
+                'container': 'test_stream',
+                'object': 'stream_object'},
+             'expected': {
+                'complete': True,
+                'segment_etag': md5(b'A' * 2047).hexdigest()}},
+            {'test_params': {
+                'segment_size': 1024,
+                'segment_index': 2,
+                'content_size': 1025},
+             'put_object_args': {
+                'container': 'segments',
+                'object': 'test_stream_2'},
+             'expected': {
+                'complete': False,
+                'segment_etag': md5(b'A' * 1024).hexdigest()}},
+        ]
+
+        for test_args in tests:
+            params = test_args['test_params']
+            stream = test_utils.FakeStream(params['content_size'])
+            segment_size = params['segment_size']
+            segment_index = params['segment_index']
+
+            def _fake_put_object(*args, **kwargs):
+                contents = args[2]
+                # Consume and compute md5
+                return md5(contents).hexdigest()
+
+            mock_conn = mock.Mock()
+            mock_conn.put_object.side_effect = _fake_put_object
+
+            s = SwiftService()
+            resp = s._upload_stream_segment(
+                conn=mock_conn,
+                container=common_params['container'],
+                object_name=common_params['object'],
+                segment_container=common_params['segment_container'],
+                segment_name=common_params['segment_name'],
+                segment_size=segment_size,
+                segment_index=segment_index,
+                headers={},
+                fd=stream)
+            expected_args = test_args['expected']
+            put_args = test_args['put_object_args']
+            expected_response = {
+                'segment_size': min(len(stream), segment_size),
+                'complete': expected_args['complete'],
+                'success': True,
+            }
+            if len(stream) or segment_index == 0:
+                segment_location = '/%s/%s' % (put_args['container'],
+                                               put_args['object'])
+                expected_response.update(
+                    {'segment_index': segment_index,
+                     'segment_location': segment_location,
+                     'segment_etag': expected_args['segment_etag'],
+                     'for_object': common_params['object']})
+                mock_conn.put_object.assert_called_once_with(
+                    put_args['container'],
+                    put_args['object'],
+                    mock.ANY,
+                    content_length=min(len(stream), segment_size),
+                    headers={'etag': expected_args['segment_etag']},
+                    response_dict=mock.ANY)
+            else:
+                self.assertEqual([], mock_conn.put_object.mock_calls)
+                expected_response.update(
+                    {'segment_index': None,
+                     'segment_location': None,
+                     'segment_etag': None})
+            self.assertEqual(expected_response, resp)
+
     def test_etag_mismatch_with_ignore_checksum(self):
         def _consuming_conn(*a, **kw):
             contents = a[2]
@@ -1350,12 +1559,8 @@
                                          container='test_c',
                                          source=f.name,
                                          obj='test_o',
-                                         options={'changed': False,
-                                                  'skip_identical': False,
-                                                  'leave_segments': True,
-                                                  'header': '',
-                                                  'segment_size': 0,
-                                                  'checksum': True})
+                                         options=dict(s._options,
+                                                      leave_segments=True))
 
             mtime = r['headers']['x-object-meta-mtime']
             self.assertEqual(expected_mtime, mtime)
@@ -1405,12 +1610,8 @@
                                      container='test_c',
                                      source=f,
                                      obj='test_o',
-                                     options={'changed': False,
-                                              'skip_identical': False,
-                                              'leave_segments': True,
-                                              'header': '',
-                                              'segment_size': 0,
-                                              'checksum': True})
+                                     options=dict(s._options,
+                                                  leave_segments=True))
 
             mtime = float(r['headers']['x-object-meta-mtime'])
             self.assertEqual(mtime, expected_mtime)
@@ -1452,12 +1653,8 @@
                                      container='test_c',
                                      source=f.name,
                                      obj='test_o',
-                                     options={'changed': False,
-                                              'skip_identical': False,
-                                              'leave_segments': True,
-                                              'header': '',
-                                              'segment_size': 0,
-                                              'checksum': True})
+                                     options=dict(s._options,
+                                                  leave_segments=True))
 
             self.assertIs(r['success'], False)
             self.assertIn('md5 mismatch', str(r.get('error')))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-swiftclient-3.4.0/tests/unit/test_shell.py 
new/python-swiftclient-3.5.0/tests/unit/test_shell.py
--- old/python-swiftclient-3.4.0/tests/unit/test_shell.py       2017-07-27 
09:59:13.000000000 +0200
+++ new/python-swiftclient-3.5.0/tests/unit/test_shell.py       2018-01-29 
18:15:49.000000000 +0100
@@ -624,7 +624,8 @@
         connection.return_value.put_object.return_value = EMPTY_ETAG
         connection.return_value.attempts = 0
         argv = ["", "upload", "container", self.tmpfile,
-                "-H", "X-Storage-Policy:one"]
+                "-H", "X-Storage-Policy:one",
+                "--meta", "Color:Blue"]
         swiftclient.shell.main(argv)
         connection.return_value.put_container.assert_called_once_with(
             'container',
@@ -637,7 +638,8 @@
             mock.ANY,
             content_length=0,
             headers={'x-object-meta-mtime': mock.ANY,
-                     'X-Storage-Policy': 'one'},
+                     'X-Storage-Policy': 'one',
+                     'X-Object-Meta-Color': 'Blue'},
             response_dict={})
 
         # upload to pseudo-folder (via <container> param)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-swiftclient-3.4.0/tests/unit/test_swiftclient.py 
new/python-swiftclient-3.5.0/tests/unit/test_swiftclient.py
--- old/python-swiftclient-3.4.0/tests/unit/test_swiftclient.py 2017-07-27 
09:59:13.000000000 +0200
+++ new/python-swiftclient-3.5.0/tests/unit/test_swiftclient.py 2018-01-29 
18:15:49.000000000 +0100
@@ -575,6 +575,26 @@
         self.assertTrue(url.startswith("http"))
         self.assertTrue(token)
 
+    def test_get_auth_keystone_versionless(self):
+        fake_ks = FakeKeystone(endpoint='http://some_url', token='secret')
+
+        with mock.patch('swiftclient.client._import_keystone_client',
+                        _make_fake_import_keystone_client(fake_ks)):
+            c.get_auth_keystone('http://authurl', 'user', 'key', {})
+        self.assertEqual(1, len(fake_ks.calls))
+        self.assertEqual('http://authurl/v3', fake_ks.calls[0].get('auth_url'))
+
+    def test_get_auth_keystone_versionless_auth_version_set(self):
+        fake_ks = FakeKeystone(endpoint='http://some_url', token='secret')
+
+        with mock.patch('swiftclient.client._import_keystone_client',
+                        _make_fake_import_keystone_client(fake_ks)):
+            c.get_auth_keystone('http://auth_url', 'user', 'key',
+                                {}, auth_version='2.0')
+        self.assertEqual(1, len(fake_ks.calls))
+        self.assertEqual('http://auth_url/v2.0',
+                         fake_ks.calls[0].get('auth_url'))
+
     def test_auth_with_session(self):
         mock_session = mock.MagicMock()
         mock_session.get_endpoint.return_value = 'http://storagehost/v1/acct'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-swiftclient-3.4.0/tests/unit/utils.py 
new/python-swiftclient-3.5.0/tests/unit/utils.py
--- old/python-swiftclient-3.4.0/tests/unit/utils.py    2017-07-27 
09:59:13.000000000 +0200
+++ new/python-swiftclient-3.5.0/tests/unit/utils.py    2018-01-29 
18:15:49.000000000 +0100
@@ -548,3 +548,24 @@
         return fake_import, fake_import
 
     return _fake_import_keystone_client
+
+
+class FakeStream(object):
+    def __init__(self, size):
+        self.bytes_read = 0
+        self.size = size
+
+    def read(self, size=-1):
+        if self.bytes_read == self.size:
+            return b''
+
+        if size == -1 or size + self.bytes_read > self.size:
+            remaining = self.size - self.bytes_read
+            self.bytes_read = self.size
+            return b'A' * remaining
+
+        self.bytes_read += size
+        return b'A' * size
+
+    def __len__(self):
+        return self.size
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-swiftclient-3.4.0/tools/tox_install.sh 
new/python-swiftclient-3.5.0/tools/tox_install.sh
--- old/python-swiftclient-3.4.0/tools/tox_install.sh   2017-07-27 
09:59:13.000000000 +0200
+++ new/python-swiftclient-3.5.0/tools/tox_install.sh   1970-01-01 
01:00:00.000000000 +0100
@@ -1,31 +0,0 @@
-#!/usr/bin/env bash
-
-# Client constraint file contains this client version pin that is in conflict
-# with installing the client from source. We should remove the version pin in
-# the constraints file before applying it for from-source installation.
-
-set -e
-
-if [[ -z "$CONSTRAINTS_FILE" ]]; then
-    echo 'WARNING: expected $CONSTRAINTS_FILE to be set' >&2
-    PIP_FLAGS=(-U)
-else
-    # NOTE(tonyb): Place this in the tox enviroment's log dir so it will get
-    # published to logs.openstack.org for easy debugging.
-    localfile="$VIRTUAL_ENV/log/upper-constraints.txt"
-
-    if [[ "$CONSTRAINTS_FILE" != http* ]]; then
-        CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE"
-    fi
-    curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile"
-
-    pip install -c"$localfile" openstack-requirements
-
-    # This is the main purpose of the script: Allow local installation of
-    # the current repo. It is listed in constraints file and thus any
-    # install will be constrained and we need to unconstrain it.
-    edit-constraints "$localfile" -- "$CLIENT_NAME"
-    PIP_FLAGS=(-c"$localfile" -U)
-fi
-
-pip install "${PIP_FLAGS[@]}" "$@"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-swiftclient-3.4.0/tox.ini 
new/python-swiftclient-3.5.0/tox.ini
--- old/python-swiftclient-3.4.0/tox.ini        2017-07-27 09:59:13.000000000 
+0200
+++ new/python-swiftclient-3.5.0/tox.ini        2018-01-29 18:15:49.000000000 
+0100
@@ -5,13 +5,11 @@
 
 [testenv]
 usedevelop = True
-install_command = {toxinidir}/tools/tox_install.sh {opts} {packages}
+install_command = python -m pip install -U {opts} {packages}
+list_dependencies_command = python -m pip freeze
 setenv =
   LANG=en_US.utf8
   VIRTUAL_ENV={envdir}
-  BRANCH_NAME=master
-  CLIENT_NAME=python-swiftclient
-  
CONSTRAINTS_FILE={env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
 
 deps = -r{toxinidir}/requirements.txt
        -r{toxinidir}/test-requirements.txt
@@ -25,19 +23,17 @@
 
 [testenv:pep8]
 commands =
-    flake8 swiftclient tests
+    python -m flake8 swiftclient tests
 
 [testenv:venv]
 commands = {posargs}
 
 [testenv:cover]
-commands = python setup.py testr --coverage
+commands = python setup.py testr --coverage 
            coverage report
 
 [testenv:func]
-setenv =
-    {[testenv]setenv}
-    OS_TEST_PATH=tests.functional
+setenv = OS_TEST_PATH=tests.functional
 whitelist_externals =
     coverage
     rm
@@ -71,3 +67,6 @@
 usedevelop = False
 deps = bindep
 commands = bindep test
+
+[testenv:releasenotes]
+commands = sphinx-build -a -W -E -d releasenotes/build/doctrees -b html 
releasenotes/source releasenotes/build/html


Reply via email to