Skip to content

Commit

Permalink
Make RestResource.from_url work when url_path has a trailing slash.
Browse files Browse the repository at this point in the history
Clean the .egg-info directory from src in Makefile.
Make non_django an actual package in Python 2 (add __init__.py file).
  • Loading branch information
gnuworldman committed Aug 30, 2015
1 parent 0a4fdcb commit 1b96471
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 11 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ clean:
$(MAKE) -C docs clean
$(RM) MANIFEST
$(RM) -r dist
$(RM) -r src/*.egg-info
$(RM) -r htmlcov
coverage erase
find . -type d -name '__pycache__' | xargs $(RM) -r
Expand Down
26 changes: 15 additions & 11 deletions src/pyneric/rest_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,24 @@

_ENCODING = 'UTF-8'

SEPARATOR = '/'
"""URL path separator"""


def _url_join(base, url):
base = ensure_text(base)
url = ensure_text(url)
if base.endswith('/'):
if not url.endswith('/'):
url += '/'
if base.endswith(SEPARATOR):
if not url.endswith(SEPARATOR):
url += SEPARATOR
else:
base += '/'
base += SEPARATOR
return urljoin(base, url)


def _url_split(url):
result = list(urlsplit(url))
result[2] = result[2].rstrip('/')
result[2] = result[2].rstrip(SEPARATOR)
result[3:] = '', ''
return result

Expand Down Expand Up @@ -65,7 +68,7 @@ def validate_url_path(value):
raise ValueError(
"invalid url_path attribute (empty): {!r}"
.format(value))
elif value.startswith('/'):
elif value.startswith(SEPARATOR):
raise ValueError(
"invalid url_path attribute (leading slash): {!r}"
.format(value))
Expand Down Expand Up @@ -217,8 +220,9 @@ def _from_url(cls, url, **kwargs):
def _get_container_from_url(cls, url):
original_url, url = url, ensure_text(url, _ENCODING)
url_split = _url_split(url)
segments = url_split[2].split('/')
resource_segments = ensure_text(cls.url_path).split('/')
segments = url_split[2].split(SEPARATOR)
resource_segments = (ensure_text(cls.url_path).rstrip(SEPARATOR)
.split(SEPARATOR))
size = len(resource_segments)
if segments[-size:] != resource_segments:
multiple = size != 1
Expand All @@ -227,7 +231,7 @@ def _get_container_from_url(cls, url):
.format("{} ".format(size) if multiple else "",
"s" if multiple else "", original_url,
"are" if multiple else "is", cls.__name__))
url_split[2] = '/'.join(segments[:-size])
url_split[2] = SEPARATOR.join(segments[:-size])
return urlunsplit(url_split)

def __getattr__(self, item):
Expand Down Expand Up @@ -335,14 +339,14 @@ def _from_url(cls, url, **kwargs):
except ValueError:
result = None
url_split = _url_split(ensure_text(url, _ENCODING))
segments = url_split[2].split('/')
segments = url_split[2].split(SEPARATOR)
try:
id = cls.validate_id(segments.pop(-1))
except ValueError:
if result:
return result
raise
url_split[2] = '/'.join(segments)
url_split[2] = SEPARATOR.join(segments)
collection_url = urlunsplit(url_split)
member_result = tryf(super_method, collection_url, id=id)
if result and member_result:
Expand Down
Empty file added tests/non_django/__init__.py
Empty file.
99 changes: 99 additions & 0 deletions tests/non_django/test_rest_requests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# -*- coding: utf-8 -*-
"""Tests for pyneric.rest_requests"""

from unittest import TestCase

from future.utils import PY2
from pyneric import rest_requests


Expand Down Expand Up @@ -37,6 +41,14 @@ def test_init_invalid_container_class(self):
attrs = dict(container_class=object())
self.assertRaises(TypeError, type, 'Resource', bases, attrs)

def test_init_url_path_unicode(self):
PATH = u'resöurce'

class Resource(rest_requests.RestResource):
url_path = PATH

self.assertEqual(PATH, Resource.url_path)

def test_init_container_url(self):
class Resource(rest_requests.RestResource):
url_path = 'resource'
Expand Down Expand Up @@ -150,6 +162,15 @@ def test_init_invalid_reference_attribute(self):
self.assertRaises(TypeError, type, 'Resource', bases, attrs)

def test_from_url(self):
class Resource(rest_requests.RestResource):
url_path = 'resource'

url = '{}/{}'.format(ROOT, Resource.url_path)
obj = Resource.from_url(url)
self.assertIsInstance(obj.container, basestring if PY2 else str)
self.assertEqual(obj.container, ROOT)

def test_from_url_container(self):
class Container(rest_requests.RestResource):
url_path = 'container'

Expand All @@ -162,6 +183,58 @@ class Resource(rest_requests.RestResource):
self.assertIsInstance(obj.container_, Container)
self.assertEqual(obj.container_.container, ROOT)

def test_from_url_trailing_slash_container(self):
class Container(rest_requests.RestResource):
url_path = 'container/'

class Resource(rest_requests.RestResource):
url_path = 'resource'
container_class = Container

url = '{}/{}{}'.format(ROOT, Container.url_path, Resource.url_path)
obj = Resource.from_url(url)
self.assertIsInstance(obj.container_, Container)
self.assertEqual(obj.container_.container, ROOT)

def test_from_url_trailing_slash_container_not_url(self):
class Container(rest_requests.RestResource):
url_path = 'container/'

class Resource(rest_requests.RestResource):
url_path = 'resource'
container_class = Container

url = '{}/{}{}'.format(ROOT, Container.url_path, Resource.url_path)
obj = Resource.from_url(url.rstrip('/'))
self.assertIsInstance(obj.container_, Container)
self.assertEqual(obj.container_.container, ROOT)

def test_from_url_trailing_slash_resource(self):
class Container(rest_requests.RestResource):
url_path = 'container'

class Resource(rest_requests.RestResource):
url_path = 'resource/'
container_class = Container

url = '{}/{}/{}'.format(ROOT, Container.url_path, Resource.url_path)
obj = Resource.from_url(url)
self.assertIsInstance(obj.container_, Container)
self.assertEqual(obj.container_.container, ROOT)

def test_from_url_trailing_slash_both(self):
class Container(rest_requests.RestResource):
url_path = 'container/'

class Resource(rest_requests.RestResource):
url_path = 'resource/'
container_class = Container

url = '{}/{}{}'.format(ROOT, Container.url_path, Resource.url_path)
obj = Resource.from_url(url)
self.assertIsInstance(obj.container_, Container)
self.assertEqual(obj.container_.container, ROOT)

def test_from_url_invalid(self):
class Container(rest_requests.RestResource):
url_path = 'container'
Expand Down Expand Up @@ -213,6 +286,14 @@ class Collection(rest_requests.RestCollection):
url = '{}/{}/{}'.format(ROOT, Collection.url_path, id_)
self.assertEqual(url, Collection(ROOT, id_).url)

def test_init_member_unicode(self):
class Collection(rest_requests.RestCollection):
url_path = 'things'

id_ = u'impörtant thing'
url = u'{}/{}/{}'.format(ROOT, Collection.url_path, id_)
self.assertEqual(url, Collection(ROOT, id_).url)

def test_init_invalid_id_type(self):
bases = (rest_requests.RestCollection,)
attrs = dict(id_type='not a class')
Expand Down Expand Up @@ -250,6 +331,24 @@ class Resource(rest_requests.RestCollection):
self.assertEqual(ROOT, obj.container_.container)
self.assertEqual(resource_id, obj.id)

def test_from_url_member_unicode(self):
class Container(rest_requests.RestCollection):
url_path = 'things'

class Resource(rest_requests.RestCollection):
url_path = 'others'
container_class = Container

container_id = u'a thĩng'
resource_id = u'anöther'
url = u'{}/{}/{}/{}/{}'.format(ROOT, Container.url_path, container_id,
Resource.url_path, resource_id)
obj = Resource.from_url(url)
self.assertIsInstance(obj.container_, Container)
self.assertEqual(container_id, obj.container_.id)
self.assertEqual(ROOT, obj.container_.container)
self.assertEqual(resource_id, obj.id)

def test_id_type_int(self):
class Collection(rest_requests.RestCollection):
url_path = 'things'
Expand Down

0 comments on commit 1b96471

Please sign in to comment.