Repository: celery/kombu

Issue:
SQS queue name gets mangled
Using Celery/Kombu in an environment with _future_ strings support you end up with mangled queue names in SQS.

The code that tries to translate invalid SQS queue characters causes the byte string to get converted to a string representation such as b'my_queue_name' and then translated that to 'b_my_queue_name'

I am finding this because we are using Apache Airflow in a Python 2.7 environment using SQS queues as the broker.


Discussion:
@jkoehl Airflow uses Celery 3 correct?
I (and others I think) are using Celery 4.x with Airflow 1.8 

Resolving patch:
diff --git a/kombu/utils/encoding.py b/kombu/utils/encoding.py
--- a/kombu/utils/encoding.py
+++ b/kombu/utils/encoding.py
@@ -122,11 +122,18 @@ def _safe_str(s, errors='replace', file=None):
             return '<Unrepresentable {0!r}: {1!r} {2!r}>'.format(
                 type(s), exc, '\n'.join(traceback.format_stack()))
 else:
+    def _ensure_str(s, encoding, errors):
+        if isinstance(s, bytes):
+            return s.decode(encoding, errors)
+        return s
+
+
     def _safe_str(s, errors='replace', file=None):  # noqa
         encoding = default_encoding(file)
         try:
             if isinstance(s, unicode):
-                return s.encode(encoding, errors)
+                return _ensure_str(s.encode(encoding, errors),
+                                   encoding, errors)
             return unicode(s, encoding, errors)
         except Exception as exc:
             return '<Unrepresentable {0!r}: {1!r} {2!r}>'.format(


Test patch:
diff --git a/t/unit/utils/test_encoding.py b/t/unit/utils/test_encoding.py
--- a/t/unit/utils/test_encoding.py
+++ b/t/unit/utils/test_encoding.py
@@ -7,7 +7,7 @@
 
 from case import patch, skip
 
-from kombu.five import bytes_t, string_t
+from kombu.five import bytes_t, string_t, string
 from kombu.utils.encoding import (
     get_default_encoding_file, safe_str,
     set_default_encoding_file, default_encoding,
@@ -65,6 +65,22 @@ def test_default_encode():
         assert e.default_encode(b'foo')
 
 
+class newbytes(bytes):
+    """Mock class to simulate python-future newbytes class"""
+    def __repr__(self):
+        return 'b' + super(newbytes, self).__repr__()
+
+    def __str__(self):
+        return 'b' + "'{0}'".format(super(newbytes, self).__str__())
+
+
+class newstr(string):
+    """Mock class to simulate python-future newstr class"""
+
+    def encode(self, encoding=None, errors=None):
+        return newbytes(super(newstr, self).encode(encoding, errors))
+
+
 class test_safe_str:
 
     def setup(self):
@@ -74,6 +90,10 @@ def setup(self):
     def test_when_bytes(self):
         assert safe_str('foo') == 'foo'
 
+    def test_when_newstr(self):
+        """Simulates using python-future package under 2.7"""
+        assert str(safe_str(newstr('foo'))) == 'foo'
+
     def test_when_unicode(self):
         assert isinstance(safe_str('foo'), string_t)
 
@@ -82,13 +102,13 @@ def test_when_encoding_utf8(self):
         assert default_encoding() == 'utf-8'
         s = 'The quiæk fåx jømps øver the lazy dåg'
         res = safe_str(s)
-        assert isinstance(res, str)
+        assert isinstance(res, string_t)
 
     def test_when_containing_high_chars(self):
         self._encoding.return_value = 'ascii'
         s = 'The quiæk fåx jømps øver the lazy dåg'
         res = safe_str(s)
-        assert isinstance(res, str)
+        assert isinstance(res, string_t)
         assert len(s) == len(res)
 
     def test_when_not_string(self):