Browse Source

fixes #29: port to Python 3 - welcome to the future

Davide Alberani 7 years ago
parent
commit
83b29fd2d4
6 changed files with 56 additions and 32 deletions
  1. 4 5
      README.md
  2. 11 9
      ibt2.py
  3. 2 2
      monco.py
  4. 2 2
      package.json
  5. 27 11
      tests/ibt2_tests.py
  6. 10 3
      utils.py

+ 4 - 5
README.md

@@ -22,10 +22,9 @@ See [https://ibt2.ismito.it:3002/](https://ibt2.ismito.it:3002/)
 To install it:
 ``` bash
 wget https://bootstrap.pypa.io/get-pip.py
-sudo python get-pip.py
-sudo pip install tornado
-sudo pip install pymongo
-sudo pip install python-dateutil
+sudo python3 get-pip.py
+sudo pip3 install tornado
+sudo pip3 install pymongo
 git clone https://github.com/raspibo/ibt2
 cd ibt2
 ```
@@ -62,7 +61,7 @@ npm run devserver &
 npm run dev
 
 # only when the devserver is running, you can also run the testsuite
-python ./tests/ibt2_tests.py
+python3 ./tests/ibt2_tests.py
 ```
 
 Your browser will automatically open [http://localhost:8080/](http://localhost:8080/) (that's the server for development)

+ 11 - 9
ibt2.py

@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 """I'll Be There, 2 (ibt2) - an oversimplified attendees registration system.
 
 Copyright 2016-2017 Davide Alberani <da@erlug.linux.it>
@@ -63,8 +63,8 @@ class BaseHandler(tornado.web.RequestHandler):
     collection = None
 
     # A property to access the first value of each argument.
-    arguments = property(lambda self: dict([(k, v[0])
-        for k, v in self.request.arguments.iteritems()]))
+    arguments = property(lambda self: dict([(k, v[0].decode('utf-8'))
+        for k, v in self.request.arguments.items()]))
 
     # Arguments suitable for a query on MongoDB.
     clean_arguments = property(lambda self: self._clean_dict(self.arguments))
@@ -86,8 +86,8 @@ class BaseHandler(tornado.web.RequestHandler):
         :param data: dictionary to clean
         :type data: dict"""
         if isinstance(data, dict):
-            for key in data.keys():
-                if (isinstance(key, (str, unicode)) and key.startswith('$')) or key in ('_id', 'created_by',
+            for key in list(data.keys()):
+                if (isinstance(key, str) and key.startswith('$')) or key in ('_id', 'created_by',
                                                                                         'created_at', 'updated_by',
                                                                                         'updated_at'):
                     del data[key]
@@ -109,13 +109,16 @@ class BaseHandler(tornado.web.RequestHandler):
 
     def initialize(self, **kwargs):
         """Add every passed (key, value) as attributes of the instance."""
-        for key, value in kwargs.iteritems():
+        for key, value in kwargs.items():
             setattr(self, key, value)
 
     @property
     def current_user(self):
         """Retrieve current user ID from the secure cookie."""
-        return self.get_secure_cookie("user")
+        current_user = self.get_secure_cookie("user")
+        if isinstance(current_user, bytes):
+            current_user = current_user.decode('utf-8')
+        return current_user
 
     @property
     def current_user_info(self):
@@ -381,8 +384,7 @@ class GroupsHandler(BaseHandler):
         if not (day and group):
             return self.build_error(status=404, message='unable to access the resource')
         if not self.current_user_info.get('isAdmin'):
-            self.build_error(status=401, message='insufficient permissions: must be admin')
-            return False
+            return self.build_error(status=401, message='insufficient permissions: must be admin')
         query = {'day': day, 'group': group}
         howMany = self.db.delete('attendees', query)
         self.db.delete('groups', query)

+ 2 - 2
monco.py

@@ -56,7 +56,7 @@ def convert(seq):
     """
     if isinstance(seq, dict):
         d = {}
-        for key, item in seq.iteritems():
+        for key, item in seq.items():
             if key in _force_conversion:
                 try:
                     d[key] = _force_conversion[key](item)
@@ -254,7 +254,7 @@ class Monco(object):
         operator = self._operations.get(operation)
         if updateList:
             newData = {}
-            for key, value in data.iteritems():
+            for key, value in data.items():
                 newData['%s.$.%s' % (updateList, key)] = value
             data = newData
         res = db[collection].find_and_modify(query=_id_or_query,

+ 2 - 2
package.json

@@ -15,8 +15,8 @@
   },
   "scripts": {
     "dev": "node build/dev-server.js",
-    "devserver": "python ibt2.py --debug --db_name=ibt2_test",
-    "server": "python ibt2.py --debug",
+    "devserver": "python3 ibt2.py --debug --db_name=ibt2_test",
+    "server": "python3 ibt2.py --debug",
     "build": "node build/build.js"
   },
   "dependencies": {

+ 27 - 11
tests/ibt2_tests.py

@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 """I'll Be There, 2 (ibt2) - tests
 
 Copyright 2016-2017 Davide Alberani <da@erlug.linux.it>
@@ -23,7 +23,7 @@ BASE_URL = 'http://localhost:3000/v1.1/'
 DB_NAME = 'ibt2_test'
 
 def dictInDict(d, dContainer):
-    for k, v in d.viewitems():
+    for k, v in d.items():
         if k not in dContainer:
             return False
         if v != dContainer[k]:
@@ -39,15 +39,15 @@ class Ibt2Tests(unittest.TestCase):
         self.db['attendees'].drop()
         self.db['days'].drop()
         self.db['groups'].drop()
-        self.db['users'].remove({'username': 'newuser'})
-        self.db['users'].remove({'username': 'newuser2'})
+        self.db['users'].delete_one({'username': 'newuser'})
+        self.db['users'].delete_one({'username': 'newuser2'})
 
     def tearDown(self):
         self.db['attendees'].drop()
         self.db['days'].drop()
         self.db['groups'].drop()
-        self.db['users'].remove({'username': 'newuser'})
-        self.db['users'].remove({'username': 'newuser2'})
+        self.db['users'].delete_one({'username': 'newuser'})
+        self.db['users'].delete_one({'username': 'newuser2'})
 
     def add_attendee(self, attendee):
         r = requests.post('%sattendees' % BASE_URL, json=attendee)
@@ -85,9 +85,11 @@ class Ibt2Tests(unittest.TestCase):
         attendee = {'name': 'A Name', 'day': '2017-01-15', 'group': 'A group'}
         r = self.add_attendee(attendee)
         id_ = r.json().get('_id')
+        r.connection.close()
         r = requests.delete(BASE_URL + 'attendees/' + id_)
         r.raise_for_status()
         self.assertTrue(r.json().get('success'))
+        r.connection.close()
 
     def test_get_days(self):
         self.add_attendee({'day': '2017-01-15', 'name': 'A name', 'group': 'group A'})
@@ -118,9 +120,11 @@ class Ibt2Tests(unittest.TestCase):
     def test_create_user(self):
         r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
         r.raise_for_status()
+        r.connection.close()
         s = self.login('newuser', 'ibt2')
         r = s.get(BASE_URL + 'users/current')
         r.raise_for_status()
+        r.connection.close()
 
     def test_update_user(self):
         r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
@@ -130,17 +134,20 @@ class Ibt2Tests(unittest.TestCase):
         r.raise_for_status()
         id2_ = r.json()['_id']
         r = requests.put(BASE_URL + 'users/' + id_, json={'email': 't@example.com'})
-        self.assertRaises(r.raise_for_status)
+        self.assertRaises(requests.exceptions.HTTPError, r.raise_for_status)
         s = self.login('newuser', 'ibt2')
         r = s.put(BASE_URL + 'users/' + id_, json={'email': 'test@example.com'})
         r.raise_for_status()
         self.assertEqual(r.json().get('email'), 'test@example.com')
+        r.connection.close()
         r = s.put(BASE_URL + 'users/' + id2_, json={'email': 'test@example.com'})
-        self.assertRaises(r.raise_for_status)
+        self.assertRaises(requests.exceptions.HTTPError, r.raise_for_status)
+        r.connection.close()
         s = self.login('admin', 'ibt2')
         r = s.put(BASE_URL + 'users/' + id_, json={'email': 'test2@example.com'})
         r.raise_for_status()
         self.assertEqual(r.json().get('email'), 'test2@example.com')
+        r.connection.close()
 
     def test_delete_user(self):
         r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
@@ -150,26 +157,31 @@ class Ibt2Tests(unittest.TestCase):
         r.raise_for_status()
         id2_ = r.json()['_id']
         r = requests.delete(BASE_URL + 'users/' + id_)
-        self.assertRaises(r.raise_for_status)
+        self.assertRaises(requests.exceptions.HTTPError, r.raise_for_status)
+        r.connection.close()
         s = self.login('newuser', 'ibt2')
         r = s.delete(BASE_URL + 'users/' + id_)
         r.raise_for_status()
+        r.connection.close()
         r = s.delete(BASE_URL + 'users/' + id2_)
-        self.assertRaises(r.raise_for_status)
+        self.assertRaises(requests.exceptions.HTTPError, r.raise_for_status)
+        r.connection.close()
         s = self.login('admin', 'ibt2')
         r = s.delete(BASE_URL + 'users/' + id2_)
         r.raise_for_status()
+        r.connection.close()
 
     def test_duplicate_user(self):
         r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt2'})
         r.raise_for_status()
         r = requests.post(BASE_URL + 'users', json={'username': 'newuser', 'password': 'ibt3'})
-        self.assertRaises(r.raise_for_status)
+        self.assertRaises(requests.exceptions.HTTPError, r.raise_for_status)
 
     def login(self, username, password):
         s = requests.Session()
         r = s.post(BASE_URL + 'login', json={'username': username, 'password': password})
         r.raise_for_status()
+        r.connection.close()
         return s
 
     def test_created_by(self):
@@ -177,12 +189,14 @@ class Ibt2Tests(unittest.TestCase):
         r = s.get(BASE_URL + 'users/current')
         r.raise_for_status()
         user_id = r.json()['_id']
+        r.connection.close()
         attendee = {'day': '2017-01-15', 'name': 'A name', 'group': 'group A'}
         r = s.post('%sattendees' % BASE_URL, json=attendee)
         r.raise_for_status()
         rj = r.json()
         self.assertEqual(user_id, rj['created_by'])
         self.assertEqual(user_id, rj['updated_by'])
+        r.connection.close()
 
     def test_put_day(self):
         day = {'day': '2017-01-16', 'notes': 'A day note'}
@@ -214,10 +228,12 @@ class Ibt2Tests(unittest.TestCase):
         r = s.delete(BASE_URL + 'days/2017-01-16/groups/A group', params={'day': '2017-01-16', 'group': 'A group'})
         r.raise_for_status()
         rj = r.json()
+        r.connection.close()
         r = requests.get(BASE_URL + 'days/2017-01-16')
         r.raise_for_status()
         rj = r.json()
         self.assertTrue(rj == {})
+        r.connection.close()
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)

+ 10 - 3
utils.py

@@ -36,15 +36,22 @@ def hash_password(password, salt=None):
     :rtype: str"""
     if salt is None:
         salt_pool = string.ascii_letters + string.digits
-        salt = ''.join(random.choice(salt_pool) for x in xrange(32))
-    hash_ = hashlib.sha512('%s%s' % (salt, password))
+        salt = ''.join(random.choice(salt_pool) for x in range(32))
+    pass_and_salt = '%s%s' % (salt, password)
+    pass_and_salt = pass_and_salt.encode('utf-8', 'ignore')
+    hash_ = hashlib.sha512(pass_and_salt)
     return '$%s$%s' % (salt, hash_.hexdigest())
 
 
 class ImprovedEncoder(json.JSONEncoder):
     """Enhance the default JSON encoder to serialize datetime and ObjectId instances."""
     def default(self, o):
-        if isinstance(o, (datetime.datetime, datetime.date,
+        if isinstance(o, bytes):
+            try:
+                return o.decode('utf-8')
+            except:
+                pass
+        elif isinstance(o, (datetime.datetime, datetime.date,
                           datetime.time, datetime.timedelta, ObjectId)):
             try:
                 return str(o)