support for Python 3.3

This commit is contained in:
Martin Zimmermann 2013-10-09 16:28:54 +02:00
parent f9133b984e
commit 61a486d2ea
13 changed files with 109 additions and 60 deletions

View File

@ -33,8 +33,6 @@ dist = pkg_resources.get_distribution("isso")
import sys
import os
import socket
import httplib
import urlparse
from os.path import dirname, join
from argparse import ArgumentParser
@ -73,7 +71,7 @@ rules = Map([
class Isso(object):
salt = "Eech7co8Ohloopo9Ol6baimi"
salt = b"Eech7co8Ohloopo9Ol6baimi"
def __init__(self, conf):

View File

@ -1,10 +1,7 @@
# -*- encoding: utf-8 -*-
# from isso import compat
# from isso.compat import text_type as str, string_types
str = unicode
string_types = (unicode, str)
from isso.compat import text_type as str, string_types
# @compat.implements_to_string

21
isso/compat.py Normal file
View File

@ -0,0 +1,21 @@
# -*- encoding: utf-8 -*-
import sys
PY2K = sys.version_info[0] == 2
if not PY2K:
# iterkeys = lambda d: iter(d.keys())
# iteritems = lambda d: iter(d.items())
text_type = str
string_types = (str, )
buffer = memoryview
else:
# iterkeys = lambda d: d.iterkeys()
# iteritems = lambda d: d.iteritems()
text_type = unicode
string_types = (str, unicode)
buffer = buffer

View File

@ -6,16 +6,11 @@ import io
import os
import time
import binascii
import thread
import threading
import socket
import smtplib
import httplib
import urlparse
from configparser import ConfigParser
try:
@ -23,6 +18,19 @@ try:
except ImportError:
uwsgi = None
from isso.compat import PY2K
if PY2K:
import thread
import httplib
import urlparse
else:
import _thread as thread
import http.client as httplib
import urllib.parse as urlparse
from isso import notify, colors

View File

@ -10,6 +10,8 @@ import hashlib
import binascii
import operator
from functools import reduce
def _bin_to_long(x):
"""
@ -76,7 +78,7 @@ def _pbkdf2(password, salt, iterations, dklen=0, digest=None):
def F(i):
def U():
u = salt + struct.pack(b'>I', i)
for j in xrange(int(iterations)):
for j in range(int(iterations)):
u = _fast_hmac(password, u, digest).digest()
yield _bin_to_long(u)
return _long_to_bin(reduce(operator.xor, U()), hex_format_string)

View File

@ -4,6 +4,7 @@ import time
from isso.db import spam
from isso.utils import Bloomfilter
from isso.compat import buffer
class Comments:
@ -77,7 +78,7 @@ class Comments:
'UPDATE comments SET',
','.join(key + '=' + '?' for key in data),
'WHERE id=?;'],
data.values() + [id])
list(data.values()) + [id])
return self.get(id)

View File

@ -11,9 +11,13 @@ import sys
import os
from time import mktime, strptime
from urlparse import urlparse
from collections import defaultdict
try:
from urlparse import urlparse
except ImportError:
from urllib.parse import urlparse
from xml.etree import ElementTree

View File

@ -3,13 +3,18 @@
from __future__ import division
import socket
import httplib
try:
import httplib
from urlparse import urlparse
except ImportError:
import http.client as httplib
from urllib.parse import urlparse
import random
import hashlib
from string import ascii_letters, digits
from urlparse import urlparse
from contextlib import closing
import html5lib

View File

@ -12,6 +12,8 @@ from itsdangerous import SignatureExpired, BadSignature
from werkzeug.wrappers import Response
from werkzeug.exceptions import abort, BadRequest
from isso.compat import text_type as str
from isso import utils, notify, db
from isso.crypto import pbkdf2
@ -48,7 +50,7 @@ def new(app, environ, request, uri):
return Response('URI does not exist', 404)
try:
data = json.loads(request.data)
data = json.loads(request.get_data().decode('utf-8'))
except ValueError:
return Response("No JSON object could be decoded", 400)
@ -65,7 +67,7 @@ def new(app, environ, request, uri):
if data.get(field):
data[field] = cgi.escape(data[field])
data['remote_addr'] = utils.anonymize(unicode(request.remote_addr))
data['remote_addr'] = utils.anonymize(str(request.remote_addr))
with app.lock:
if uri not in app.db.threads:
@ -82,14 +84,14 @@ def new(app, environ, request, uri):
abort(403)
href = (app.conf.get('general', 'host').rstrip("/") + uri + "#isso-%i" % rv["id"])
app.notify(title, notify.format(rv, href, utils.anonymize(unicode(request.remote_addr))))
app.notify(title, notify.format(rv, href, utils.anonymize(str(request.remote_addr))))
# save checksum of text into cookie, so mallory can't modify/delete a comment, if
# he add a comment, then removed it but not the signed cookie.
checksum = hashlib.md5(rv["text"].encode('utf-8')).hexdigest()
rv["text"] = app.markdown(rv["text"])
rv["hash"] = pbkdf2(rv.get('email') or rv['remote_addr'], app.salt, 1000, 6)
rv["hash"] = str(pbkdf2(rv.get('email') or rv['remote_addr'], app.salt, 1000, 6))
for key in set(rv.keys()) - FIELDS:
rv.pop(key)
@ -132,7 +134,7 @@ def single(app, environ, request, id):
if request.method == 'PUT':
try:
data = json.loads(request.data)
data = json.loads(request.get_data().decode('utf-8'))
except ValueError:
return Response("No JSON object could be decoded", 400)
@ -181,7 +183,7 @@ def fetch(app, environ, request, uri):
for item in rv:
item['hash'] = pbkdf2(item['email'] or item['remote_addr'], app.salt, 1000, 6)
item['hash'] = str(pbkdf2(item['email'] or item['remote_addr'], app.salt, 1000, 6))
for key in set(item.keys()) - FIELDS:
item.pop(key)
@ -195,13 +197,13 @@ def fetch(app, environ, request, uri):
def like(app, environ, request, id):
nv = app.db.comments.vote(True, id, utils.anonymize(unicode(request.remote_addr)))
nv = app.db.comments.vote(True, id, utils.anonymize(str(request.remote_addr)))
return Response(json.dumps(nv), 200)
def dislike(app, environ, request, id):
nv = app.db.comments.vote(False, id, utils.anonymize(unicode(request.remote_addr)))
nv = app.db.comments.vote(False, id, utils.anonymize(str(request.remote_addr)))
return Response(json.dumps(nv), 200)
@ -217,4 +219,4 @@ def count(app, environ, request, uri):
def checkip(app, env, req):
return Response(utils.anonymize(unicode(req.remote_addr)), 200)
return Response(utils.anonymize(str(req.remote_addr)), 200)

View File

@ -7,6 +7,9 @@ from setuptools import setup, find_packages
requires = ['Jinja2>=2.7', 'werkzeug>=0.9', 'itsdangerous', 'misaka', 'html5lib']
if (3, 0) <= sys.version_info < (3, 3):
raise SystemExit("Python 3.0, 3.1 and 3.2 are not supported")
if sys.version_info < (3, 0):
requires += ['ipaddress', 'configparser']
@ -28,7 +31,8 @@ setup(
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
"License :: OSI Approved :: BSD License",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7"
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.3"
],
install_requires=requires,
entry_points={

View File

@ -3,10 +3,14 @@ from __future__ import unicode_literals
import os
import json
import urllib
import tempfile
import unittest
try:
from urllib.parse import urlencode
except ImportError:
from urllib import urlencode
from werkzeug.test import Client
from werkzeug.wrappers import Response
@ -16,6 +20,8 @@ from isso.views import comment
utils.heading = lambda *args: "Untitled."
utils.urlexists = lambda *args: True
loads = lambda data: json.loads(data.decode('utf-8'))
class FakeIP(object):
@ -56,7 +62,7 @@ class TestComments(unittest.TestCase):
r = self.get('/id/1')
assert r.status_code == 200
rv = json.loads(r.data)
rv = loads(r.data)
assert rv['id'] == 1
assert rv['text'] == '<p>Lorem ipsum ...</p>\n'
@ -66,9 +72,9 @@ class TestComments(unittest.TestCase):
rv = self.post('/new?uri=%2Fpath%2F', data=json.dumps({'text': 'Lorem ipsum ...'}))
assert rv.status_code == 201
assert len(filter(lambda header: header[0] == 'Set-Cookie', rv.headers)) == 1
assert any(filter(lambda header: header[0] == 'Set-Cookie', rv.headers))
rv = json.loads(rv.data)
rv = loads(rv.data)
assert rv["mode"] == 1
assert rv["text"] == '<p>Lorem ipsum ...</p>\n'
@ -79,9 +85,9 @@ class TestComments(unittest.TestCase):
b = self.post('/new?uri=test', data=json.dumps({'text': '...'}))
c = self.post('/new?uri=test', data=json.dumps({'text': '...'}))
assert json.loads(a.data)["id"] == 1
assert json.loads(b.data)["id"] == 2
assert json.loads(c.data)["id"] == 3
assert loads(a.data)["id"] == 1
assert loads(b.data)["id"] == 2
assert loads(c.data)["id"] == 3
def testCreateAndGetMultiple(self):
@ -91,7 +97,7 @@ class TestComments(unittest.TestCase):
r = self.get('/?uri=%2Fpath%2F')
assert r.status_code == 200
rv = json.loads(r.data)
rv = loads(r.data)
assert len(rv) == 20
def testGetInvalid(self):
@ -109,7 +115,7 @@ class TestComments(unittest.TestCase):
r = self.get('/id/1?plain=1')
assert r.status_code == 200
rv = json.loads(r.data)
rv = loads(r.data)
assert rv['text'] == 'Hello World'
assert rv['author'] == 'me'
assert rv['website'] == 'http://example.com/'
@ -120,7 +126,7 @@ class TestComments(unittest.TestCase):
self.post('/new?uri=%2Fpath%2F', data=json.dumps({'text': 'Lorem ipsum ...'}))
r = self.delete('/id/1')
assert r.status_code == 200
assert json.loads(r.data) == None
assert loads(r.data) == None
assert self.get('/id/1').status_code == 404
def testDeleteWithReference(self):
@ -131,8 +137,7 @@ class TestComments(unittest.TestCase):
r = client.delete('/id/1')
assert r.status_code == 200
print r.data
assert json.loads(r.data)['mode'] == 4
assert loads(r.data)['mode'] == 4
assert self.get('/?uri=%2Fpath%2F&id=1').status_code == 200
assert self.get('/?uri=%2Fpath%2F&id=2').status_code == 200
@ -175,11 +180,11 @@ class TestComments(unittest.TestCase):
paths = ['/sub/path/', '/path.html', '/sub/path.html', 'path', '/']
for path in paths:
assert self.post('/new?' + urllib.urlencode({'uri': path}),
assert self.post('/new?' + urlencode({'uri': path}),
data=json.dumps({'text': '...'})).status_code == 201
for i, path in enumerate(paths):
assert self.get('/?' + urllib.urlencode({'uri': path})).status_code == 200
assert self.get('/?' + urlencode({'uri': path})).status_code == 200
assert self.get('/id/%i' % (i + 1)).status_code == 200
def testDeleteAndCreateByDifferentUsersButSamePostId(self):
@ -201,9 +206,9 @@ class TestComments(unittest.TestCase):
c = self.post('/new?uri=%2Fpath%2F', data=json.dumps({"text": "Ccc", "email": "..."}))
assert a.status_code == b.status_code == c.status_code == 201
a = json.loads(a.data)
b = json.loads(b.data)
c = json.loads(c.data)
a = loads(a.data)
b = loads(b.data)
c = loads(c.data)
assert a['hash'] != '192.168.1.1'
assert a['hash'] == b['hash']
@ -214,12 +219,12 @@ class TestComments(unittest.TestCase):
rv = self.post('/new?uri=%2Fpath%2F', data=json.dumps({"text": "..."}))
assert rv.status_code == 201
rv = json.loads(rv.data)
rv = loads(rv.data)
for key in comment.FIELDS:
rv.pop(key)
assert rv.keys() == []
assert not any(rv.keys())
def testCounts(self):
@ -228,14 +233,14 @@ class TestComments(unittest.TestCase):
rv = self.get('/count?uri=%2Fpath%2F')
assert rv.status_code == 200
assert json.loads(rv.data) == 1
assert loads(rv.data) == 1
for x in range(3):
self.post('/new?uri=%2Fpath%2F', data=json.dumps({"text": "..."}))
rv = self.get('/count?uri=%2Fpath%2F')
assert rv.status_code == 200
assert json.loads(rv.data) == 4
assert loads(rv.data) == 4
for x in range(4):
self.delete('/id/%i' % (x + 1))
@ -247,7 +252,7 @@ class TestComments(unittest.TestCase):
self.post('/new?uri=test', data=json.dumps({"text": "Tpyo"}))
self.put('/id/1', data=json.dumps({"text": "Tyop"}))
assert json.loads(self.get('/id/1').data)["text"] == "<p>Tyop</p>\n"
assert loads(self.get('/id/1').data)["text"] == "<p>Tyop</p>\n"
self.put('/id/1', data=json.dumps({"text": "Typo"}))
assert json.loads(self.get('/id/1').data)["text"] == "<p>Typo</p>\n"
assert loads(self.get('/id/1').data)["text"] == "<p>Typo</p>\n"

View File

@ -14,6 +14,8 @@ from isso import Isso, notify, utils, core
utils.heading = lambda *args: "Untitled."
utils.urlexists = lambda *args: True
loads = lambda data: json.loads(data.decode('utf-8'))
class FakeIP(object):
@ -51,7 +53,7 @@ class TestVote(unittest.TestCase):
def testZeroLikes(self):
rv = self.makeClient("127.0.0.1").post("/new?uri=test", data=json.dumps({"text": "..."}))
assert json.loads(rv.data)['likes'] == json.loads(rv.data)['dislikes'] == 0
assert loads(rv.data)['likes'] == loads(rv.data)['dislikes'] == 0
def testSingleLike(self):
@ -59,7 +61,7 @@ class TestVote(unittest.TestCase):
rv = self.makeClient("0.0.0.0").post("/id/1/like")
assert rv.status_code == 200
assert json.loads(rv.data)["likes"] == 1
assert loads(rv.data)["likes"] == 1
def testSelfLike(self):
@ -68,7 +70,7 @@ class TestVote(unittest.TestCase):
rv = bob.post('/id/1/like')
assert rv.status_code == 200
assert json.loads(rv.data)["likes"] == 0
assert loads(rv.data)["likes"] == 0
def testMultipleLikes(self):
@ -76,12 +78,12 @@ class TestVote(unittest.TestCase):
for num in range(15):
rv = self.makeClient("1.2.%i.0" % num).post('/id/1/like')
assert rv.status_code == 200
assert json.loads(rv.data)["likes"] == num + 1
assert loads(rv.data)["likes"] == num + 1
def testVoteOnNonexistentComment(self):
rv = self.makeClient("1.2.3.4").post('/id/1/like')
assert rv.status_code == 200
assert json.loads(rv.data) == None
assert loads(rv.data) == None
def testTooManyLikes(self):
@ -91,14 +93,14 @@ class TestVote(unittest.TestCase):
assert rv.status_code == 200
if num >= 142:
assert json.loads(rv.data)["likes"] == 142
assert loads(rv.data)["likes"] == 142
else:
assert json.loads(rv.data)["likes"] == num + 1
assert loads(rv.data)["likes"] == num + 1
def testDislike(self):
self.makeClient("127.0.0.1").post("/new?uri=test", data=json.dumps({"text": "..."}))
rv = self.makeClient("1.2.3.4").post('/id/1/dislike')
assert rv.status_code == 200
assert json.loads(rv.data)['likes'] == 0
assert json.loads(rv.data)['dislikes'] == 1
assert loads(rv.data)['likes'] == 0
assert loads(rv.data)['dislikes'] == 1

View File

@ -1,5 +1,5 @@
[tox]
envlist = py26,py27
envlist = py26,py27,py33
indexserver =
default = https://pypi.switch.posativ.org/root/pypi/+simple
[testenv:py26]