Hello community,

here is the log from the commit of package python-amqp for openSUSE:Factory 
checked in at 2017-10-26 18:46:26
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-amqp (Old)
 and      /work/SRC/openSUSE:Factory/.python-amqp.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-amqp"

Thu Oct 26 18:46:26 2017 rev:22 rq:536836 version:2.2.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-amqp/python-amqp.changes  2017-04-12 
18:19:21.311325142 +0200
+++ /work/SRC/openSUSE:Factory/.python-amqp.new/python-amqp.changes     
2017-10-26 18:46:27.878124936 +0200
@@ -1,0 +2,39 @@
+Sun Oct 15 22:25:08 UTC 2017 - a...@gmx.de
+
+- specfile:
+  * added fdupes
+
+- update to version 2.2.2:
+  * Sending empty messages no longer hangs. Instead an empty message
+    is sent correctly.(addresses #151) Fix contributed by **Christian
+    Blades**
+  * Fixed compatibility issues in UTF-8 encoding behavior between
+    Py2/Py3 (#164) Fix contributed by **Tyler James Harden**
+
+- changes from version 2.2.1:
+  * Fix implicit conversion from bytes to string on the connection
+    object. (Issue #155) This issue has caused Celery to crash on
+    connection to RabbitMQ.  Fix contributed by **Omer Katz**
+
+- changes from version 2.2.0:
+  * Fix random delays in task execution.  This is a bug that caused
+    performance issues due to polling timeouts that occur when
+    receiving incomplete AMQP frames. (Issues #3978 #3737 #3814) Fix
+    contributed by **Robert Kopaczewski**
+  * Calling "conn.collect()" multiple times will no longer raise an
+    "AttributeError" when no channels exist.  Fix contributed by
+    **Gord Chung**
+  * Fix compatibility code for Python 2.7.6.  Fix contributed by
+    **Jonathan Schuff**
+  * When running in Windows, py-amqp will no longer use the
+    unsupported TCP option TCP_MAXSEG.  Fix contributed by **Tony
+    Breeds**
+  * Added support for setting the SNI hostname header.  The SSL
+    protocol version is now set to SSLv23 Contributed by **Dhananjay
+    Sathe**
+  * Authentication mechanisms were refactored to be more
+    modular. GSSAPI authentication is now supported.  Contributed by
+    **Alexander Dutton**
+  * Do not reconnect on collect.  Fix contributed by **Gord Chung**
+
+-------------------------------------------------------------------

Old:
----
  amqp-2.1.4.tar.gz

New:
----
  amqp-2.2.2.tar.gz

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

Other differences:
------------------
++++++ python-amqp.spec ++++++
--- /var/tmp/diff_new_pack.72Lo9r/_old  2017-10-26 18:46:28.902077141 +0200
+++ /var/tmp/diff_new_pack.72Lo9r/_new  2017-10-26 18:46:28.906076954 +0200
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-amqp
-Version:        2.1.4
+Version:        2.2.2
 Release:        0
 Summary:        Low-level AMQP client for Python (fork of amqplib)
 License:        LGPL-2.1+
@@ -30,6 +30,7 @@
 BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  %{python_module vine}
+BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires:       python-vine >= 1.1.3
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build
@@ -51,6 +52,7 @@
 
 %install
 %python_install
+%python_expand %fdupes %{buildroot}%{$python_sitelib}
 
 %check
 %python_exec %{_bindir}/py.test

++++++ amqp-2.1.4.tar.gz -> amqp-2.2.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/Changelog new/amqp-2.2.2/Changelog
--- old/amqp-2.1.4/Changelog    2016-12-15 00:50:09.000000000 +0100
+++ new/amqp-2.2.2/Changelog    2017-09-14 13:36:47.000000000 +0200
@@ -5,6 +5,73 @@
 The previous amqplib changelog is here:
 http://code.google.com/p/py-amqplib/source/browse/CHANGES
 
+.. _version-2.2.2:
+
+2.2.2
+=====
+:release-date: 2017-09-14 09:00 A.M UTC+2
+:release-by: Omer Katz
+
+- Sending empty messages no longer hangs. Instead an empty message is sent 
correctly.(addresses #151)
+
+  Fix contributed by **Christian Blades**
+
+- Fixed compatibility issues in UTF-8 encoding behavior between Py2/Py3 (#164)
+
+  Fix contributed by **Tyler James Harden**
+
+.. _version-2.2.1:
+
+2.2.1
+=====
+:release-date: 2017-07-14 09:00 A.M UTC+2
+:release-by: Omer Katz
+
+- Fix implicit conversion from bytes to string on the connection object. 
(Issue #155)
+
+  This issue has caused Celery to crash on connection to RabbitMQ.
+
+  Fix contributed by **Omer Katz**
+
+.. _version-2.2.0:
+
+2.2.0
+=====
+:release-date: 2017-07-12 10:00 A.M UTC+2
+:release-by: Ask Solem
+
+- Fix random delays in task execution.
+
+  This is a bug that caused performance issues due to polling timeouts that 
occur when receiving incomplete AMQP frames. (Issues #3978 #3737 #3814)
+
+  Fix contributed by **Robert Kopaczewski**
+
+- Calling ``conn.collect()`` multiple times will no longer raise an 
``AttributeError`` when no channels exist.
+
+  Fix contributed by **Gord Chung**
+
+- Fix compatibility code for Python 2.7.6.
+
+  Fix contributed by **Jonathan Schuff**
+
+- When running in Windows, py-amqp will no longer use the unsupported TCP 
option TCP_MAXSEG.
+
+  Fix contributed by **Tony Breeds**
+
+- Added support for setting the SNI hostname header.
+
+  The SSL protocol version is now set to SSLv23
+
+  Contributed by **Dhananjay Sathe**
+
+- Authentication mechanisms were refactored to be more modular. GSSAPI 
authentication is now supported.
+
+  Contributed by **Alexander Dutton**
+
+- Do not reconnect on collect.
+
+  Fix contributed by **Gord Chung**
+
 .. _version-2.1.4:
 
 2.1.4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/PKG-INFO new/amqp-2.2.2/PKG-INFO
--- old/amqp-2.1.4/PKG-INFO     2016-12-15 00:52:11.000000000 +0100
+++ new/amqp-2.2.2/PKG-INFO     2017-09-14 13:47:35.000000000 +0200
@@ -1,18 +1,19 @@
 Metadata-Version: 1.1
 Name: amqp
-Version: 2.1.4
+Version: 2.2.2
 Summary: Low-level AMQP client for Python (fork of amqplib).
 Home-page: http://github.com/celery/py-amqp
 Author: Ask Solem
 Author-email: pya...@celeryproject.org
 License: BSD
+Description-Content-Type: UNKNOWN
 Description: 
=====================================================================
          Python AMQP 0.9.1 client library
         =====================================================================
         
         |build-status| |coverage| |license| |wheel| |pyversion| |pyimp|
         
-        :Version: 2.1.4
+        :Version: 2.2.2
         :Web: https://amqp.readthedocs.io/
         :Download: http://pypi.python.org/pypi/amqp/
         :Source: http://github.com/celery/py-amqp/
@@ -144,9 +145,9 @@
 Classifier: Programming Language :: Python :: 2
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.3
 Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
 Classifier: License :: OSI Approved :: BSD License
 Classifier: Intended Audience :: Developers
 Classifier: Operating System :: OS Independent
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/README.rst new/amqp-2.2.2/README.rst
--- old/amqp-2.1.4/README.rst   2016-12-15 00:51:26.000000000 +0100
+++ new/amqp-2.2.2/README.rst   2017-09-14 13:38:45.000000000 +0200
@@ -4,7 +4,7 @@
 
 |build-status| |coverage| |license| |wheel| |pyversion| |pyimp|
 
-:Version: 2.1.4
+:Version: 2.2.2
 :Web: https://amqp.readthedocs.io/
 :Download: http://pypi.python.org/pypi/amqp/
 :Source: http://github.com/celery/py-amqp/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/amqp/__init__.py 
new/amqp-2.2.2/amqp/__init__.py
--- old/amqp-2.1.4/amqp/__init__.py     2016-12-15 00:51:26.000000000 +0100
+++ new/amqp-2.2.2/amqp/__init__.py     2017-09-14 13:38:45.000000000 +0200
@@ -1,26 +1,12 @@
 """Low-level AMQP client for Python (fork of amqplib)."""
 # Copyright (C) 2007-2008 Barry Pederson <b...@barryp.org>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 from __future__ import absolute_import, unicode_literals
 
 import re
 
 from collections import namedtuple
 
-__version__ = '2.1.4'
+__version__ = '2.2.2'
 __author__ = 'Barry Pederson'
 __maintainer__ = 'Ask Solem'
 __contact__ = 'pya...@celeryproject.org'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/amqp/abstract_channel.py 
new/amqp-2.2.2/amqp/abstract_channel.py
--- old/amqp-2.1.4/amqp/abstract_channel.py     2016-12-12 22:08:05.000000000 
+0100
+++ new/amqp-2.2.2/amqp/abstract_channel.py     2017-09-08 06:48:10.000000000 
+0200
@@ -1,19 +1,5 @@
 """Code common to Connection and Channel objects."""
 # Copyright (C) 2007-2008 Barry Pederson <b...@barryp.org>)
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 from __future__ import absolute_import, unicode_literals
 
 from vine import ensure_promise, promise
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/amqp/basic_message.py 
new/amqp-2.2.2/amqp/basic_message.py
--- old/amqp-2.1.4/amqp/basic_message.py        2016-12-12 22:08:05.000000000 
+0100
+++ new/amqp-2.2.2/amqp/basic_message.py        2017-09-08 06:48:10.000000000 
+0200
@@ -1,19 +1,5 @@
 """AMQP Messages."""
 # Copyright (C) 2007-2008 Barry Pederson <b...@barryp.org>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 from __future__ import absolute_import, unicode_literals
 
 # Intended to fix #85: ImportError: cannot import name spec
@@ -32,7 +18,7 @@
 
 
 class Message(GenericContent):
-    """A Message for use with the Channnel.basic_* methods.
+    """A Message for use with the Channel.basic_* methods.
 
     Expected arg types
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/amqp/channel.py 
new/amqp-2.2.2/amqp/channel.py
--- old/amqp-2.1.4/amqp/channel.py      2016-12-12 22:08:05.000000000 +0100
+++ new/amqp-2.2.2/amqp/channel.py      2017-09-08 06:48:10.000000000 +0200
@@ -1,19 +1,5 @@
 """AMQP Channels."""
 # Copyright (C) 2007-2008 Barry Pederson <b...@barryp.org>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 from __future__ import absolute_import, unicode_literals
 
 import logging
@@ -1604,7 +1590,7 @@
         try:
             fun = self.callbacks[consumer_tag]
         except KeyError:
-            AMQP_LOGGER.warn(
+            AMQP_LOGGER.warning(
                 REJECTED_MESSAGE_WITHOUT_CALLBACK,
                 delivery_tag, consumer_tag, exchange, routing_key,
             )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/amqp/connection.py 
new/amqp-2.2.2/amqp/connection.py
--- old/amqp-2.1.4/amqp/connection.py   2016-12-12 22:08:05.000000000 +0100
+++ new/amqp-2.2.2/amqp/connection.py   2017-09-08 06:48:10.000000000 +0200
@@ -1,19 +1,5 @@
 """AMQP Connections."""
 # Copyright (C) 2007-2008 Barry Pederson <b...@barryp.org>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 from __future__ import absolute_import, unicode_literals
 
 import logging
@@ -21,11 +7,10 @@
 import uuid
 import warnings
 
-from io import BytesIO
-
 from vine import ensure_promise
 
 from . import __version__
+from . import sasl
 from . import spec
 from .abstract_channel import AbstractChannel
 from .channel import Channel
@@ -34,9 +19,8 @@
     ConnectionForced, ConnectionError, error_for_code,
     RecoverableConnectionError, RecoverableChannelError,
 )
-from .five import array, items, monotonic, range, values
+from .five import array, items, monotonic, range, values, string
 from .method_framing import frame_handler, frame_writer
-from .serialization import _write_table
 from .transport import Transport
 
 try:
@@ -100,8 +84,9 @@
     (defaults to 'localhost', if a port is not specified then
     5672 is used)
 
-    If login_response is not specified, one is built up for you from
-    userid and password if they are present.
+    Authentication can be controlled by passing one or more
+    `amqp.sasl.SASL` instances as the `authentication` parameter, or
+    by using the userid and password parameters (for AMQPLAIN and PLAIN).
 
     The 'ssl' parameter may be simply True/False, or for Python >= 2.6
     a dictionary of options to pass to ssl.wrap_socket() such as
@@ -188,7 +173,8 @@
     )
 
     def __init__(self, host='localhost:5672', userid='guest', password='guest',
-                 login_method='AMQPLAIN', login_response=None,
+                 login_method=None, login_response=None,
+                 authentication=(),
                  virtual_host='/', locale='en_US', client_properties=None,
                  ssl=False, connect_timeout=None, channel_max=None,
                  frame_max=None, heartbeat=0, on_open=None, on_blocked=None,
@@ -199,20 +185,22 @@
         self._connection_id = uuid.uuid4().hex
         channel_max = channel_max or 65535
         frame_max = frame_max or 131072
-        if (login_response is None) \
-                and (userid is not None) \
-                and (password is not None):
-            login_response = BytesIO()
-            _write_table({'LOGIN': userid, 'PASSWORD': password},
-                         login_response.write, [])
-            # Skip the length at the beginning
-            login_response = login_response.getvalue()[4:]
+        if authentication:
+            if isinstance(authentication, sasl.SASL):
+                authentication = (authentication,)
+            self.authentication = authentication
+        elif login_method is not None and login_response is not None:
+            self.authentication = (sasl.RAW(login_method, login_response),)
+        elif userid is not None and password is not None:
+            self.authentication = (sasl.GSSAPI(userid, fail_soft=True),
+                                   sasl.AMQPLAIN(userid, password),
+                                   sasl.PLAIN(userid, password))
+        else:
+            raise ValueError("Must supply authentication or userid/password")
 
         self.client_properties = dict(
             self.library_properties, **client_properties or {}
         )
-        self.login_method = login_method
-        self.login_response = login_response
         self.locale = locale
         self.host = host
         self.virtual_host = virtual_host
@@ -342,7 +330,9 @@
         self.version_major = version_major
         self.version_minor = version_minor
         self.server_properties = server_properties
-        self.mechanisms = mechanisms.split(' ')
+        if isinstance(mechanisms, string):
+            mechanisms = mechanisms.encode('utf-8')
+        self.mechanisms = mechanisms.split(b' ')
         self.locales = locales.split(' ')
         AMQP_LOGGER.debug(
             START_DEBUG_FMT,
@@ -363,10 +353,24 @@
             # this key present in client_properties, so we remove it.
             client_properties.pop('capabilities', None)
 
+        for authentication in self.authentication:
+            if authentication.mechanism in self.mechanisms:
+                login_response = authentication.start(self)
+                if login_response is not NotImplemented:
+                    break
+        else:
+            raise ConnectionError(
+                "Couldn't find appropriate auth mechanism "
+                "(can offer: {0}; available: {1})".format(
+                    b", ".join(m.mechanism
+                               for m in self.authentication
+                               if m.mechanism).decode(),
+                    b", ".join(self.mechanisms).decode()))
+
         self.send_method(
             spec.Connection.StartOk, argsig,
-            (client_properties, self.login_method,
-             self.login_response, self.locale),
+            (client_properties, authentication.mechanism,
+             login_response, self.locale),
         )
 
     def _on_secure(self, challenge):
@@ -418,9 +422,11 @@
 
     def collect(self):
         try:
-            self.transport.close()
+            if self._transport:
+                self._transport.close()
 
-            temp_list = [x for x in values(self.channels) if x is not self]
+            temp_list = [x for x in values(self.channels or {})
+                         if x is not self]
             for ch in temp_list:
                 ch.collect()
         except socket.error:
@@ -461,7 +467,9 @@
         raise NotImplementedError('Use AMQP heartbeats')
 
     def drain_events(self, timeout=None):
-        return self.blocking_read(timeout)
+        # read until message is ready
+        while not self.blocking_read(timeout):
+            pass
 
     def blocking_read(self, timeout=None):
         with self.transport.having_timeout(timeout):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/amqp/exceptions.py 
new/amqp-2.2.2/amqp/exceptions.py
--- old/amqp-2.1.4/amqp/exceptions.py   2016-12-15 00:00:59.000000000 +0100
+++ new/amqp-2.2.2/amqp/exceptions.py   2017-09-08 06:48:10.000000000 +0200
@@ -1,19 +1,5 @@
 """Exceptions used by amqp."""
 # Copyright (C) 2007-2008 Barry Pederson <b...@barryp.org>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 from __future__ import absolute_import, unicode_literals
 from .five import python_2_unicode_compatible
 from .platform import pack, unpack
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/amqp/method_framing.py 
new/amqp-2.2.2/amqp/method_framing.py
--- old/amqp-2.1.4/amqp/method_framing.py       2016-12-14 23:53:58.000000000 
+0100
+++ new/amqp-2.2.2/amqp/method_framing.py       2017-09-08 06:48:10.000000000 
+0200
@@ -1,19 +1,5 @@
 """Convert between frames and higher-level AMQP methods."""
 # Copyright (C) 2007-2008 Barry Pederson <b...@barryp.org>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 from __future__ import absolute_import, unicode_literals
 
 from collections import defaultdict
@@ -64,21 +50,24 @@
                     frame_method=method_sig, frame_args=buf,
                 )
                 expected_types[channel] = 2
-            else:
-                callback(channel, method_sig, buf, None)
+                return False
+
+            callback(channel, method_sig, buf, None)
 
         elif frame_type == 2:
             msg = partial_messages[channel]
             msg.inbound_header(buf)
 
-            if msg.ready:
-                # bodyless message, we're done
-                expected_types[channel] = 1
-                partial_messages.pop(channel, None)
-                callback(channel, msg.frame_method, msg.frame_args, msg)
-            else:
+            if not msg.ready:
                 # wait for the content-body
                 expected_types[channel] = 3
+                return False
+
+            # bodyless message, we're done
+            expected_types[channel] = 1
+            partial_messages.pop(channel, None)
+            callback(channel, msg.frame_method, msg.frame_args, msg)
+
         elif frame_type == 3:
             msg = partial_messages[channel]
             msg.inbound_body(buf)
@@ -89,6 +78,7 @@
         elif frame_type == 8:
             # bytes_recv already updated
             pass
+        return True
 
     return on_frame
 
@@ -155,7 +145,7 @@
             pack_into('>BHI%dsB' % framelen, buf, offset,
                       type_, channel, framelen, frame, 0xce)
             offset += 8 + framelen
-            if body:
+            if body is not None:
                 frame = b''.join([
                     pack('>HHQ', method_sig[0], 0, len(body)),
                     properties,
@@ -166,10 +156,12 @@
                           2, channel, framelen, frame, 0xce)
                 offset += 8 + framelen
 
-                framelen = len(body)
-                pack_into('>BHI%dsB' % framelen, buf, offset,
-                          3, channel, framelen, str_to_bytes(body), 0xce)
-                offset += 8 + framelen
+                bodylen = len(body)
+                if bodylen > 0:
+                    framelen = bodylen
+                    pack_into('>BHI%dsB' % framelen, buf, offset,
+                              3, channel, framelen, str_to_bytes(body), 0xce)
+                    offset += 8 + framelen
 
             write(view[:offset])
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/amqp/platform.py 
new/amqp-2.2.2/amqp/platform.py
--- old/amqp-2.1.4/amqp/platform.py     2016-12-15 00:02:33.000000000 +0100
+++ new/amqp-2.2.2/amqp/platform.py     2017-07-12 18:36:51.000000000 +0200
@@ -41,8 +41,14 @@
     TCP_USER_TIMEOUT = 18
     HAS_TCP_USER_TIMEOUT = LINUX_VERSION and LINUX_VERSION >= (2, 6, 37)
 
+HAS_TCP_MAXSEG = True
+# According to MSDN Windows platforms support getsockopt(TCP_MAXSSEG) but not
+# setsockopt(TCP_MAXSEG) on IPPROTO_TCP sockets.
+if sys.platform.startswith('win'):
+    HAS_TCP_MAXSEG = False
 
-if sys.version_info < (2, 7, 6):
+
+if sys.version_info < (2, 7, 7):
     import functools
 
     def _to_bytes_arg(fun):
@@ -66,6 +72,7 @@
     'SOL_TCP',
     'TCP_USER_TIMEOUT',
     'HAS_TCP_USER_TIMEOUT',
+    'HAS_TCP_MAXSEG',
     'pack',
     'pack_into',
     'unpack',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/amqp/sasl.py new/amqp-2.2.2/amqp/sasl.py
--- old/amqp-2.1.4/amqp/sasl.py 1970-01-01 01:00:00.000000000 +0100
+++ new/amqp-2.2.2/amqp/sasl.py 2017-07-12 18:36:51.000000000 +0200
@@ -0,0 +1,159 @@
+"""SASL mechanisms for AMQP authentication."""
+from __future__ import absolute_import, unicode_literals
+
+from io import BytesIO
+import socket
+
+import warnings
+
+from amqp.serialization import _write_table
+
+
+class SASL(object):
+    """The base class for all amqp SASL authentication mechanisms.
+
+    You should sub-class this if you're implementing your own authentication.
+    """
+
+    @property
+    def mechanism(self):
+        """Return a bytes containing the SASL mechanism name."""
+        raise NotImplementedError
+
+    def start(self, connection):
+        """Return the first response to a SASL challenge as a bytes object."""
+        raise NotImplementedError
+
+
+class PLAIN(SASL):
+    """PLAIN SASL authentication mechanism.
+
+    See https://tools.ietf.org/html/rfc4616 for details
+    """
+
+    mechanism = b'PLAIN'
+
+    def __init__(self, username, password):
+        self.username, self.password = username, password
+
+    def start(self, connection):
+        login_response = BytesIO()
+        login_response.write(b'\0')
+        login_response.write(self.username.encode('utf-8'))
+        login_response.write(b'\0')
+        login_response.write(self.password.encode('utf-8'))
+        return login_response.getvalue()
+
+
+class AMQPLAIN(SASL):
+    """AMQPLAIN SASL authentication mechanism.
+
+    This is a non-standard mechanism used by AMQP servers.
+    """
+
+    mechanism = b'AMQPLAIN'
+
+    def __init__(self, username, password):
+        self.username, self.password = username, password
+
+    def start(self, connection):
+        login_response = BytesIO()
+        _write_table({b'LOGIN': self.username, b'PASSWORD': self.password},
+                     login_response.write, [])
+        # Skip the length at the beginning
+        return login_response.getvalue()[4:]
+
+
+def _get_gssapi_mechanism():
+    try:
+        import gssapi
+    except ImportError:
+        class FakeGSSAPI(SASL):
+            """A no-op SASL mechanism for when gssapi isn't available."""
+
+            mechanism = None
+
+            def __init__(self, client_name=None, service=b'amqp',
+                         rdns=False, fail_soft=False):
+                if not fail_soft:
+                    raise NotImplementedError(
+                        "You need to install the `gssapi` module for GSSAPI "
+                        "SASL support")
+
+            def start(self):  # pragma: no cover
+                return NotImplemented
+        return FakeGSSAPI
+    else:
+        import gssapi.raw.misc
+
+        class GSSAPI(SASL):
+            """GSSAPI SASL authentication mechanism.
+
+            See https://tools.ietf.org/html/rfc4752 for details
+            """
+
+            mechanism = b'GSSAPI'
+
+            def __init__(self, client_name=None, service=b'amqp',
+                         rdns=False, fail_soft=False):
+                if client_name and not isinstance(client_name, bytes):
+                    client_name = client_name.encode('ascii')
+                self.client_name = client_name
+                self.fail_soft = fail_soft
+                self.service = service
+                self.rdns = rdns
+
+            def get_hostname(self, connection):
+                sock = connection.transport.sock
+                if self.rdns and sock.family in (socket.AF_INET,
+                                                 socket.AF_INET6):
+                    peer = sock.getpeername()
+                    hostname, _, _ = socket.gethostbyaddr(peer[0])
+                else:
+                    hostname = connection.transport.host
+                if not isinstance(hostname, bytes):
+                    hostname = hostname.encode('ascii')
+                return hostname
+
+            def start(self, connection):
+                try:
+                    if self.client_name:
+                        creds = gssapi.Credentials(
+                            name=gssapi.Name(self.client_name))
+                    else:
+                        creds = None
+                    hostname = self.get_hostname(connection)
+                    name = gssapi.Name(b'@'.join([self.service, hostname]),
+                                       gssapi.NameType.hostbased_service)
+                    context = gssapi.SecurityContext(name=name, creds=creds)
+                    return context.step(None)
+                except gssapi.raw.misc.GSSError:
+                    if self.fail_soft:
+                        return NotImplemented
+                    else:
+                        raise
+        return GSSAPI
+
+
+GSSAPI = _get_gssapi_mechanism()
+
+
+class RAW(SASL):
+    """A generic custom SASL mechanism.
+
+    This mechanism takes a mechanism name and response to send to the server,
+    so can be used for simple custom authentication schemes.
+    """
+
+    mechanism = None
+
+    def __init__(self, mechanism, response):
+        assert isinstance(mechanism, bytes)
+        assert isinstance(response, bytes)
+        self.mechanism, self.response = mechanism, response
+        warnings.warn("Passing login_method and login_response to Connection "
+                      "is deprecated. Please implement a SASL subclass "
+                      "instead.", DeprecationWarning)
+
+    def start(self, connection):
+        return self.response
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/amqp/serialization.py 
new/amqp-2.2.2/amqp/serialization.py
--- old/amqp-2.1.4/amqp/serialization.py        2016-12-14 23:56:26.000000000 
+0100
+++ new/amqp-2.2.2/amqp/serialization.py        2017-09-14 13:13:58.000000000 
+0200
@@ -4,20 +4,6 @@
 
 """
 # Copyright (C) 2007 Barry Pederson <b...@barryp.org>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 from __future__ import absolute_import, unicode_literals
 
 import calendar
@@ -304,14 +290,14 @@
             val = val or ''
             bitcount = _flushbits(bits, write)
             if isinstance(val, string):
-                val = val.encode('utf-8')
+                val = val.encode('utf-8', 'surrogatepass')
             write(pack('B', len(val)))
             write(val)
         elif p == 'S':
             val = val or ''
             bitcount = _flushbits(bits, write)
             if isinstance(val, string):
-                val = val.encode('utf-8')
+                val = val.encode('utf-8', 'surrogatepass')
             write(pack('>I', len(val)))
             write(val)
         elif p == 'F':
@@ -332,7 +318,7 @@
     twrite = out.write
     for k, v in items(d):
         if isinstance(k, string):
-            k = k.encode('utf-8')
+            k = k.encode('utf-8', 'surrogatepass')
         twrite(pack('B', len(k)))
         twrite(k)
         try:
@@ -366,7 +352,7 @@
                 None_t=None):
     if isinstance(v, (string_t, bytes)):
         if isinstance(v, string):
-            v = v.encode('utf-8')
+            v = v.encode('utf-8', 'surrogatepass')
         write(pack('>cI', b'S', len(v)))
         write(v)
     elif isinstance(v, bool):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/amqp/transport.py 
new/amqp-2.2.2/amqp/transport.py
--- old/amqp-2.1.4/amqp/transport.py    2016-12-14 23:57:32.000000000 +0100
+++ new/amqp-2.2.2/amqp/transport.py    2017-09-08 06:48:10.000000000 +0200
@@ -1,19 +1,5 @@
 """Transport implementation."""
 # Copyright (C) 2009 Barry Pederson <b...@barryp.org>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
 from __future__ import absolute_import, unicode_literals
 
 import errno
@@ -26,7 +12,7 @@
 from .exceptions import UnexpectedFrame
 from .five import items
 from .platform import (
-    SOL_TCP, TCP_USER_TIMEOUT, HAS_TCP_USER_TIMEOUT,
+    SOL_TCP, TCP_USER_TIMEOUT, HAS_TCP_USER_TIMEOUT, HAS_TCP_MAXSEG,
     pack, unpack,
 )
 from .utils import get_errno, set_cloexec
@@ -55,9 +41,13 @@
 KNOWN_TCP_OPTS = (
     'TCP_CORK', 'TCP_DEFER_ACCEPT', 'TCP_KEEPCNT',
     'TCP_KEEPIDLE', 'TCP_KEEPINTVL', 'TCP_LINGER2',
-    'TCP_MAXSEG', 'TCP_NODELAY', 'TCP_QUICKACK',
+    'TCP_NODELAY', 'TCP_QUICKACK',
     'TCP_SYNCNT', 'TCP_WINDOW_CLAMP',
 )
+
+if HAS_TCP_MAXSEG:
+    KNOWN_TCP_OPTS += ('TCP_MAXSEG',)
+
 TCP_OPTS = {
     getattr(socket, opt) for opt in KNOWN_TCP_OPTS if hasattr(socket, opt)
 }
@@ -70,9 +60,8 @@
     TCP_OPTS.add(TCP_USER_TIMEOUT)
     DEFAULT_SOCKET_SETTINGS[TCP_USER_TIMEOUT] = 1000
 
-
 try:
-    from socket import TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT # noqa
+    from socket import TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT  # noqa
 except ImportError:
     pass
 else:
@@ -286,20 +275,55 @@
 
     def _setup_transport(self):
         """Wrap the socket in an SSL object."""
-        self.sock = self._wrap_socket(self.sock, **self.sslopts or {})
+        self.sock = self._wrap_socket(self.sock, **self.sslopts)
         self.sock.do_handshake()
         self._quick_recv = self.sock.read
 
     def _wrap_socket(self, sock, context=None, **sslopts):
         if context:
             return self._wrap_context(sock, sslopts, **context)
-        return ssl.wrap_socket(sock, **sslopts)
+        return self._wrap_socket_sni(sock, **sslopts)
 
     def _wrap_context(self, sock, sslopts, check_hostname=None, **ctx_options):
         ctx = ssl.create_default_context(**ctx_options)
         ctx.check_hostname = check_hostname
         return ctx.wrap_socket(sock, **sslopts)
 
+    def _wrap_socket_sni(self, sock, keyfile=None, certfile=None,
+                         server_side=False, cert_reqs=ssl.CERT_NONE,
+                         ca_certs=None, do_handshake_on_connect=True,
+                         suppress_ragged_eofs=True, server_hostname=None,
+                         ciphers=None, ssl_version=None):
+        """Socket wrap with SNI headers.
+
+        Default `ssl.wrap_socket` method augmented with support for
+        setting the server_hostname field required for SNI hostname header
+        """
+        opts = dict(sock=sock, keyfile=keyfile, certfile=certfile,
+                    server_side=server_side, cert_reqs=cert_reqs,
+                    ca_certs=ca_certs,
+                    do_handshake_on_connect=do_handshake_on_connect,
+                    suppress_ragged_eofs=suppress_ragged_eofs,
+                    ciphers=ciphers)
+        # Setup the right SSL version; default to optimal versions across
+        # ssl implementations
+        if ssl_version is not None:
+            opts['ssl_version'] = ssl_version
+        else:
+            # older versions of python 2.7 and python 2.6 do not have the
+            # ssl.PROTOCOL_TLS defined the equivalent is ssl.PROTOCOL_SSLv23
+            # we default to PROTOCOL_TLS and fallback to PROTOCOL_SSLv23
+            if hasattr(ssl, 'PROTOCOL_TLS'):
+                opts['ssl_version'] = ssl.PROTOCOL_TLS
+            else:
+                opts['ssl_version'] = ssl.PROTOCOL_SSLv23
+        # Set SNI headers if supported
+        if (server_hostname is not None) and (
+                hasattr(ssl, 'HAS_SNI') and ssl.HAS_SNI):
+            opts['server_hostname'] = server_hostname
+        sock = ssl.SSLSocket(**opts)
+        return sock
+
     def _shutdown_transport(self):
         """Unwrap a Python 2.6 SSL socket, so we can call shutdown()."""
         if self.sock is not None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/amqp.egg-info/PKG-INFO 
new/amqp-2.2.2/amqp.egg-info/PKG-INFO
--- old/amqp-2.1.4/amqp.egg-info/PKG-INFO       2016-12-15 00:52:08.000000000 
+0100
+++ new/amqp-2.2.2/amqp.egg-info/PKG-INFO       2017-09-14 13:47:35.000000000 
+0200
@@ -1,18 +1,19 @@
 Metadata-Version: 1.1
 Name: amqp
-Version: 2.1.4
+Version: 2.2.2
 Summary: Low-level AMQP client for Python (fork of amqplib).
 Home-page: http://github.com/celery/py-amqp
 Author: Ask Solem
 Author-email: pya...@celeryproject.org
 License: BSD
+Description-Content-Type: UNKNOWN
 Description: 
=====================================================================
          Python AMQP 0.9.1 client library
         =====================================================================
         
         |build-status| |coverage| |license| |wheel| |pyversion| |pyimp|
         
-        :Version: 2.1.4
+        :Version: 2.2.2
         :Web: https://amqp.readthedocs.io/
         :Download: http://pypi.python.org/pypi/amqp/
         :Source: http://github.com/celery/py-amqp/
@@ -144,9 +145,9 @@
 Classifier: Programming Language :: Python :: 2
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.3
 Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
 Classifier: License :: OSI Approved :: BSD License
 Classifier: Intended Audience :: Developers
 Classifier: Operating System :: OS Independent
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/amqp.egg-info/SOURCES.txt 
new/amqp-2.2.2/amqp.egg-info/SOURCES.txt
--- old/amqp-2.1.4/amqp.egg-info/SOURCES.txt    2016-12-15 00:52:08.000000000 
+0100
+++ new/amqp-2.2.2/amqp.egg-info/SOURCES.txt    2017-09-14 13:47:35.000000000 
+0200
@@ -14,6 +14,7 @@
 amqp/method_framing.py
 amqp/platform.py
 amqp/protocol.py
+amqp/sasl.py
 amqp/serialization.py
 amqp/spec.py
 amqp/transport.py
@@ -43,6 +44,7 @@
 docs/reference/amqp.method_framing.rst
 docs/reference/amqp.platform.rst
 docs/reference/amqp.protocol.rst
+docs/reference/amqp.sasl.rst
 docs/reference/amqp.serialization.rst
 docs/reference/amqp.spec.rst
 docs/reference/amqp.transport.rst
@@ -64,6 +66,7 @@
 t/unit/test_exceptions.py
 t/unit/test_method_framing.py
 t/unit/test_platform.py
+t/unit/test_sasl.py
 t/unit/test_serialization.py
 t/unit/test_transport.py
 t/unit/test_utils.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/docs/conf.py new/amqp-2.2.2/docs/conf.py
--- old/amqp-2.1.4/docs/conf.py 2016-10-14 06:55:59.000000000 +0200
+++ new/amqp-2.2.2/docs/conf.py 2017-07-14 07:36:34.000000000 +0200
@@ -7,8 +7,8 @@
     'amqp', __file__,
     project='py-amqp',
     description='Python Promises',
-    version_dev='2.1',
-    version_stable='2.0',
+    version_dev='2.3',
+    version_stable='2.2',
     canonical_url='https://amqp.readthedocs.io',
     webdomain='celeryproject.org',
     github_project='celery/py-amqp',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/docs/includes/introduction.txt 
new/amqp-2.2.2/docs/includes/introduction.txt
--- old/amqp-2.1.4/docs/includes/introduction.txt       2016-12-15 
00:51:26.000000000 +0100
+++ new/amqp-2.2.2/docs/includes/introduction.txt       2017-09-14 
13:38:45.000000000 +0200
@@ -1,4 +1,4 @@
-:Version: 2.1.4
+:Version: 2.2.2
 :Web: https://amqp.readthedocs.io/
 :Download: http://pypi.python.org/pypi/amqp/
 :Source: http://github.com/celery/py-amqp/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/docs/reference/amqp.sasl.rst 
new/amqp-2.2.2/docs/reference/amqp.sasl.rst
--- old/amqp-2.1.4/docs/reference/amqp.sasl.rst 1970-01-01 01:00:00.000000000 
+0100
+++ new/amqp-2.2.2/docs/reference/amqp.sasl.rst 2017-07-12 18:36:51.000000000 
+0200
@@ -0,0 +1,11 @@
+=====================================================
+ amqp.spec
+=====================================================
+
+.. contents::
+    :local:
+.. currentmodule:: amqp.sasl
+
+.. automodule:: amqp.sasl
+    :members:
+    :undoc-members:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/docs/reference/index.rst 
new/amqp-2.2.2/docs/reference/index.rst
--- old/amqp-2.1.4/docs/reference/index.rst     2016-12-12 22:08:05.000000000 
+0100
+++ new/amqp-2.2.2/docs/reference/index.rst     2017-07-12 18:36:51.000000000 
+0200
@@ -19,6 +19,7 @@
     amqp.method_framing
     amqp.platform
     amqp.protocol
+    amqp.sasl
     amqp.serialization
     amqp.spec
     amqp.utils
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/requirements/pkgutils.txt 
new/amqp-2.2.2/requirements/pkgutils.txt
--- old/amqp-2.1.4/requirements/pkgutils.txt    2016-12-12 22:08:05.000000000 
+0100
+++ new/amqp-2.2.2/requirements/pkgutils.txt    2017-09-08 06:47:55.000000000 
+0200
@@ -5,4 +5,4 @@
 tox>=2.3.1
 sphinx2rst>=1.0
 bumpversion
-pydocstyle
+pydocstyle==1.1.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/setup.cfg new/amqp-2.2.2/setup.cfg
--- old/amqp-2.1.4/setup.cfg    2016-12-15 00:52:11.000000000 +0100
+++ new/amqp-2.2.2/setup.cfg    2017-09-14 13:47:35.000000000 +0200
@@ -17,5 +17,4 @@
 [egg_info]
 tag_build = 
 tag_date = 0
-tag_svn_revision = 0
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/setup.py new/amqp-2.2.2/setup.py
--- old/amqp-2.1.4/setup.py     2016-12-12 22:08:05.000000000 +0100
+++ new/amqp-2.2.2/setup.py     2017-07-12 18:36:51.000000000 +0200
@@ -22,9 +22,9 @@
     Programming Language :: Python :: 2
     Programming Language :: Python :: 2.7
     Programming Language :: Python :: 3
-    Programming Language :: Python :: 3.3
     Programming Language :: Python :: 3.4
     Programming Language :: Python :: 3.5
+    Programming Language :: Python :: 3.6
     License :: OSI Approved :: BSD License
     Intended Audience :: Developers
     Operating System :: OS Independent
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/t/unit/test_connection.py 
new/amqp-2.2.2/t/unit/test_connection.py
--- old/amqp-2.1.4/t/unit/test_connection.py    2016-12-12 22:08:05.000000000 
+0100
+++ new/amqp-2.2.2/t/unit/test_connection.py    2017-07-14 07:51:26.000000000 
+0200
@@ -3,6 +3,7 @@
 import pytest
 import socket
 
+import warnings
 from case import ContextMock, Mock, call
 
 from amqp import Connection
@@ -10,6 +11,7 @@
 from amqp.connection import SSLError
 from amqp.exceptions import ConnectionError, NotFound, ResourceError
 from amqp.five import items
+from amqp.sasl import SASL, AMQPLAIN, PLAIN
 from amqp.transport import TCPTransport
 
 
@@ -22,6 +24,7 @@
         self.conn = Connection(
             frame_handler=self.frame_handler,
             frame_writer=self.frame_writer,
+            authentication=AMQPLAIN('foo', 'bar'),
         )
         self.conn.Channel = Mock(name='Channel')
         self.conn.Transport = Mock(name='Transport')
@@ -29,9 +32,27 @@
         self.conn.send_method = Mock(name='send_method')
         self.conn.frame_writer = Mock(name='frame_writer')
 
-    def test_login_response(self):
-        self.conn = Connection(login_response='foo')
-        assert self.conn.login_response == 'foo'
+    def test_sasl_authentication(self):
+        authentication = SASL()
+        self.conn = Connection(authentication=authentication)
+        assert self.conn.authentication == (authentication,)
+
+    def test_sasl_authentication_iterable(self):
+        authentication = SASL()
+        self.conn = Connection(authentication=(authentication,))
+        assert self.conn.authentication == (authentication,)
+
+    def test_amqplain(self):
+        self.conn = Connection(userid='foo', password='bar')
+        assert isinstance(self.conn.authentication[1], AMQPLAIN)
+        assert self.conn.authentication[1].username == 'foo'
+        assert self.conn.authentication[1].password == 'bar'
+
+    def test_plain(self):
+        self.conn = Connection(userid='foo', password='bar')
+        assert isinstance(self.conn.authentication[2], PLAIN)
+        assert self.conn.authentication[2].username == 'foo'
+        assert self.conn.authentication[2].password == 'bar'
 
     def test_enter_exit(self):
         self.conn.connect = Mock(name='connect')
@@ -68,23 +89,72 @@
         callback.assert_called_with()
 
     def test_on_start(self):
-        self.conn._on_start(3, 4, {'foo': 'bar'}, 'x y z', 'en_US en_GB')
+        self.conn._on_start(3, 4, {'foo': 'bar'}, b'x y z AMQPLAIN PLAIN',
+                            'en_US en_GB')
         assert self.conn.version_major == 3
         assert self.conn.version_minor == 4
         assert self.conn.server_properties == {'foo': 'bar'}
-        assert self.conn.mechanisms == ['x', 'y', 'z']
+        assert self.conn.mechanisms == [b'x', b'y', b'z',
+                                        b'AMQPLAIN', b'PLAIN']
         assert self.conn.locales == ['en_US', 'en_GB']
         self.conn.send_method.assert_called_with(
             spec.Connection.StartOk, 'FsSs', (
-                self.conn.client_properties, self.conn.login_method,
-                self.conn.login_response, self.conn.locale,
+                self.conn.client_properties, b'AMQPLAIN',
+                self.conn.authentication[0].start(self.conn), self.conn.locale,
+            ),
+        )
+
+    def test_on_start_string_mechanisms(self):
+        self.conn._on_start(3, 4, {'foo': 'bar'}, 'x y z AMQPLAIN PLAIN',
+                            'en_US en_GB')
+        assert self.conn.version_major == 3
+        assert self.conn.version_minor == 4
+        assert self.conn.server_properties == {'foo': 'bar'}
+        assert self.conn.mechanisms == [b'x', b'y', b'z',
+                                        b'AMQPLAIN', b'PLAIN']
+        assert self.conn.locales == ['en_US', 'en_GB']
+        self.conn.send_method.assert_called_with(
+            spec.Connection.StartOk, 'FsSs', (
+                self.conn.client_properties, b'AMQPLAIN',
+                self.conn.authentication[0].start(self.conn), self.conn.locale,
+            ),
+        )
+
+    def test_missing_credentials(self):
+        with pytest.raises(ValueError):
+            self.conn = Connection(userid=None, password=None)
+        with pytest.raises(ValueError):
+            self.conn = Connection(password=None)
+
+    def test_mechanism_mismatch(self):
+        with pytest.raises(ConnectionError):
+            self.conn._on_start(3, 4, {'foo': 'bar'}, b'x y z',
+                                'en_US en_GB')
+
+    def test_login_method_response(self):
+        # An old way of doing things.:
+        login_method, login_response = b'foo', b'bar'
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter("always")
+            self.conn = Connection(login_method=login_method,
+                                   login_response=login_response)
+            self.conn.send_method = Mock(name='send_method')
+            self.conn._on_start(3, 4, {'foo': 'bar'}, login_method,
+                                'en_US en_GB')
+            assert len(w) == 1
+            assert issubclass(w[0].category, DeprecationWarning)
+
+        self.conn.send_method.assert_called_with(
+            spec.Connection.StartOk, 'FsSs', (
+                self.conn.client_properties, login_method,
+                login_response, self.conn.locale,
             ),
         )
 
     def test_on_start__consumer_cancel_notify(self):
         self.conn._on_start(
             3, 4, {'capabilities': {'consumer_cancel_notify': 1}},
-            '', '',
+            b'AMQPLAIN', '',
         )
         cap = self.conn.client_properties['capabilities']
         assert cap['consumer_cancel_notify']
@@ -92,7 +162,7 @@
     def test_on_start__connection_blocked(self):
         self.conn._on_start(
             3, 4, {'capabilities': {'connection.blocked': 1}},
-            '', '',
+            b'AMQPLAIN', '',
         )
         cap = self.conn.client_properties['capabilities']
         assert cap['connection.blocked']
@@ -100,7 +170,7 @@
     def test_on_start__authentication_failure_close(self):
         self.conn._on_start(
             3, 4, {'capabilities': {'authentication_failure_close': 1}},
-            '', '',
+            b'AMQPLAIN', '',
         )
         cap = self.conn.client_properties['capabilities']
         assert cap['authentication_failure_close']
@@ -108,7 +178,7 @@
     def test_on_start__authentication_failure_close__disabled(self):
         self.conn._on_start(
             3, 4, {'capabilities': {}},
-            '', '',
+            b'AMQPLAIN', '',
         )
         assert 'capabilities' not in self.conn.client_properties
 
@@ -171,6 +241,18 @@
         self.conn.channels[1].collect.side_effect = socket.error()
         self.conn.collect()
 
+    def test_collect_no_transport(self):
+        self.conn = Connection()
+        self.conn.connect = Mock(name='connect')
+        assert not self.conn.connected
+        self.conn.collect()
+        assert not self.conn.connect.called
+
+    def test_collect_again(self):
+        self.conn = Connection()
+        self.conn.collect()
+        self.conn.collect()
+
     def test_get_free_channel_id__raises_IndexError(self):
         self.conn._avail_channel_ids = []
         with pytest.raises(ResourceError):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/t/unit/test_method_framing.py 
new/amqp-2.2.2/t/unit/test_method_framing.py
--- old/amqp-2.1.4/t/unit/test_method_framing.py        2016-12-14 
23:59:42.000000000 +0100
+++ new/amqp-2.2.2/t/unit/test_method_framing.py        2017-09-08 
06:48:10.000000000 +0200
@@ -99,3 +99,9 @@
         frame = 2, 1, spec.Basic.Publish, b'x' * 10, msg
         self.g(*frame)
         self.write.assert_called()
+
+    def test_write_zero_len_body(self):
+        msg = Message(body=b'', content_type='application/octet-stream')
+        frame = 2, 1, spec.Basic.Publish, b'x' * 10, msg
+        self.g(*frame)
+        self.write.assert_called()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/t/unit/test_platform.py 
new/amqp-2.2.2/t/unit/test_platform.py
--- old/amqp-2.1.4/t/unit/test_platform.py      2016-12-14 23:39:46.000000000 
+0100
+++ new/amqp-2.2.2/t/unit/test_platform.py      2017-07-12 18:36:51.000000000 
+0200
@@ -3,6 +3,11 @@
 from amqp.platform import _linux_version_to_tuple
 
 
+def test_struct_argument_type():
+    from amqp.exceptions import FrameSyntaxError
+    FrameSyntaxError()
+
+
 @pytest.mark.parametrize('s,expected', [
     ('3.13.0-46-generic', (3, 13, 0)),
     ('3.19.43-1-amd64', (3, 19, 43)),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/t/unit/test_sasl.py 
new/amqp-2.2.2/t/unit/test_sasl.py
--- old/amqp-2.1.4/t/unit/test_sasl.py  1970-01-01 01:00:00.000000000 +0100
+++ new/amqp-2.2.2/t/unit/test_sasl.py  2017-07-12 18:36:51.000000000 +0200
@@ -0,0 +1,149 @@
+from __future__ import absolute_import, unicode_literals
+
+import contextlib
+import socket
+from io import BytesIO
+
+from case import Mock, patch, call
+import pytest
+import sys
+
+from amqp import sasl
+from amqp.serialization import _write_table
+
+
+class test_SASL:
+    def test_sasl_notimplemented(self):
+        mech = sasl.SASL()
+        with pytest.raises(NotImplementedError):
+            mech.mechanism
+        with pytest.raises(NotImplementedError):
+            mech.start(None)
+
+    def test_plain(self):
+        username, password = 'foo', 'bar'
+        mech = sasl.PLAIN(username, password)
+        response = mech.start(None)
+        assert isinstance(response, bytes)
+        assert response.split(b'\0') == \
+            [b'', username.encode('utf-8'), password.encode('utf-8')]
+
+    def test_amqplain(self):
+        username, password = 'foo', 'bar'
+        mech = sasl.AMQPLAIN(username, password)
+        response = mech.start(None)
+        assert isinstance(response, bytes)
+        login_response = BytesIO()
+        _write_table({b'LOGIN': username, b'PASSWORD': password},
+                     login_response.write, [])
+        expected_response = login_response.getvalue()[4:]
+        assert response == expected_response
+
+    def test_gssapi_missing(self):
+        gssapi = sys.modules.pop('gssapi', None)
+        GSSAPI = sasl._get_gssapi_mechanism()
+        with pytest.raises(NotImplementedError):
+            GSSAPI()
+        if gssapi is not None:
+            sys.modules['gssapi'] = gssapi
+
+    @contextlib.contextmanager
+    def fake_gssapi(self):
+        orig_gssapi = sys.modules.pop('gssapi', None)
+        orig_gssapi_raw = sys.modules.pop('gssapi.raw', None)
+        orig_gssapi_raw_misc = sys.modules.pop('gssapi.raw.misc', None)
+        gssapi = sys.modules['gssapi'] = Mock()
+        sys.modules['gssapi.raw'] = gssapi.raw
+        sys.modules['gssapi.raw.misc'] = gssapi.raw.misc
+
+        class GSSError(Exception):
+            pass
+
+        gssapi.raw.misc.GSSError = GSSError
+        try:
+            yield gssapi
+        finally:
+            if orig_gssapi is None:
+                del sys.modules['gssapi']
+            else:
+                sys.modules['gssapi'] = orig_gssapi
+            if orig_gssapi_raw is None:
+                del sys.modules['gssapi.raw']
+            else:
+                sys.modules['gssapi.raw'] = orig_gssapi_raw
+            if orig_gssapi_raw_misc is None:
+                del sys.modules['gssapi.raw.misc']
+            else:
+                sys.modules['gssapi.raw.misc'] = orig_gssapi_raw_misc
+
+    @patch('socket.gethostbyaddr')
+    def test_gssapi_rdns(self, gethostbyaddr):
+        with self.fake_gssapi() as gssapi:
+            connection = Mock()
+            connection.transport.sock.getpeername.return_value = ('192.0.2.0',
+                                                                  5672)
+            connection.transport.sock.family = socket.AF_INET
+            gethostbyaddr.return_value = ('broker.example.org', (), ())
+            GSSAPI = sasl._get_gssapi_mechanism()
+
+            mech = GSSAPI(rdns=True)
+            mech.start(connection)
+
+            connection.transport.sock.getpeername.assert_called()
+            gethostbyaddr.assert_called_with('192.0.2.0')
+            gssapi.Name.assert_called_with(b'a...@broker.example.org',
+                                           gssapi.NameType.hostbased_service)
+
+    def test_gssapi_no_rdns(self):
+        with self.fake_gssapi() as gssapi:
+            connection = Mock()
+            connection.transport.host = 'broker.example.org'
+            GSSAPI = sasl._get_gssapi_mechanism()
+
+            mech = GSSAPI()
+            mech.start(connection)
+
+            gssapi.Name.assert_called_with(b'a...@broker.example.org',
+                                           gssapi.NameType.hostbased_service)
+
+    def test_gssapi_step_without_client_name(self):
+        with self.fake_gssapi() as gssapi:
+            context = Mock()
+            context.step.return_value = b'secrets'
+            name = Mock()
+            gssapi.SecurityContext.return_value = context
+            gssapi.Name.return_value = name
+            connection = Mock()
+            connection.transport.host = 'broker.example.org'
+            GSSAPI = sasl._get_gssapi_mechanism()
+
+            mech = GSSAPI()
+            response = mech.start(connection)
+
+            gssapi.SecurityContext.assert_called_with(name=name, creds=None)
+            context.step.assert_called_with(None)
+            assert response == b'secrets'
+
+    def test_gssapi_step_with_client_name(self):
+        with self.fake_gssapi() as gssapi:
+            context = Mock()
+            context.step.return_value = b'secrets'
+            client_name, service_name, credentials = Mock(), Mock(), Mock()
+            gssapi.SecurityContext.return_value = context
+            gssapi.Credentials.return_value = credentials
+            gssapi.Name.side_effect = [client_name, service_name]
+            connection = Mock()
+            connection.transport.host = 'broker.example.org'
+            GSSAPI = sasl._get_gssapi_mechanism()
+
+            mech = GSSAPI(client_name='amqp-client/client.example.org')
+            response = mech.start(connection)
+            gssapi.Name.assert_has_calls([
+                call(b'amqp-client/client.example.org'),
+                call(b'a...@broker.example.org',
+                     gssapi.NameType.hostbased_service)])
+            gssapi.Credentials.assert_called_with(name=client_name)
+            gssapi.SecurityContext.assert_called_with(name=service_name,
+                                                      creds=credentials)
+            context.step.assert_called_with(None)
+            assert response == b'secrets'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/amqp-2.1.4/t/unit/test_transport.py 
new/amqp-2.2.2/t/unit/test_transport.py
--- old/amqp-2.1.4/t/unit/test_transport.py     2016-12-15 00:00:41.000000000 
+0100
+++ new/amqp-2.2.2/t/unit/test_transport.py     2017-07-12 18:36:51.000000000 
+0200
@@ -323,12 +323,12 @@
         self.t.sock.do_handshake.assert_called_with()
         assert self.t._quick_recv is self.t.sock.read
 
-    @patch('ssl.wrap_socket', create=True)
-    def test_wrap_socket(self, wrap_socket):
+    def test_wrap_socket(self):
         sock = Mock()
         self.t._wrap_context = Mock()
+        self.t._wrap_socket_sni = Mock()
         self.t._wrap_socket(sock, foo=1)
-        wrap_socket.assert_called_with(sock, foo=1)
+        self.t._wrap_socket_sni.assert_called_with(sock, foo=1)
 
         self.t._wrap_socket(sock, {'c': 2}, foo=1)
         self.t._wrap_context.assert_called_with(sock, {'foo': 1}, c=2)


Reply via email to