Skip to content
This repository has been archived by the owner on May 16, 2020. It is now read-only.

Commit

Permalink
Python 3.6 compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
jace committed Jul 14, 2017
1 parent f12bfe9 commit 9085a53
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 97 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ language: python
cache: pip
python:
- "2.7"
- "3.6"
- "pypy"
addons:
postgresql: "9.4"
Expand Down
98 changes: 49 additions & 49 deletions nodular/publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
assert isinstance(registry, NodeRegistry)
# Publish everything under /
publisher = NodePublisher(root, registry, u'/')
publisher = NodePublisher(root, registry, '/')
@app.route('/<path:anypath>', methods=['GET', 'POST', 'PUT', 'DELETE'])
def publish_path(anypath):
return publisher.publish(anypath)
"""

from urllib import urlencode
from urlparse import urljoin
from __future__ import unicode_literals
from six.moves.urllib.parse import urlencode, urljoin
from flask import request, redirect, g
from .node import pathjoin, Node, NodeAlias
from .exceptions import RootNotFound, NodeGone, ViewNotFound
Expand Down Expand Up @@ -49,45 +49,45 @@ def _make_path_tree(basepath, path):
Tests::
>>> _make_path_tree(u'/', u'')
(u'/', [u'/'])
>>> _make_path_tree(u'/', u'/')
(u'/', [u'/'])
>>> _make_path_tree(u'/', u'/foo')
(u'/foo', [u'/', u'/foo'])
>>> _make_path_tree(u'/', u'/foo/')
(u'/foo', [u'/', u'/foo'])
>>> _make_path_tree(u'/', u'foo')
(u'/foo', [u'/', u'/foo'])
>>> _make_path_tree(u'/', u'/foo/bar')
(u'/foo/bar', [u'/', u'/foo', u'/foo/bar'])
>>> _make_path_tree(u'/', u'foo/bar')
(u'/foo/bar', [u'/', u'/foo', u'/foo/bar'])
>>> _make_path_tree(u'/', u'/foo/bar/baz')
(u'/foo/bar/baz', [u'/', u'/foo', u'/foo/bar', u'/foo/bar/baz'])
>>> _make_path_tree(u'/foo', u'')
(u'/foo', [u'/', u'/foo'])
>>> _make_path_tree(u'/foo', u'/bar')
(u'/foo/bar', [u'/', u'/foo', u'/foo/bar'])
>>> _make_path_tree(u'/foo', u'/bar/')
(u'/foo/bar', [u'/', u'/foo', u'/foo/bar'])
>>> _make_path_tree(u'/foo', u'bar')
(u'/foo/bar', [u'/', u'/foo', u'/foo/bar'])
>>> _make_path_tree('/', '') == ('/', ['/'])
True
>>> _make_path_tree('/', '/') == ('/', ['/'])
True
>>> _make_path_tree('/', '/foo') == ('/foo', ['/', '/foo'])
True
>>> _make_path_tree('/', '/foo/') == ('/foo', ['/', '/foo'])
True
>>> _make_path_tree('/', 'foo') == ('/foo', ['/', '/foo'])
True
>>> _make_path_tree('/', '/foo/bar') == ('/foo/bar', ['/', '/foo', '/foo/bar'])
True
>>> _make_path_tree('/', 'foo/bar') == ('/foo/bar', ['/', '/foo', '/foo/bar'])
True
>>> _make_path_tree('/', '/foo/bar/baz') == ('/foo/bar/baz', ['/', '/foo', '/foo/bar', '/foo/bar/baz'])
True
>>> _make_path_tree('/foo', '') == ('/foo', ['/', '/foo'])
True
>>> _make_path_tree('/foo', '/bar') == ('/foo/bar', ['/', '/foo', '/foo/bar'])
True
>>> _make_path_tree('/foo', '/bar/') == ('/foo/bar', ['/', '/foo', '/foo/bar'])
True
>>> _make_path_tree('/foo', 'bar') == ('/foo/bar', ['/', '/foo', '/foo/bar'])
True
"""
if path.startswith(u'/'):
if path.startswith('/'):
path = path[1:] # Strip leading slash
if path.endswith(u'/'):
if path.endswith('/'):
path = path[:-1] # Strip trailing slash
if path == u'':
if path == '':
searchpath = basepath
else:
searchpath = pathjoin(basepath, path)
if searchpath == u'/':
searchpaths = [u'/']
if searchpath == '/':
searchpaths = ['/']
else:
parts = searchpath.split(u'/')
searchpaths = [u'/'.join(parts[:x + 1]) for x in range(len(parts))]
searchpaths[0] = u'/'
parts = searchpath.split('/')
searchpaths = ['/'.join(parts[:x + 1]) for x in range(len(parts))]
searchpaths[0] = '/'
return searchpath, searchpaths


Expand All @@ -109,9 +109,9 @@ def __init__(self, registry, node, user, permissions):
self.permissions = permissions

def __call__(self, endpoint, args):
if u'/' not in endpoint: # pragma: no cover
if '/' not in endpoint: # pragma: no cover
raise ViewNotFound(endpoint) # We don't know about endpoints that aren't in 'view/function' syntax
viewname, endpointname = endpoint.split(u'/', 1)
viewname, endpointname = endpoint.split('/', 1)
view = self.registry.viewlist[viewname](self.node, self.user, self.permissions)
g.view = view
return view.view_functions[endpointname](view, **args)
Expand All @@ -135,15 +135,15 @@ class NodePublisher(object):
def __init__(self, root, registry, basepath, urlpath=None):
self.root = root
self.registry = registry
if not basepath.startswith(u'/'):
if not basepath.startswith('/'):
raise ValueError("Parameter ``basepath`` must be an absolute path starting with '/'")
if basepath != u'/' and basepath.endswith(u'/'):
if basepath != '/' and basepath.endswith('/'):
basepath = basepath[:-1] # Strip trailing slash for non-root paths
self.basepath = basepath
if urlpath is None:
self.urlpath = basepath
else:
if not urlpath.startswith(u'/'):
if not urlpath.startswith('/'):
raise ValueError("Parameter ``urlpath`` must be an absolute path starting with '/'")
self.urlpath = urlpath

Expand Down Expand Up @@ -182,8 +182,8 @@ def traverse(self, path, redirect=True):
:class:`NodePublisher` may be initialized with ``registry=None`` if only used for
traversal.
"""
if not path.startswith(u'/'):
path = u'/' + path
if not path.startswith('/'):
path = '/' + path
if not path.startswith(self.urlpath):
return TRAVERSE_STATUS.NOROOT, None, path
path = path[len(self.urlpath):]
Expand Down Expand Up @@ -211,13 +211,13 @@ def traverse(self, path, redirect=True):
pathfragment = nodepath[len(lastnode.path):]
redirectpath = None

if pathfragment.startswith(u'/'):
if pathfragment.startswith('/'):
pathfragment = pathfragment[1:]

status = TRAVERSE_STATUS.PARTIAL

if redirect:
aliasname = pathfragment.split(u'/', 1)[0]
aliasname = pathfragment.split('/', 1)[0]
alias = NodeAlias.query.filter_by(parent=lastnode, name=aliasname).first()
if alias is None:
# No alias, but the remaining path may be handled by the node,
Expand All @@ -227,12 +227,12 @@ def traverse(self, path, redirect=True):
status = TRAVERSE_STATUS.GONE
else:
status = TRAVERSE_STATUS.REDIRECT
if u'/' in pathfragment:
redirectpath = pathjoin(lastnode.path, alias.node.name, pathfragment.split(u'/', 1)[1])
if '/' in pathfragment:
redirectpath = pathjoin(lastnode.path, alias.node.name, pathfragment.split('/', 1)[1])
else:
redirectpath = pathjoin(lastnode.path, alias.node.name)
redirectpath = redirectpath[len(self.basepath):]
if redirectpath.startswith(u'/'):
if redirectpath.startswith('/'):
redirectpath = pathjoin(self.urlpath, redirectpath[1:])
else:
redirectpath = pathjoin(self.urlpath, redirectpath)
Expand All @@ -242,7 +242,7 @@ def traverse(self, path, redirect=True):

# Add / prefix to pathfragment to help with URL matching
# within the node
pathfragment = u'/' + pathfragment
pathfragment = '/' + pathfragment

if status == TRAVERSE_STATUS.REDIRECT:
return status, lastnode, redirectpath
Expand Down Expand Up @@ -273,7 +273,7 @@ def publish(self, path, user=None, permissions=None):
urls = self.registry.urlmaps[node.etype].bind_to_environ(request)
if status == TRAVERSE_STATUS.MATCH:
# Find '/' path handler. If none, return 404
return urls.dispatch(NodeDispatcher(self.registry, node, user, permissions), path_info=u'/')
return urls.dispatch(NodeDispatcher(self.registry, node, user, permissions), path_info='/')
elif status == TRAVERSE_STATUS.PARTIAL:
return urls.dispatch(NodeDispatcher(self.registry, node, user, permissions), path_info=pathfragment)
else:
Expand Down
82 changes: 42 additions & 40 deletions nodular/view.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-

from functools import wraps
from six import with_metaclass
from werkzeug.routing import Map as UrlMap, Rule as UrlRule
from flask import g, abort

Expand All @@ -23,7 +24,47 @@ def __call__(self, *args, **kwargs): # pragma: no cover
return self.f(*args, **kwargs)


class NodeView(object):
class _NodeViewMeta(type):
"""Metaclass for NodeView."""
def __new__(cls, name, bases, attrs):
# Add a url_map to the class
url_map = UrlMap(strict_slashes=False)
# Add a collection of (unbound) view functions
view_functions = {}
for base in bases:
# Extend from url_map of base class
if hasattr(base, 'url_map') and isinstance(base.url_map, UrlMap):
for rule in base.url_map.iter_rules():
url_map.add(rule.empty())
# Extend from view_functions of base class
if hasattr(base, 'view_functions') and isinstance(base.view_functions, dict):
view_functions.update(base.view_functions)
for routeattr, route in attrs.items():
if isinstance(route, _NodeRoute):
# For wrapped routes, add a rule for each layer of wrapping
endpoints = []
while isinstance(route, _NodeRoute):
# Save the endpoint name
endpoints.append(route.endpoint)
# Construct the url rule
url_rule = UrlRule(route.rule, endpoint=route.endpoint, methods=route.methods, defaults=route.defaults)
url_rule.provide_automatic_options = True
url_map.add(url_rule)
route = route.f
# Make a list of endpoints
for e in endpoints:
view_functions[e] = route
# Restore the original function
attrs[routeattr] = route
# Finally, update the URL map and insert it into the class
url_map.update()
attrs['url_map'] = url_map
attrs['view_functions'] = view_functions

return type.__new__(cls, name, bases, attrs)


class NodeView(with_metaclass(_NodeViewMeta, object)):
"""
Base class for node view handlers, to be initialized once per view render.
Views are typically constructed like this::
Expand All @@ -49,45 +90,6 @@ def delete(self):
:param user: User that the view is being rendered for.
:type node: :class:`~nodular.node.Node`
"""
class __metaclass__(type):
"""Metaclass for NodeView."""
def __new__(cls, name, bases, attrs):
# Add a url_map to the class
url_map = UrlMap(strict_slashes=False)
# Add a collection of (unbound) view functions
view_functions = {}
for base in bases:
# Extend from url_map of base class
if hasattr(base, 'url_map') and isinstance(base.url_map, UrlMap):
for rule in base.url_map.iter_rules():
url_map.add(rule.empty())
# Extend from view_functions of base class
if hasattr(base, 'view_functions') and isinstance(base.view_functions, dict):
view_functions.update(base.view_functions)
for routeattr, route in attrs.items():
if isinstance(route, _NodeRoute):
# For wrapped routes, add a rule for each layer of wrapping
endpoints = []
while isinstance(route, _NodeRoute):
# Save the endpoint name
endpoints.append(route.endpoint)
# Construct the url rule
url_rule = UrlRule(route.rule, endpoint=route.endpoint, methods=route.methods, defaults=route.defaults)
url_rule.provide_automatic_options = True
url_map.add(url_rule)
route = route.f
# Make a list of endpoints
for e in endpoints:
view_functions[e] = route
# Restore the original function
attrs[routeattr] = route
# Finally, update the URL map and insert it into the class
url_map.update()
attrs['url_map'] = url_map
attrs['view_functions'] = view_functions

return type.__new__(cls, name, bases, attrs)

def __init__(self, node, user=None, permissions=None):
self.node = node
self.user = user
Expand Down
18 changes: 10 additions & 8 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
raise RuntimeError("Unable to find version string in nodular/_version.py.")

requires = [
'six',
'simplejson',
'Flask-SQLAlchemy',
'SQLAlchemy>=1.0',
'Flask',
'coaster>=0.5.0',
'coaster>=0.6.0',
]

setup(
Expand All @@ -27,13 +28,14 @@
description='Revisioned content objects',
long_description=README + '\n\n' + CHANGES,
classifiers=[
"Programming Language :: Python",
"Programming Language :: Python :: 2.7",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Intended Audience :: Developers",
"Development Status :: 3 - Alpha",
"Topic :: Software Development :: Libraries",
'Programming Language :: Python',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.6',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Intended Audience :: Developers',
'Development Status :: 3 - Alpha',
'Topic :: Software Development :: Libraries',
],
author='Kiran Jonnalagadda',
author_email='kiran@hasgeek.com',
Expand Down

0 comments on commit 9085a53

Please sign in to comment.