diff --git a/.travis.yml b/.travis.yml index a6fe14d..8ff53a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,11 +13,12 @@ matrix: env: TOX_ENV=wheezy install: - pip install -U pip - - pip install pyflakes tox + - pip install flake8 tox - sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm script: - tox -e $TOX_ENV - - python -m pyflakes.__main__ $(git ls-files | grep -E "^isso/.+.py$" | grep -v "^isso/compat.py") + - IGNORE=E226,E241,E265,E402,E501,E704 + - flake8 . --count --ignore=${IGNORE} --max-line-length=127 --show-source --statistics notifications: irc: channels: diff --git a/docs/_isso/remove_heading.py b/docs/_isso/remove_heading.py index 48d7708..f15f5d5 100644 --- a/docs/_isso/remove_heading.py +++ b/docs/_isso/remove_heading.py @@ -9,4 +9,3 @@ class IssoTranslator(HTMLTranslator): if self.section_level == 1: raise nodes.SkipNode HTMLTranslator.visit_title(self, node) - diff --git a/docs/conf.py b/docs/conf.py index 0b85a07..0d0c005 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -200,22 +200,22 @@ htmlhelp_basename = 'Issodoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'Isso.tex', u'Isso Documentation', - u'Martin Zimmermann', 'manual'), + ('index', 'Isso.tex', u'Isso Documentation', + u'Martin Zimmermann', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -260,9 +260,9 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'Isso', u'Isso Documentation', - u'Martin Zimmermann', 'Isso', 'a commenting server similar to Disqus', - 'Miscellaneous'), + ('index', 'Isso', u'Isso Documentation', + u'Martin Zimmermann', 'Isso', 'a commenting server similar to Disqus', + 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. diff --git a/isso/__init__.py b/isso/__init__.py index 035cafd..0af5b43 100644 --- a/isso/__init__.py +++ b/isso/__init__.py @@ -35,7 +35,8 @@ import sys if sys.argv[0].startswith("isso"): try: - import gevent.monkey; gevent.monkey.patch_all() + import gevent.monkey + gevent.monkey.patch_all() except ImportError: pass @@ -87,7 +88,8 @@ class Isso(object): self.conf = conf self.db = db.SQLite3(conf.get('general', 'dbpath'), conf) - self.signer = URLSafeTimedSerializer(self.db.preferences.get("session-key")) + self.signer = URLSafeTimedSerializer( + self.db.preferences.get("session-key")) self.markup = html.Markup(conf.section('markup')) self.hasher = hash.new(conf.section("hash")) @@ -122,7 +124,8 @@ class Isso(object): local.request = request local.host = wsgi.host(request.environ) - local.origin = origin(self.conf.getiter("general", "host"))(request.environ) + local.origin = origin(self.conf.getiter( + "general", "host"))(request.environ) adapter = self.urls.bind_to_environ(request.environ) @@ -136,7 +139,8 @@ class Isso(object): except HTTPException as e: return e except Exception: - logger.exception("%s %s", request.method, request.environ["PATH_INFO"]) + logger.exception("%s %s", request.method, + request.environ["PATH_INFO"]) return InternalServerError() else: return response @@ -181,19 +185,19 @@ def make_app(conf=None, threading=True, multiprocessing=False, uwsgi=False): if isso.conf.getboolean("server", "profile"): wrapper.append(partial(ProfilerMiddleware, - sort_by=("cumulative", ), restrictions=("isso/(?!lib)", 10))) + sort_by=("cumulative", ), restrictions=("isso/(?!lib)", 10))) wrapper.append(partial(SharedDataMiddleware, exports={ '/js': join(dirname(__file__), 'js/'), '/css': join(dirname(__file__), 'css/'), '/img': join(dirname(__file__), 'img/'), '/demo': join(dirname(__file__), 'demo/') - })) + })) wrapper.append(partial(wsgi.CORSMiddleware, - origin=origin(isso.conf.getiter("general", "host")), - allowed=("Origin", "Referer", "Content-Type"), - exposed=("X-Set-Cookie", "Date"))) + origin=origin(isso.conf.getiter("general", "host")), + allowed=("Origin", "Referer", "Content-Type"), + exposed=("X-Set-Cookie", "Date"))) wrapper.extend([wsgi.SubURI, ProxyFix]) @@ -208,9 +212,10 @@ def main(): parser = ArgumentParser(description="a blog comment hosting service") subparser = parser.add_subparsers(help="commands", dest="command") - parser.add_argument('--version', action='version', version='%(prog)s ' + dist.version) + parser.add_argument('--version', action='version', + version='%(prog)s ' + dist.version) parser.add_argument("-c", dest="conf", default="/etc/isso.conf", - metavar="/etc/isso.conf", help="set configuration file") + metavar="/etc/isso.conf", help="set configuration file") imprt = subparser.add_parser('import', help="import Disqus XML export") imprt.add_argument("dump", metavar="FILE") @@ -225,7 +230,8 @@ def main(): subparser.add_parser("run", help="run server") args = parser.parse_args() - conf = config.load(join(dist.location, dist.project_name, "defaults.ini"), args.conf) + conf = config.load( + join(dist.location, dist.project_name, "defaults.ini"), args.conf) if args.command == "import": conf.set("guard", "enabled", "off") diff --git a/isso/compat.py b/isso/compat.py index e38ecc5..c93270d 100644 --- a/isso/compat.py +++ b/isso/compat.py @@ -12,11 +12,13 @@ except NameError: # Python 3 if not PY2K: buffer = memoryview filter, map, zip = filter, map, zip - iteritems = lambda dikt: iter(dikt.items()) # noqa: E731 + + def iteritems(dikt): return iter(dikt.items()) # noqa: E731 from functools import reduce else: buffer = buffer from itertools import ifilter, imap, izip filter, map, zip = ifilter, imap, izip - iteritems = lambda dikt: dikt.iteritems() # noqa: E731 + + def iteritems(dikt): return dikt.iteritems() # noqa: E731 reduce = reduce diff --git a/isso/config.py b/isso/config.py index c2a05ce..488224b 100644 --- a/isso/config.py +++ b/isso/config.py @@ -123,8 +123,8 @@ def new(options=None): def load(default, user=None): # return set of (section, option) - setify = lambda cp: set((section, option) for section in cp.sections() - for option in cp.options(section)) + def setify(cp): return set((section, option) for section in cp.sections() + for option in cp.options(section)) parser = new() parser.read(default) diff --git a/isso/core.py b/isso/core.py index 368c8e3..9992df9 100644 --- a/isso/core.py +++ b/isso/core.py @@ -122,7 +122,8 @@ class uWSGIMixin(Mixin): self.cache = uWSGICache timedelta = conf.getint("moderation", "purge-after") - purge = lambda signum: self.db.comments.purge(timedelta) + + def purge(signum): return self.db.comments.purge(timedelta) uwsgi.register_signal(1, "", purge) uwsgi.add_timer(1, timedelta) diff --git a/isso/db/__init__.py b/isso/db/__init__.py index 919137c..ecc7a44 100644 --- a/isso/db/__init__.py +++ b/isso/db/__init__.py @@ -98,10 +98,11 @@ class SQLite3: # limit max. nesting level to 1 if self.version == 2: - first = lambda rv: list(map(operator.itemgetter(0), rv)) + def first(rv): return list(map(operator.itemgetter(0), rv)) with sqlite3.connect(self.path) as con: - top = first(con.execute("SELECT id FROM comments WHERE parent IS NULL").fetchall()) + top = first(con.execute( + "SELECT id FROM comments WHERE parent IS NULL").fetchall()) flattened = defaultdict(set) for id in top: @@ -109,13 +110,15 @@ class SQLite3: ids = [id, ] while ids: - rv = first(con.execute("SELECT id FROM comments WHERE parent=?", (ids.pop(), ))) + rv = first(con.execute( + "SELECT id FROM comments WHERE parent=?", (ids.pop(), ))) ids.extend(rv) flattened[id].update(set(rv)) for id in flattened.keys(): for n in flattened[id]: - con.execute("UPDATE comments SET parent=? WHERE id=?", (id, n)) + con.execute( + "UPDATE comments SET parent=? WHERE id=?", (id, n)) con.execute('PRAGMA user_version = 3') logger.info("%i rows changed", con.total_changes) diff --git a/isso/db/comments.py b/isso/db/comments.py index a66c248..0f359a3 100644 --- a/isso/db/comments.py +++ b/isso/db/comments.py @@ -83,7 +83,7 @@ class Comments: """ self.db.execute([ 'UPDATE comments SET', - ','.join(key + '=' + '?' for key in data), + ','.join(key + '=' + '?' for key in data), 'WHERE id=?;'], list(data.values()) + [id]) @@ -94,7 +94,8 @@ class Comments: Search for comment :param:`id` and return a mapping of :attr:`fields` and values. """ - rv = self.db.execute('SELECT * FROM comments WHERE id=?', (id, )).fetchone() + rv = self.db.execute( + 'SELECT * FROM comments WHERE id=?', (id, )).fetchone() if rv: return dict(zip(Comments.fields, rv)) @@ -122,7 +123,7 @@ class Comments: for f in fields_comments]) sql_threads_fields = ', '.join(['threads.' + f for f in fields_threads]) - sql = ['SELECT ' + sql_comments_fields + ', ' + \ + sql = ['SELECT ' + sql_comments_fields + ', ' + sql_threads_fields + ' ' 'FROM comments INNER JOIN threads ' 'ON comments.tid=threads.id ' @@ -162,9 +163,9 @@ class Comments: """ Return comments for :param:`uri` with :param:`mode`. """ - sql = [ 'SELECT comments.* FROM comments INNER JOIN threads ON', - ' threads.uri=? AND comments.tid=threads.id AND (? | comments.mode) = ?', - ' AND comments.created>?'] + sql = ['SELECT comments.* FROM comments INNER JOIN threads ON', + ' threads.uri=? AND comments.tid=threads.id AND (? | comments.mode) = ?', + ' AND comments.created>?'] sql_args = [uri, mode, mode, after] @@ -216,7 +217,8 @@ class Comments: In the second case this comment can be safely removed without any side effects.""" - refs = self.db.execute('SELECT * FROM comments WHERE parent=?', (id, )).fetchone() + refs = self.db.execute( + 'SELECT * FROM comments WHERE parent=?', (id, )).fetchone() if refs is None: self.db.execute('DELETE FROM comments WHERE id=?', (id, )) @@ -226,7 +228,8 @@ class Comments: self.db.execute('UPDATE comments SET text=? WHERE id=?', ('', id)) self.db.execute('UPDATE comments SET mode=? WHERE id=?', (4, id)) for field in ('author', 'website'): - self.db.execute('UPDATE comments SET %s=? WHERE id=?' % field, (None, id)) + self.db.execute('UPDATE comments SET %s=? WHERE id=?' % + field, (None, id)) self._remove_stale() return self.get(id) diff --git a/isso/db/preferences.py b/isso/db/preferences.py index 28936e4..3eb12a3 100644 --- a/isso/db/preferences.py +++ b/isso/db/preferences.py @@ -24,7 +24,7 @@ class Preferences: def get(self, key, default=None): rv = self.db.execute( - 'SELECT value FROM preferences WHERE key=?', (key, )).fetchone() + 'SELECT value FROM preferences WHERE key=?', (key, )).fetchone() if rv is None: return default diff --git a/isso/db/spam.py b/isso/db/spam.py index d198f6a..5069229 100644 --- a/isso/db/spam.py +++ b/isso/db/spam.py @@ -50,7 +50,7 @@ class Guard: return False, "%i direct responses to %s" % (len(rv), uri) # block replies to self unless :param:`reply-to-self` is enabled - elif self.conf.getboolean("reply-to-self") == False: + elif self.conf.getboolean("reply-to-self") is False: rv = self.db.execute([ 'SELECT id FROM comments WHERE' ' remote_addr = ?', diff --git a/isso/db/threads.py b/isso/db/threads.py index bb7067b..4f0b476 100644 --- a/isso/db/threads.py +++ b/isso/db/threads.py @@ -26,5 +26,6 @@ class Threads(object): return Thread(*self.db.execute("SELECT * FROM threads WHERE uri=?", (uri, )).fetchone()) def new(self, uri, title): - self.db.execute("INSERT INTO threads (uri, title) VALUES (?, ?)", (uri, title)) + self.db.execute( + "INSERT INTO threads (uri, title) VALUES (?, ?)", (uri, title)) return self[uri] diff --git a/isso/dispatch.py b/isso/dispatch.py index bf3a163..7c8bb14 100644 --- a/isso/dispatch.py +++ b/isso/dispatch.py @@ -25,7 +25,8 @@ class Dispatcher(DispatcherMiddleware): def __init__(self, *confs): self.isso = {} - default = os.path.join(dist.location, dist.project_name, "defaults.ini") + default = os.path.join( + dist.location, dist.project_name, "defaults.ini") for i, path in enumerate(confs): conf = config.load(default, path) @@ -45,7 +46,8 @@ class Dispatcher(DispatcherMiddleware): return super(Dispatcher, self).__call__(environ, start_response) def default(self, environ, start_response): - resp = Response("\n".join(self.isso.keys()), 404, content_type="text/plain") + resp = Response("\n".join(self.isso.keys()), + 404, content_type="text/plain") return resp(environ, start_response) diff --git a/isso/ext/notifications.py b/isso/ext/notifications.py index 3ba708b..80e1504 100644 --- a/isso/ext/notifications.py +++ b/isso/ext/notifications.py @@ -58,7 +58,8 @@ class SMTP(object): uwsgi.spooler = spooler def __enter__(self): - klass = (smtplib.SMTP_SSL if self.conf.get('security') == 'ssl' else smtplib.SMTP) + klass = (smtplib.SMTP_SSL if self.conf.get( + 'security') == 'ssl' else smtplib.SMTP) self.client = klass(host=self.conf.get('host'), port=self.conf.getint('port'), timeout=self.conf.getint('timeout')) @@ -104,7 +105,8 @@ class SMTP(object): rv.write("User's URL: %s\n" % comment["website"]) rv.write("IP address: %s\n" % comment["remote_addr"]) - rv.write("Link to comment: %s\n" % (local("origin") + thread["uri"] + "#isso-%i" % comment["id"])) + rv.write("Link to comment: %s\n" % + (local("origin") + thread["uri"] + "#isso-%i" % comment["id"])) rv.write("\n") uri = local("host") + "/id/%i" % comment["id"] @@ -173,7 +175,8 @@ class Stdout(object): logger.info("comment created: %s", json.dumps(comment)) def _edit_comment(self, comment): - logger.info('comment %i edited: %s', comment["id"], json.dumps(comment)) + logger.info('comment %i edited: %s', + comment["id"], json.dumps(comment)) def _delete_comment(self, id): logger.info('comment %i deleted', id) diff --git a/isso/migrate.py b/isso/migrate.py index f53341a..f6297b7 100644 --- a/isso/migrate.py +++ b/isso/migrate.py @@ -30,6 +30,7 @@ from xml.etree import ElementTree logger = logging.getLogger("isso") + def strip(val): if isinstance(val, string_types): return val.strip() @@ -82,7 +83,8 @@ class Disqus(object): remap = dict() if path not in self.db.threads: - self.db.threads.new(path, thread.find(Disqus.ns + 'title').text.strip()) + self.db.threads.new(path, thread.find( + Disqus.ns + 'title').text.strip()) for item in sorted(posts, key=lambda k: k['created']): @@ -112,9 +114,11 @@ class Disqus(object): } if post.find(Disqus.ns + 'parent') is not None: - item['dsq:parent'] = post.find(Disqus.ns + 'parent').attrib.get(Disqus.internals + 'id') + item['dsq:parent'] = post.find( + Disqus.ns + 'parent').attrib.get(Disqus.internals + 'id') - res[post.find('%sthread' % Disqus.ns).attrib.get(Disqus.internals + 'id')].append(item) + res[post.find('%sthread' % Disqus.ns).attrib.get( + Disqus.internals + 'id')].append(item) progress = Progress(len(tree.findall(Disqus.ns + 'thread'))) for i, thread in enumerate(tree.findall(Disqus.ns + 'thread')): @@ -135,7 +139,8 @@ class Disqus(object): progress.finish("{0} threads, {1} comments".format( len(self.threads), len(self.comments))) - orphans = set(map(lambda e: e.attrib.get(Disqus.internals + "id"), tree.findall(Disqus.ns + "post"))) - self.comments + orphans = set(map(lambda e: e.attrib.get(Disqus.internals + "id"), + tree.findall(Disqus.ns + "post"))) - self.comments if orphans and not self.threads: print("Isso couldn't import any thread, try again with --empty-id") elif orphans: @@ -258,22 +263,22 @@ def autodetect(peek): def dispatch(type, db, dump, empty_id=False): - if db.execute("SELECT * FROM comments").fetchone(): - if input("Isso DB is not empty! Continue? [y/N]: ") not in ("y", "Y"): - raise SystemExit("Abort.") - - if type == "disqus": - cls = Disqus - elif type == "wordpress": - cls = WordPress - else: - with io.open(dump, encoding="utf-8") as fp: - cls = autodetect(fp.read(io.DEFAULT_BUFFER_SIZE)) + if db.execute("SELECT * FROM comments").fetchone(): + if input("Isso DB is not empty! Continue? [y/N]: ") not in ("y", "Y"): + raise SystemExit("Abort.") + + if type == "disqus": + cls = Disqus + elif type == "wordpress": + cls = WordPress + else: + with io.open(dump, encoding="utf-8") as fp: + cls = autodetect(fp.read(io.DEFAULT_BUFFER_SIZE)) - if cls is None: - raise SystemExit("Unknown format, abort.") + if cls is None: + raise SystemExit("Unknown format, abort.") - if cls is Disqus: - cls = functools.partial(cls, empty_id=empty_id) + if cls is Disqus: + cls = functools.partial(cls, empty_id=empty_id) - cls(db, dump).migrate() + cls(db, dump).migrate() diff --git a/isso/tests/fixtures.py b/isso/tests/fixtures.py index 710d0d2..303e8bb 100644 --- a/isso/tests/fixtures.py +++ b/isso/tests/fixtures.py @@ -37,5 +37,7 @@ class Dummy: pass -curl = lambda method, host, path: Dummy() -loads = lambda data: json.loads(data.decode('utf-8')) +def curl(method, host, path): return Dummy() + + +def loads(data): return json.loads(data.decode('utf-8')) diff --git a/isso/tests/test_comments.py b/isso/tests/test_comments.py index e7bc011..cc27ae8 100644 --- a/isso/tests/test_comments.py +++ b/isso/tests/test_comments.py @@ -50,7 +50,8 @@ class TestComments(unittest.TestCase): def testGet(self): - self.post('/new?uri=%2Fpath%2F', data=json.dumps({'text': 'Lorem ipsum ...'})) + self.post('/new?uri=%2Fpath%2F', + data=json.dumps({'text': 'Lorem ipsum ...'})) r = self.get('/id/1') self.assertEqual(r.status_code, 200) @@ -61,7 +62,8 @@ class TestComments(unittest.TestCase): def testCreate(self): - rv = self.post('/new?uri=%2Fpath%2F', data=json.dumps({'text': 'Lorem ipsum ...'})) + rv = self.post('/new?uri=%2Fpath%2F', + data=json.dumps({'text': 'Lorem ipsum ...'})) self.assertEqual(rv.status_code, 201) self.assertIn("Set-Cookie", rv.headers) @@ -73,7 +75,8 @@ class TestComments(unittest.TestCase): def textCreateWithNonAsciiText(self): - rv = self.post('/new?uri=%2Fpath%2F', data=json.dumps({'text': 'Здравствуй, мир!'})) + rv = self.post('/new?uri=%2Fpath%2F', + data=json.dumps({'text': 'Здравствуй, мир!'})) self.assertEqual(rv.status_code, 201) rv = loads(rv.data) @@ -105,14 +108,16 @@ class TestComments(unittest.TestCase): def testCreateInvalidParent(self): self.post('/new?uri=test', data=json.dumps({'text': '...'})) - self.post('/new?uri=test', data=json.dumps({'text': '...', 'parent': 1})) - invalid = self.post('/new?uri=test', data=json.dumps({'text': '...', 'parent': 2})) + self.post('/new?uri=test', + data=json.dumps({'text': '...', 'parent': 1})) + invalid = self.post( + '/new?uri=test', data=json.dumps({'text': '...', 'parent': 2})) self.assertEqual(loads(invalid.data)["parent"], 1) def testVerifyFields(self): - verify = lambda comment: comments.API.verify(comment)[0] + def verify(comment): return comments.API.verify(comment)[0] # text is missing self.assertFalse(verify({})) @@ -128,10 +133,12 @@ class TestComments(unittest.TestCase): # email/website length self.assertTrue(verify({"text": "...", "email": "*"*254})) - self.assertTrue(verify({"text": "...", "website": "google.de/" + "a"*128})) + self.assertTrue( + verify({"text": "...", "website": "google.de/" + "a"*128})) self.assertFalse(verify({"text": "...", "email": "*"*1024})) - self.assertFalse(verify({"text": "...", "website": "google.de/" + "*"*1024})) + self.assertFalse( + verify({"text": "...", "website": "google.de/" + "*"*1024})) # valid website url self.assertTrue(comments.isurl("example.tld")) @@ -139,7 +146,8 @@ class TestComments(unittest.TestCase): self.assertTrue(comments.isurl("https://example.tld")) self.assertTrue(comments.isurl("https://example.tld:1337/")) self.assertTrue(comments.isurl("https://example.tld:1337/foobar")) - self.assertTrue(comments.isurl("https://example.tld:1337/foobar?p=1#isso-thread")) + self.assertTrue(comments.isurl( + "https://example.tld:1337/foobar?p=1#isso-thread")) self.assertFalse(comments.isurl("ftp://example.tld/")) self.assertFalse(comments.isurl("tel:+1234567890")) @@ -149,7 +157,8 @@ class TestComments(unittest.TestCase): def testGetInvalid(self): self.assertEqual(self.get('/?uri=%2Fpath%2F&id=123').status_code, 404) - self.assertEqual(self.get('/?uri=%2Fpath%2Fspam%2F&id=123').status_code, 404) + self.assertEqual( + self.get('/?uri=%2Fpath%2Fspam%2F&id=123').status_code, 404) self.assertEqual(self.get('/?uri=?uri=%foo%2F').status_code, 404) def testGetLimited(self): @@ -166,7 +175,8 @@ class TestComments(unittest.TestCase): def testGetNested(self): self.post('/new?uri=test', data=json.dumps({'text': '...'})) - self.post('/new?uri=test', data=json.dumps({'text': '...', 'parent': 1})) + self.post('/new?uri=test', + data=json.dumps({'text': '...', 'parent': 1})) r = self.get('/?uri=test&parent=1') self.assertEqual(r.status_code, 200) @@ -178,7 +188,8 @@ class TestComments(unittest.TestCase): self.post('/new?uri=test', data=json.dumps({'text': '...'})) for i in range(20): - self.post('/new?uri=test', data=json.dumps({'text': '...', 'parent': 1})) + self.post('/new?uri=test', + data=json.dumps({'text': '...', 'parent': 1})) r = self.get('/?uri=test&parent=1&limit=10') self.assertEqual(r.status_code, 200) @@ -188,7 +199,8 @@ class TestComments(unittest.TestCase): def testUpdate(self): - self.post('/new?uri=%2Fpath%2F', data=json.dumps({'text': 'Lorem ipsum ...'})) + self.post('/new?uri=%2Fpath%2F', + data=json.dumps({'text': 'Lorem ipsum ...'})) self.put('/id/1', data=json.dumps({ 'text': 'Hello World', 'author': 'me', 'website': 'http://example.com/'})) @@ -203,7 +215,8 @@ class TestComments(unittest.TestCase): def testDelete(self): - self.post('/new?uri=%2Fpath%2F', data=json.dumps({'text': 'Lorem ipsum ...'})) + self.post('/new?uri=%2Fpath%2F', + data=json.dumps({'text': 'Lorem ipsum ...'})) r = self.delete('/id/1') self.assertEqual(r.status_code, 200) self.assertEqual(loads(r.data), None) @@ -213,7 +226,8 @@ class TestComments(unittest.TestCase): client = JSONClient(self.app, Response) client.post('/new?uri=%2Fpath%2F', data=json.dumps({'text': 'First'})) - client.post('/new?uri=%2Fpath%2F', data=json.dumps({'text': 'First', 'parent': 1})) + client.post('/new?uri=%2Fpath%2F', + data=json.dumps({'text': 'First', 'parent': 1})) r = client.delete('/id/1') self.assertEqual(r.status_code, 200) @@ -242,8 +256,10 @@ class TestComments(unittest.TestCase): client = JSONClient(self.app, Response) client.post('/new?uri=%2Fpath%2F', data=json.dumps({'text': 'First'})) - client.post('/new?uri=%2Fpath%2F', data=json.dumps({'text': 'Second', 'parent': 1})) - client.post('/new?uri=%2Fpath%2F', data=json.dumps({'text': 'Third', 'parent': 1})) + client.post('/new?uri=%2Fpath%2F', + data=json.dumps({'text': 'Second', 'parent': 1})) + client.post('/new?uri=%2Fpath%2F', + data=json.dumps({'text': 'Third', 'parent': 1})) client.post('/new?uri=%2Fpath%2F', data=json.dumps({'text': 'Last'})) client.delete('/id/1') @@ -261,10 +277,11 @@ class TestComments(unittest.TestCase): for path in paths: self.assertEqual(self.post('/new?' + urlencode({'uri': path}), - data=json.dumps({'text': '...'})).status_code, 201) + data=json.dumps({'text': '...'})).status_code, 201) for i, path in enumerate(paths): - self.assertEqual(self.get('/?' + urlencode({'uri': path})).status_code, 200) + self.assertEqual( + self.get('/?' + urlencode({'uri': path})).status_code, 200) self.assertEqual(self.get('/id/%i' % (i + 1)).status_code, 200) def testDeleteAndCreateByDifferentUsersButSamePostId(self): @@ -283,7 +300,8 @@ class TestComments(unittest.TestCase): a = self.post('/new?uri=%2Fpath%2F', data=json.dumps({"text": "Aaa"})) b = self.post('/new?uri=%2Fpath%2F', data=json.dumps({"text": "Bbb"})) - c = self.post('/new?uri=%2Fpath%2F', data=json.dumps({"text": "Ccc", "email": "..."})) + c = self.post('/new?uri=%2Fpath%2F', + data=json.dumps({"text": "Ccc", "email": "..."})) a = loads(a.data) b = loads(b.data) @@ -295,7 +313,8 @@ class TestComments(unittest.TestCase): def testVisibleFields(self): - rv = self.post('/new?uri=%2Fpath%2F', data=json.dumps({"text": "...", "invalid": "field"})) + rv = self.post('/new?uri=%2Fpath%2F', + data=json.dumps({"text": "...", "invalid": "field"})) self.assertEqual(rv.status_code, 201) rv = loads(rv.data) @@ -333,7 +352,8 @@ class TestComments(unittest.TestCase): for uri, count in iteritems(expected): for _ in range(count): - self.post('/new?uri=%s' % uri, data=json.dumps({"text": "..."})) + self.post('/new?uri=%s' % + uri, data=json.dumps({"text": "..."})) rv = self.post('/count', data=json.dumps(list(expected.keys()))) self.assertEqual(loads(rv.data), list(expected.values())) @@ -362,22 +382,26 @@ class TestComments(unittest.TestCase): self.post('/new?uri=%2F', data=json.dumps({"text": "..."})) # no header is fine (default for XHR) - self.assertEqual(self.post('/id/1/dislike', content_type="").status_code, 200) + self.assertEqual( + self.post('/id/1/dislike', content_type="").status_code, 200) # x-www-form-urlencoded is definitely not RESTful - self.assertEqual(self.post('/id/1/dislike', content_type=form).status_code, 403) + self.assertEqual( + self.post('/id/1/dislike', content_type=form).status_code, 403) self.assertEqual(self.post('/new?uri=%2F', data=json.dumps({"text": "..."}), - content_type=form).status_code, 403) + content_type=form).status_code, 403) # just for the record - self.assertEqual(self.post('/id/1/dislike', content_type=js).status_code, 200) + self.assertEqual( + self.post('/id/1/dislike', content_type=js).status_code, 200) def testPreview(self): - response = self.post('/preview', data=json.dumps({'text': 'This is **mark***down*'})) + response = self.post( + '/preview', data=json.dumps({'text': 'This is **mark***down*'})) self.assertEqual(response.status_code, 200) rv = loads(response.data) - self.assertEqual(rv["text"], '

This is markdown

') - + self.assertEqual( + rv["text"], '

This is markdown

') class TestModeratedComments(unittest.TestCase): @@ -402,7 +426,8 @@ class TestModeratedComments(unittest.TestCase): def testAddComment(self): - rv = self.client.post('/new?uri=test', data=json.dumps({"text": "..."})) + rv = self.client.post( + '/new?uri=test', data=json.dumps({"text": "..."})) self.assertEqual(rv.status_code, 202) self.assertEqual(self.client.get('/id/1').status_code, 200) diff --git a/isso/tests/test_cors.py b/isso/tests/test_cors.py index 415ea51..c1d22a0 100644 --- a/isso/tests/test_cors.py +++ b/isso/tests/test_cors.py @@ -19,31 +19,37 @@ class CORSTest(unittest.TestCase): def test_simple(self): app = CORSMiddleware(hello_world, - origin=origin([ - "https://example.tld/", - "http://example.tld/", - ]), - allowed=("Foo", "Bar"), exposed=("Spam", )) + origin=origin([ + "https://example.tld/", + "http://example.tld/", + ]), + allowed=("Foo", "Bar"), exposed=("Spam", )) client = Client(app, Response) rv = client.get("/", headers={"Origin": "https://example.tld"}) - self.assertEqual(rv.headers["Access-Control-Allow-Origin"], "https://example.tld") - self.assertEqual(rv.headers["Access-Control-Allow-Credentials"], "true") - self.assertEqual(rv.headers["Access-Control-Allow-Methods"], "HEAD, GET, POST, PUT, DELETE") - self.assertEqual(rv.headers["Access-Control-Allow-Headers"], "Foo, Bar") + self.assertEqual( + rv.headers["Access-Control-Allow-Origin"], "https://example.tld") + self.assertEqual( + rv.headers["Access-Control-Allow-Credentials"], "true") + self.assertEqual( + rv.headers["Access-Control-Allow-Methods"], "HEAD, GET, POST, PUT, DELETE") + self.assertEqual( + rv.headers["Access-Control-Allow-Headers"], "Foo, Bar") self.assertEqual(rv.headers["Access-Control-Expose-Headers"], "Spam") a = client.get("/", headers={"Origin": "http://example.tld"}) - self.assertEqual(a.headers["Access-Control-Allow-Origin"], "http://example.tld") + self.assertEqual( + a.headers["Access-Control-Allow-Origin"], "http://example.tld") b = client.get("/", headers={"Origin": "http://example.tld"}) - self.assertEqual(b.headers["Access-Control-Allow-Origin"], "http://example.tld") + self.assertEqual( + b.headers["Access-Control-Allow-Origin"], "http://example.tld") c = client.get("/", headers={"Origin": "http://foo.other"}) - self.assertEqual(c.headers["Access-Control-Allow-Origin"], "https://example.tld") - + self.assertEqual( + c.headers["Access-Control-Allow-Origin"], "https://example.tld") def test_preflight(self): @@ -51,10 +57,12 @@ class CORSTest(unittest.TestCase): allowed=("Foo", ), exposed=("Bar", )) client = Client(app, Response) - rv = client.open(method="OPTIONS", path="/", headers={"Origin": "http://example.tld"}) + rv = client.open(method="OPTIONS", path="/", + headers={"Origin": "http://example.tld"}) self.assertEqual(rv.status_code, 200) for hdr in ("Origin", "Headers", "Credentials", "Methods"): self.assertIn("Access-Control-Allow-%s" % hdr, rv.headers) - self.assertEqual(rv.headers["Access-Control-Allow-Origin"], "http://example.tld") + self.assertEqual( + rv.headers["Access-Control-Allow-Origin"], "http://example.tld") diff --git a/isso/tests/test_db.py b/isso/tests/test_db.py index 74e0831..e3e7926 100644 --- a/isso/tests/test_db.py +++ b/isso/tests/test_db.py @@ -66,10 +66,10 @@ class TestDBMigration(unittest.TestCase): tree = { 1: None, 2: None, - 3: 2, - 4: 3, - 7: 3, - 5: 2, + 3: 2, + 4: 3, + 7: 3, + 5: 2, 6: None } @@ -91,7 +91,8 @@ class TestDBMigration(unittest.TestCase): " dislikes INTEGER DEFAULT 0," " voters BLOB)") - con.execute("INSERT INTO threads (uri, title) VALUES (?, ?)", ("/", "Test")) + con.execute( + "INSERT INTO threads (uri, title) VALUES (?, ?)", ("/", "Test")) for (id, parent) in iteritems(tree): con.execute("INSERT INTO comments (" " id, parent, created)" @@ -108,13 +109,14 @@ class TestDBMigration(unittest.TestCase): flattened = list(iteritems({ 1: None, 2: None, - 3: 2, - 4: 2, - 5: 2, + 3: 2, + 4: 2, + 5: 2, 6: None, 7: 2 })) with sqlite3.connect(self.path) as con: - rv = con.execute("SELECT id, parent FROM comments ORDER BY created").fetchall() + rv = con.execute( + "SELECT id, parent FROM comments ORDER BY created").fetchall() self.assertEqual(flattened, rv) diff --git a/isso/tests/test_guard.py b/isso/tests/test_guard.py index a9ae365..4bbb50e 100644 --- a/isso/tests/test_guard.py +++ b/isso/tests/test_guard.py @@ -68,7 +68,8 @@ class TestGuard(unittest.TestCase): alice = self.makeClient("1.2.3.4", 2) for i in range(2): - self.assertEqual(alice.post("/new?uri=test", data=self.data).status_code, 201) + self.assertEqual(alice.post( + "/new?uri=test", data=self.data).status_code, 201) bob.application.db.execute([ "UPDATE comments SET", @@ -76,7 +77,8 @@ class TestGuard(unittest.TestCase): "WHERE remote_addr = '127.0.0.0'" ]) - self.assertEqual(bob.post("/new?uri=test", data=self.data).status_code, 201) + self.assertEqual( + bob.post("/new?uri=test", data=self.data).status_code, 201) def testDirectReply(self): @@ -95,11 +97,13 @@ class TestGuard(unittest.TestCase): def testSelfReply(self): - payload = lambda id: json.dumps({"text": "...", "parent": id}) + def payload(id): return json.dumps({"text": "...", "parent": id}) client = self.makeClient("127.0.0.1", self_reply=False) - self.assertEqual(client.post("/new?uri=test", data=self.data).status_code, 201) - self.assertEqual(client.post("/new?uri=test", data=payload(1)).status_code, 403) + self.assertEqual(client.post( + "/new?uri=test", data=self.data).status_code, 201) + self.assertEqual(client.post( + "/new?uri=test", data=payload(1)).status_code, 403) client.application.db.execute([ "UPDATE comments SET", @@ -107,39 +111,55 @@ class TestGuard(unittest.TestCase): "WHERE id = 1" ], (client.application.conf.getint("general", "max-age"), )) - self.assertEqual(client.post("/new?uri=test", data=payload(1)).status_code, 201) + self.assertEqual(client.post( + "/new?uri=test", data=payload(1)).status_code, 201) client = self.makeClient("128.0.0.1", ratelimit=3, self_reply=False) - self.assertEqual(client.post("/new?uri=test", data=self.data).status_code, 201) - self.assertEqual(client.post("/new?uri=test", data=payload(1)).status_code, 201) - self.assertEqual(client.post("/new?uri=test", data=payload(2)).status_code, 201) + self.assertEqual(client.post( + "/new?uri=test", data=self.data).status_code, 201) + self.assertEqual(client.post( + "/new?uri=test", data=payload(1)).status_code, 201) + self.assertEqual(client.post( + "/new?uri=test", data=payload(2)).status_code, 201) def testRequireEmail(self): - payload = lambda email: json.dumps({"text": "...", "email": email}) + def payload(email): return json.dumps({"text": "...", "email": email}) client = self.makeClient("127.0.0.1", ratelimit=4, require_email=False) - client_strict = self.makeClient("127.0.0.2", ratelimit=4, require_email=True) + client_strict = self.makeClient( + "127.0.0.2", ratelimit=4, require_email=True) # if we don't require email - self.assertEqual(client.post("/new?uri=test", data=payload("")).status_code, 201) - self.assertEqual(client.post("/new?uri=test", data=payload("test@me.more")).status_code, 201) + self.assertEqual(client.post( + "/new?uri=test", data=payload("")).status_code, 201) + self.assertEqual(client.post( + "/new?uri=test", data=payload("test@me.more")).status_code, 201) # if we do require email - self.assertEqual(client_strict.post("/new?uri=test", data=payload("")).status_code, 403) - self.assertEqual(client_strict.post("/new?uri=test", data=payload("test@me.more")).status_code, 201) + self.assertEqual(client_strict.post( + "/new?uri=test", data=payload("")).status_code, 403) + self.assertEqual(client_strict.post( + "/new?uri=test", data=payload("test@me.more")).status_code, 201) def testRequireAuthor(self): - payload = lambda author: json.dumps({"text": "...", "author": author}) + def payload(author): return json.dumps( + {"text": "...", "author": author}) - client = self.makeClient("127.0.0.1", ratelimit=4, require_author=False) - client_strict = self.makeClient("127.0.0.2", ratelimit=4, require_author=True) + client = self.makeClient( + "127.0.0.1", ratelimit=4, require_author=False) + client_strict = self.makeClient( + "127.0.0.2", ratelimit=4, require_author=True) # if we don't require author - self.assertEqual(client.post("/new?uri=test", data=payload("")).status_code, 201) - self.assertEqual(client.post("/new?uri=test", data=payload("pipo author")).status_code, 201) + self.assertEqual(client.post( + "/new?uri=test", data=payload("")).status_code, 201) + self.assertEqual(client.post( + "/new?uri=test", data=payload("pipo author")).status_code, 201) # if we do require author - self.assertEqual(client_strict.post("/new?uri=test", data=payload("")).status_code, 403) - self.assertEqual(client_strict.post("/new?uri=test", data=payload("pipo author")).status_code, 201) + self.assertEqual(client_strict.post( + "/new?uri=test", data=payload("")).status_code, 403) + self.assertEqual(client_strict.post( + "/new?uri=test", data=payload("pipo author")).status_code, 201) diff --git a/isso/tests/test_html.py b/isso/tests/test_html.py index 362e239..316fbf8 100644 --- a/isso/tests/test_html.py +++ b/isso/tests/test_html.py @@ -64,7 +64,8 @@ class TestHTML(unittest.TestCase): sanitizer = html.Sanitizer(elements=[], attributes=[]) examples = [ ('Look: ', 'Look: '), - ('Ha', 'Ha'), + ('Ha', + 'Ha'), ('Ha', 'Ha'), ('

Test

', '

Test

'), ('', 'alert("Onoe")')] diff --git a/isso/tests/test_migration.py b/isso/tests/test_migration.py index 8a876ee..151f27b 100644 --- a/isso/tests/test_migration.py +++ b/isso/tests/test_migration.py @@ -29,7 +29,8 @@ class TestMigration(unittest.TestCase): db = SQLite3(xxx.name, conf) Disqus(db, xml).migrate() - self.assertEqual(len(db.execute("SELECT id FROM comments").fetchall()), 2) + self.assertEqual( + len(db.execute("SELECT id FROM comments").fetchall()), 2) self.assertEqual(db.threads["/"]["title"], "Hello, World!") self.assertEqual(db.threads["/"]["id"], 1) @@ -57,8 +58,10 @@ class TestMigration(unittest.TestCase): self.assertEqual(db.threads["/?p=4"]["title"], "...") self.assertEqual(db.threads["/?p=4"]["id"], 2) - self.assertEqual(len(db.execute("SELECT id FROM threads").fetchall()), 2) - self.assertEqual(len(db.execute("SELECT id FROM comments").fetchall()), 7) + self.assertEqual( + len(db.execute("SELECT id FROM threads").fetchall()), 2) + self.assertEqual( + len(db.execute("SELECT id FROM comments").fetchall()), 7) first = db.comments.get(1) self.assertEqual(first["author"], "Ohai") diff --git a/isso/tests/test_utils.py b/isso/tests/test_utils.py index 0504c15..253328c 100644 --- a/isso/tests/test_utils.py +++ b/isso/tests/test_utils.py @@ -12,7 +12,8 @@ class TestUtils(unittest.TestCase): examples = [ (u'12.34.56.78', u'12.34.56.0'), - (u'1234:5678:90ab:cdef:fedc:ba09:8765:4321', u'1234:5678:90ab:0000:0000:0000:0000:0000'), + (u'1234:5678:90ab:cdef:fedc:ba09:8765:4321', + u'1234:5678:90ab:0000:0000:0000:0000:0000'), (u'::ffff:127.0.0.1', u'127.0.0.0')] for (addr, anonymized) in examples: @@ -56,4 +57,4 @@ class TestParse(unittest.TestCase): """), ('test', 'Test')) self.assertEqual(parse.thread('
'), - ('Fuu.', 'Untitled.')) + ('Fuu.', 'Untitled.')) diff --git a/isso/tests/test_utils_hash.py b/isso/tests/test_utils_hash.py index 18a790e..5d5160d 100644 --- a/isso/tests/test_utils_hash.py +++ b/isso/tests/test_utils_hash.py @@ -33,7 +33,8 @@ class TestHasher(unittest.TestCase): class TestPBKDF2(unittest.TestCase): def test_default(self): - pbkdf2 = PBKDF2(iterations=1000) # original setting (and still default) + # original setting (and still default) + pbkdf2 = PBKDF2(iterations=1000) self.assertEqual(pbkdf2.uhash(""), "42476aafe2e4") def test_different_salt(self): diff --git a/isso/tests/test_vote.py b/isso/tests/test_vote.py index 4045ccb..3e3b1b8 100644 --- a/isso/tests/test_vote.py +++ b/isso/tests/test_vote.py @@ -37,13 +37,15 @@ class TestVote(unittest.TestCase): def testZeroLikes(self): - rv = self.makeClient("127.0.0.1").post("/new?uri=test", data=json.dumps({"text": "..."})) + rv = self.makeClient("127.0.0.1").post( + "/new?uri=test", data=json.dumps({"text": "..."})) self.assertEqual(loads(rv.data)['likes'], 0) self.assertEqual(loads(rv.data)['dislikes'], 0) def testSingleLike(self): - self.makeClient("127.0.0.1").post("/new?uri=test", data=json.dumps({"text": "..."})) + self.makeClient("127.0.0.1").post( + "/new?uri=test", data=json.dumps({"text": "..."})) rv = self.makeClient("0.0.0.0").post("/id/1/like") self.assertEqual(rv.status_code, 200) @@ -60,7 +62,8 @@ class TestVote(unittest.TestCase): def testMultipleLikes(self): - self.makeClient("127.0.0.1").post("/new?uri=test", data=json.dumps({"text": "..."})) + self.makeClient("127.0.0.1").post( + "/new?uri=test", data=json.dumps({"text": "..."})) for num in range(15): rv = self.makeClient("1.2.%i.0" % num).post('/id/1/like') self.assertEqual(rv.status_code, 200) @@ -73,7 +76,8 @@ class TestVote(unittest.TestCase): def testTooManyLikes(self): - self.makeClient("127.0.0.1").post("/new?uri=test", data=json.dumps({"text": "..."})) + self.makeClient("127.0.0.1").post( + "/new?uri=test", data=json.dumps({"text": "..."})) for num in range(256): rv = self.makeClient("1.2.%i.0" % num).post('/id/1/like') self.assertEqual(rv.status_code, 200) @@ -84,7 +88,8 @@ class TestVote(unittest.TestCase): self.assertEqual(loads(rv.data)["likes"], num + 1) def testDislike(self): - self.makeClient("127.0.0.1").post("/new?uri=test", data=json.dumps({"text": "..."})) + 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') self.assertEqual(rv.status_code, 200) diff --git a/isso/utils/__init__.py b/isso/utils/__init__.py index 7536041..a0a07bc 100644 --- a/isso/utils/__init__.py +++ b/isso/utils/__init__.py @@ -89,11 +89,11 @@ class Bloomfilter: def add(self, key): for i in self.get_probes(key): - self.array[i//8] |= 2 ** (i%8) + self.array[i//8] |= 2 ** (i % 8) self.elements += 1 def __contains__(self, key): - return all(self.array[i//8] & (2 ** (i%8)) for i in self.get_probes(key)) + return all(self.array[i//8] & (2 ** (i % 8)) for i in self.get_probes(key)) def __len__(self): return self.elements @@ -117,6 +117,7 @@ def render_template(template_name, **context): '..', 'templates') jinja_env = Environment(loader=FileSystemLoader(template_path), autoescape=True) + def datetimeformat(value): return datetime.fromtimestamp(value).strftime('%H:%M / %d-%m-%Y') diff --git a/isso/utils/hash.py b/isso/utils/hash.py index 0e93a1a..0638c1b 100644 --- a/isso/utils/hash.py +++ b/isso/utils/hash.py @@ -73,7 +73,7 @@ class Hash(object): class PBKDF2(Hash): - + def __init__(self, salt=None, iterations=1000, dklen=6, func="sha1"): super(PBKDF2, self).__init__(salt) @@ -109,4 +109,4 @@ def new(conf): return Hash(salt, algorithm) -sha1 = Hash(func="sha1").uhash \ No newline at end of file +sha1 = Hash(func="sha1").uhash diff --git a/isso/utils/html.py b/isso/utils/html.py index 294b8d4..fca3c7e 100644 --- a/isso/utils/html.py +++ b/isso/utils/html.py @@ -54,7 +54,8 @@ def sanitize(tokenizer, document): builder = "simpletree" stream = html5lib.treewalkers.getTreeWalker(builder)(domtree) - serializer = HTMLSerializer(quote_attr_values=True, omit_optional_tags=False) + serializer = HTMLSerializer( + quote_attr_values=True, omit_optional_tags=False) return serializer.render(stream) diff --git a/isso/utils/parse.py b/isso/utils/parse.py index 85c931b..d15b15f 100644 --- a/isso/utils/parse.py +++ b/isso/utils/parse.py @@ -15,7 +15,7 @@ from isso.compat import map, filter, PY2K if PY2K: # http://bugs.python.org/issue12984 from xml.dom.minidom import NamedNodeMap - NamedNodeMap.__contains__ = lambda self, key: self.has_key(key) + NamedNodeMap.__contains__ = lambda self, key: self.has_key(key) # noqa def thread(data, default=u"Untitled.", id=None): @@ -31,8 +31,8 @@ def thread(data, default=u"Untitled.", id=None): # aka getElementById, but limited to div and section tags el = list(filter(lambda i: i.attributes["id"].value == "isso-thread", - filter(lambda i: "id" in i.attributes, - chain(*map(html.getElementsByTagName, ("div", "section")))))) + filter(lambda i: "id" in i.attributes, + chain(*map(html.getElementsByTagName, ("div", "section")))))) if not el: return id, default diff --git a/isso/views/__init__.py b/isso/views/__init__.py index 0b995cd..54e1619 100644 --- a/isso/views/__init__.py +++ b/isso/views/__init__.py @@ -40,7 +40,8 @@ class requires: try: kwargs[self.param] = self.type(req.args[self.param]) except TypeError: - raise BadRequest("invalid type for %s, expected %s" % (self.param, self.type)) + raise BadRequest("invalid type for %s, expected %s" % + (self.param, self.type)) return func(cls, env, req, *args, **kwargs) diff --git a/isso/views/comments.py b/isso/views/comments.py index c87595b..d3b80c3 100644 --- a/isso/views/comments.py +++ b/isso/views/comments.py @@ -29,7 +29,8 @@ from isso.utils.hash import sha1 __url_re = re.compile( r'^' r'(https?://)?' - r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... + # domain... + r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' r'localhost|' # localhost... r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip r'(?::\d+)?' # optional port @@ -62,12 +63,12 @@ def xhr(func): not forged (XHR is restricted by CORS separately). """ - """ @apiDefine csrf @apiHeader {string="application/json"} Content-Type The content type must be set to `application/json` to prevent CSRF attacks. """ + def dec(self, env, req, *args, **kwargs): if req.content_type and not req.content_type.startswith("application/json"): @@ -93,8 +94,8 @@ class API(object): ('view', ('GET', '/id/')), ('edit', ('PUT', '/id/')), ('delete', ('DELETE', '/id/')), - ('moderate',('GET', '/id///')), - ('moderate',('POST', '/id///')), + ('moderate', ('GET', '/id///')), + ('moderate', ('POST', '/id///')), ('like', ('POST', '/id//like')), ('dislike', ('POST', '/id//dislike')), ('demo', ('GET', '/demo')), @@ -280,13 +281,15 @@ class API(object): self.signal("comments.new:after-save", thread, rv) cookie = functools.partial(dump_cookie, - value=self.isso.sign([rv["id"], sha1(rv["text"])]), - max_age=self.conf.getint('max-age')) + value=self.isso.sign( + [rv["id"], sha1(rv["text"])]), + max_age=self.conf.getint('max-age')) rv["text"] = self.isso.render(rv["text"]) rv["hash"] = self.hash(rv['email'] or rv['remote_addr']) - self.cache.set('hash', (rv['email'] or rv['remote_addr']).encode('utf-8'), rv['hash']) + self.cache.set( + 'hash', (rv['email'] or rv['remote_addr']).encode('utf-8'), rv['hash']) for key in set(rv.keys()) - API.FIELDS: rv.pop(key) @@ -326,6 +329,7 @@ class API(object): "likes": 1 } """ + def view(self, environ, request, id): rv = self.comments.get(id) @@ -409,8 +413,9 @@ class API(object): self.signal("comments.edit", rv) cookie = functools.partial(dump_cookie, - value=self.isso.sign([rv["id"], sha1(rv["text"])]), - max_age=self.conf.getint('max-age')) + value=self.isso.sign( + [rv["id"], sha1(rv["text"])]), + max_age=self.conf.getint('max-age')) rv["text"] = self.isso.render(rv["text"]) @@ -454,7 +459,8 @@ class API(object): if item is None: raise NotFound - self.cache.delete('hash', (item['email'] or item['remote_addr']).encode('utf-8')) + self.cache.delete( + 'hash', (item['email'] or item['remote_addr']).encode('utf-8')) with self.isso.lock: rv = self.comments.delete(id) @@ -505,6 +511,7 @@ class API(object): @apiSuccessExample Using POST: Yo """ + def moderate(self, environ, request, id, action, key): try: id = self.isso.unsign(key, max_age=2**32) @@ -547,11 +554,11 @@ class API(object): else: with self.isso.lock: self.comments.delete(id) - self.cache.delete('hash', (item['email'] or item['remote_addr']).encode('utf-8')) + self.cache.delete( + 'hash', (item['email'] or item['remote_addr']).encode('utf-8')) self.signal("comments.delete", id) return Response("Yo", 200) - """ @api {get} / get comments @apiGroup Thread @@ -681,10 +688,10 @@ class API(object): return BadRequest("nested_limit should be integer") rv = { - 'id' : root_id, - 'total_replies' : reply_counts[root_id], - 'hidden_replies' : reply_counts[root_id] - len(root_list), - 'replies' : self._process_fetched_list(root_list, plain) + 'id': root_id, + 'total_replies': reply_counts[root_id], + 'hidden_replies': reply_counts[root_id] - len(root_list), + 'replies': self._process_fetched_list(root_list, plain) } # We are only checking for one level deep comments if root_id is None: @@ -705,7 +712,8 @@ class API(object): comment['total_replies'] = 0 replies = [] - comment['hidden_replies'] = comment['total_replies'] - len(replies) + comment['hidden_replies'] = comment['total_replies'] - \ + len(replies) comment['replies'] = self._process_fetched_list(replies, plain) return JSON(rv, 200) @@ -762,7 +770,8 @@ class API(object): @xhr def like(self, environ, request, id): - nv = self.comments.vote(True, id, utils.anonymize(str(request.remote_addr))) + nv = self.comments.vote( + True, id, utils.anonymize(str(request.remote_addr))) return JSON(nv, 200) """ @@ -788,7 +797,8 @@ class API(object): @xhr def dislike(self, environ, request, id): - nv = self.comments.vote(False, id, utils.anonymize(str(request.remote_addr))) + nv = self.comments.vote( + False, id, utils.anonymize(str(request.remote_addr))) return JSON(nv, 200) # TODO: remove someday (replaced by :func:`counts`) @@ -814,6 +824,7 @@ class API(object): @apiSuccessExample Counts of 5 threads: [2, 18, 4, 0, 3] """ + def counts(self, environ, request): data = request.get_json() @@ -838,10 +849,11 @@ class API(object): data = req.form password = self.isso.conf.get("general", "admin_password") if data['password'] and data['password'] == password: - response = redirect(get_current_url(env, host_only=True) + '/admin') + response = redirect(get_current_url( + env, host_only=True) + '/admin') cookie = functools.partial(dump_cookie, - value=self.isso.sign({"logged": True}), - expires=datetime.now() + timedelta(1)) + value=self.isso.sign({"logged": True}), + expires=datetime.now() + timedelta(1)) response.headers.add("Set-Cookie", cookie("admin-session")) response.headers.add("X-Set-Cookie", cookie("isso-admin-session")) return response diff --git a/isso/wsgi.py b/isso/wsgi.py index 1bad0f2..1788d47 100644 --- a/isso/wsgi.py +++ b/isso/wsgi.py @@ -136,11 +136,14 @@ class CORSMiddleware(object): headers = Headers(headers) headers.add("Access-Control-Allow-Origin", self.origin(environ)) headers.add("Access-Control-Allow-Credentials", "true") - headers.add("Access-Control-Allow-Methods", ", ".join(self.methods)) + headers.add("Access-Control-Allow-Methods", + ", ".join(self.methods)) if self.allowed: - headers.add("Access-Control-Allow-Headers", ", ".join(self.allowed)) + headers.add("Access-Control-Allow-Headers", + ", ".join(self.allowed)) if self.exposed: - headers.add("Access-Control-Expose-Headers", ", ".join(self.exposed)) + headers.add("Access-Control-Expose-Headers", + ", ".join(self.exposed)) return start_response(status, headers.to_list(), exc_info) if environ.get("REQUEST_METHOD") == "OPTIONS":