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)