Description: Add Python3 support for Tornadio2.
 Converts exceptions to Python3 style (also supported by Python 2.x).
 Unify string handling for Python3.
Author: Tomas Sedovic <tomas@sedovic.cz>
Origin: other, https://github.com/mrjoes/tornadio2/pull/72
Bug: https://github.com/mrjoes/tornadio2/issues/48
Forwarded: not-needed
Last-Update: 2013-08-24

--- tornadio2-0.0.4.orig/tornadio2/polling.py
+++ tornadio2-0.0.4/tornadio2/polling.py
@@ -22,7 +22,10 @@
 """
 import time
 import logging
-import urllib
+try:
+    from urllib import unquote_plus  # Python 2
+except ImportError:
+    from urllib.parse import unquote_plus  # Python 3
 
 from tornado.web import HTTPError, asynchronous
 
@@ -292,7 +295,7 @@ class TornadioJSONPHandler(TornadioXHRPo
                 raise HTTPError(403)
 
             # Grab data
-            data = urllib.unquote_plus(data[2:]).decode('utf-8')
+            data = unquote_plus(data[2:]).decode('utf-8')
 
             # If starts with double quote, it is json encoded (socket.io workaround)
             if data.startswith(u'"'):
--- tornadio2-0.0.4.orig/tornadio2/sessioncontainer.py
+++ tornadio2-0.0.4/tornadio2/sessioncontainer.py
@@ -31,8 +31,9 @@ from random import random
 def _random_key():
     """Return random session key"""
     i = md5()
-    i.update('%s%s' % (random(), time()))
-    return i.hexdigest()
+    # Python 3 requires bytes not string, hence the encode
+    i.update(('%s%s' % (random(), time())).encode('utf-8'))
+    return i.hexdigest().encode('utf-8')
 
 
 class SessionBase(object):
@@ -71,9 +72,14 @@ class SessionBase(object):
         """Triggered when object was expired or deleted."""
         pass
 
+    # Python 2
     def __cmp__(self, other):
         return cmp(self.expiry_date, other.expiry_date)
 
+    # Python 3
+    def __lt__(self, other):
+        return self.expiry_date < other.expiry_date
+
     def __repr__(self):
         return '%f %s %d' % (getattr(self, 'expiry_date', -1),
                              self.session_id,
--- tornadio2-0.0.4.orig/tornadio2/session.py
+++ tornadio2-0.0.4/tornadio2/session.py
@@ -21,7 +21,10 @@
     Active TornadIO2 connection session.
 """
 
-import urlparse
+try:
+    from urlparse import urlparse  # Python 2
+except ImportError:
+    from urllib.parse import urlparse  # Python 3
 import logging
 
 from tornado.web import HTTPError
@@ -286,7 +289,7 @@ class Session(sessioncontainer.SessionBa
         `url`
             socket.io endpoint URL.
         """
-        urldata = urlparse.urlparse(url)
+        urldata = urlparse(url)
 
         endpoint = urldata.path
 
@@ -401,7 +404,7 @@ class Session(sessioncontainer.SessionBa
                 # in args
                 if len(args) == 1 and isinstance(args[0], dict):
                     # Fix for the http://bugs.python.org/issue4978 for older Python versions
-                    str_args = dict((str(x), y) for x, y in args[0].iteritems())
+                    str_args = dict((str(x), y) for x, y in args[0].items())
 
                     ack_response = conn.on_event(event['name'], kwargs=str_args)
                 else:
@@ -426,7 +429,7 @@ class Session(sessioncontainer.SessionBa
                 logging.error('Incoming error: %s' % msg_data)
             elif msg_type == proto.NOOP:
                 pass
-        except Exception, ex:
+        except Exception as ex:
             logging.exception(ex)
 
             # TODO: Add global exception callback?
--- /dev/null
+++ tornadio2-0.0.4/tornadio2/py2compat.py
@@ -0,0 +1,16 @@
+def with_metaclass(meta, *bases):
+    """
+    Helper for adding a metaclass to a class definition compatible with Python 2
+    and 3.
+
+    See Armin Ronacher's post for more information:
+    http://lucumr.pocoo.org/2013/5/21/porting-to-python-3-redux/
+    """
+    class metaclass(meta):
+        __call__ = type.__call__
+        __init__ = type.__init__
+        def __new__(cls, name, this_bases, d):
+            if this_bases is None:
+                return type.__new__(cls, name, (), d)
+            return meta(name, bases, d)
+    return metaclass('temporary_class', None, {})
--- tornadio2-0.0.4.orig/tornadio2/proto.py
+++ tornadio2-0.0.4/tornadio2/proto.py
@@ -21,6 +21,7 @@
     Socket.IO protocol related functions
 """
 import logging
+import sys
 
 try:
     import simplejson as json
@@ -36,6 +37,14 @@ except ImportError:
             return super(DecimalEncoder, self).default(o)
     json_decimal_args = {"cls": DecimalEncoder}
 
+
+if sys.version_info[0] == 2:
+    text_type = unicode
+    string_types = (str, unicode)
+else:
+    text_type = str
+    string_types = (str,)
+
 # Packet ids
 DISCONNECT = '0'
 CONNECT = '1'
@@ -102,7 +111,7 @@ def message(endpoint, msg, message_id=No
                    'message_id': message_id or u''}
 
     # Trying to send a dict over the wire ?
-    if not isinstance(msg, (unicode, str)) and isinstance(msg, (dict, object)):
+    if not isinstance(msg, string_types) and isinstance(msg, (dict, object)):
         packed_data.update({'kind': JSON,
                             'msg': json.dumps(msg, **json_decimal_args)})
 
@@ -110,7 +119,7 @@ def message(endpoint, msg, message_id=No
     # and respect forced JSON if requested
     else:
         packed_data.update({'kind': MESSAGE if not force_json else JSON,
-                            'msg': msg if isinstance(msg, unicode) else str(msg).decode('utf-8')})
+                            'msg': msg if isinstance(msg, text_type) else str(msg).decode('utf-8')})
 
     return packed_message_tpl % packed_data
 
@@ -220,7 +229,7 @@ def decode_frames(data):
 
     """
     # Single message - nothing to decode here
-    assert isinstance(data, unicode), 'frame is not unicode'
+    assert isinstance(data, text_type), 'frame is not unicode'
 
     if not data.startswith(FRAME_SEPARATOR):
         return [data]
--- tornadio2-0.0.4.orig/tornadio2/conn.py
+++ tornadio2-0.0.4/tornadio2/conn.py
@@ -25,6 +25,7 @@ import logging
 from inspect import ismethod, getmembers
 
 from tornadio2 import proto
+from tornadio2.py2compat import with_metaclass
 
 
 def event(name_or_func):
@@ -59,7 +60,7 @@ class EventMagicMeta(type):
     """Event handler metaclass"""
     def __init__(cls, name, bases, attrs):
         # find events, also in bases
-        is_event = lambda x: ismethod(x) and hasattr(x, '_event_name')
+        is_event = lambda x: hasattr(x, '_event_name')
         events = [(e._event_name, e) for _, e in getmembers(cls, is_event)]
         setattr(cls, '_events', dict(events))
 
@@ -67,7 +68,7 @@ class EventMagicMeta(type):
         super(EventMagicMeta, cls).__init__(name, bases, attrs)
 
 
-class SocketConnection(object):
+class SocketConnection(with_metaclass(EventMagicMeta, object)):
     """Subclass this class and define at least `on_message()` method to make a Socket.IO
     connection handler.
 
@@ -93,8 +94,6 @@ class SocketConnection(object):
         sock.emit('test', {msg:'Hello World'});
 
     """
-    __metaclass__ = EventMagicMeta
-
     __endpoints__ = dict()
 
     def __init__(self, session, endpoint=None):
--- tornadio2-0.0.4.orig/tornadio2/server.py
+++ tornadio2-0.0.4/tornadio2/server.py
@@ -96,7 +96,7 @@ class SocketServer(HTTPServer):
                     io_loop=io_loop,
                     port=flash_policy_port,
                     policy_file=flash_policy_file)
-            except Exception, ex:
+            except Exception as ex:
                 logging.error('Failed to start Flash policy server: %s', ex)
 
         if auto_start:
--- tornadio2-0.0.4.orig/tornadio2/persistent.py
+++ tornadio2-0.0.4/tornadio2/persistent.py
@@ -141,7 +141,7 @@ class TornadioWebSocketHandler(WebSocket
 
         try:
             self.session.raw_message(message)
-        except Exception, ex:
+        except Exception as ex:
             logging.error('Failed to handle message: ' + traceback.format_exc(ex))
 
             # Close session on exception
--- tornadio2-0.0.4.orig/tornadio2/flashserver.py
+++ tornadio2-0.0.4/tornadio2/flashserver.py
@@ -21,7 +21,6 @@
     Flash Socket policy server implementation. Merged with minor modifications
     from the SocketTornad.IO project.
 """
-from __future__ import with_statement
 
 import socket
 import errno
@@ -64,7 +63,7 @@ class FlashPolicyServer(object):
         while True:
             try:
                 connection, address = sock.accept()
-            except socket.error, ex:
+            except socket.error as ex:
                 if ex[0] not in (errno.EWOULDBLOCK, errno.EAGAIN):
                     raise
                 return
--- tornadio2-0.0.4.orig/tornadio2/router.py
+++ tornadio2-0.0.4/tornadio2/router.py
@@ -82,7 +82,7 @@ class HandshakeHandler(preflight.Preflig
 
             # TODO: Fix heartbeat timeout. For now, it is adding 5 seconds to the client timeout.
             data = '%s:%d:%d:%s' % (
-                sess.session_id,
+                sess.session_id.decode('utf-8'),
                 # TODO: Fix me somehow a well. 0.9.2 will drop connection is no
                 # heartbeat was sent over
                 settings['heartbeat_interval'] + settings['client_timeout'],
