Skip to content

Commit

Permalink
Merge branch 'links'
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolaiarocci committed Jan 19, 2013
2 parents 6c2795d + d2dbfc3 commit 4ed8818
Show file tree
Hide file tree
Showing 14 changed files with 158 additions and 112 deletions.
21 changes: 11 additions & 10 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ Version 0.0.3

Not yet released.

- Streamlined JSON responses.
The superflous ``response`` root key has been removed from JSON payloads. In
order to properly validate, XML payloads will still carry a ``response`` root
node.

GET requests to resource endpoints. Items are always wrapped in
an ``items`` list. Previously the list name would match the name of the
resource being accessed.
- JSON links are always wrapped in a ``_links`` dictionary. Key values match
the relation between the item being represented and the linked resource.

- Streamlined JSON responses.

Superflous ``response`` root key has been removed from JSON payloads.

GET requests to resource endpoints: items are now wrapped with an ``_items``
list.

GET requests to item endpoints. The item key is always set to ``item``.
Previously it would match the resource name minus the final 's'.
GET requests to item endpoints: item is now at root level, with no wrappers
around it.

- Support for API versioning through the new API_VERSION configuration setting.
- Boolean values in request forms are now correctly parsed.
Expand Down
7 changes: 5 additions & 2 deletions eve/flaskapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,7 @@ def __init__(self, settings='settings.py', validator=Validator,
super(Eve, self).__init__(__package__)
# enable regex routing
self.url_map.converters['regex'] = RegexConverter

self.validator = validator

self.settings = settings

self.load_config()
Expand Down Expand Up @@ -224,6 +222,9 @@ def validate_schema(self, schema):
def set_defaults(self):
""" When not provided, fills individual resource settings with default
or global configuration settings.
.. versionchanged:: 0.0.3
`item_title` default value.
"""

for resource, settings in self.config['DOMAIN'].items():
Expand All @@ -235,6 +236,8 @@ def set_defaults(self):
settings.setdefault('item_lookup_field',
self.config['ITEM_LOOKUP_FIELD'])
settings.setdefault('item_url', self.config['ITEM_URL'])
settings.setdefault('item_title',
resource.rstrip('s').capitalize())
settings.setdefault('item_cache_control',
self.config['ITEM_CACHE_CONTROL'])
settings.setdefault('item_lookup', self.config['ITEM_LOOKUP'])
Expand Down
43 changes: 29 additions & 14 deletions eve/methods/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ def get(resource):
"""Retrieves the resource documents that match the current request.
:param resource: the name of the resource.
.. versionchanged:: 0.0.3
Superflous ``response`` container removed. Collection items wrapped
with ``_items``. Links wrapped with ``_links``. Links are now properly
JSON formatted.
"""

documents = list()
response = dict()
last_updated = datetime.min
Expand Down Expand Up @@ -58,7 +64,8 @@ def get(resource):

# document metadata
document['etag'] = document_etag(document)
document['link'] = document_link(resource, document[config.ID_FIELD])
document['_links'] = {'self': document_link(resource,
document[config.ID_FIELD])}

documents.append(document)

Expand All @@ -71,8 +78,8 @@ def get(resource):
else:
status = 200
last_modified = last_updated if last_updated > datetime.min else None
response['items'] = documents
response['links'] = _pagination_links(resource, req, cursor.count())
response['_items'] = documents
response['_links'] = _pagination_links(resource, req, cursor.count())

etag = None
return response, last_modified, etag, status
Expand All @@ -83,6 +90,10 @@ def getitem(resource, **lookup):
:param resource: the name of the resource to which the document belongs.
:param **lookup: the lookup query.
.. versionchanged:: 0.0.3
Superflous ``response`` container removed. Links wrapped with
``_links``. Links are now properly JSON formatted.
"""
response = dict()

Expand All @@ -107,9 +118,12 @@ def getitem(resource, **lookup):
# resolution (1 second).
return response, last_modified, etag, 304

document['link'] = document_link(resource, document[config.ID_FIELD])
response['item'] = document
response['links'] = standard_links(resource)
response['_links'] = {
'self': document_link(resource, document[config.ID_FIELD]),
'collection': collection_link(resource),
'parent': home_link()
}
response.update(document)
return response, last_modified, etag, 200

abort(404)
Expand All @@ -122,23 +136,24 @@ def _pagination_links(resource, req, documents_count):
:param resource: the resource name.
:param req: and instace of :class:`eve.utils.ParsedRequest`.
:param document_count: the number of documents returned by the query.
.. versionchanged:: 0.0.3
JSON links
"""
_pagination_links = standard_links(resource)
_links = {'parent': home_link(), 'self': collection_link(resource)}

if documents_count:
if req.page * req.max_results < documents_count:
q = querydef(req.max_results, req.where, req.sort, req.page + 1)
_pagination_links.append("<link rel='next' title='next page'"
" href='%s%s' />" %
(resource_uri(resource), q))
_links['next'] = {'title': 'next page', 'href': '%s%s' %
(resource_uri(resource), q)}

if req.page > 1:
q = querydef(req.max_results, req.where, req.sort, req.page - 1)
_pagination_links.append("<link rel='prev' title='previous page'"
" href='%s%s' />" %
(resource_uri(resource), q))
_links['prev'] = {'title': 'previous page', 'href': '%s%s' %
(resource_uri(resource), q)}

return _pagination_links
return _links


def standard_links(resource):
Expand Down
6 changes: 5 additions & 1 deletion eve/methods/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ def patch(resource, **lookup):
:param resource: the name of the resource to which the document belongs.
:param **lookup: document lookup query.
.. versionchanged:: 0.0.3
JSON links. Superflous ``response`` container removed.
"""
if len(request.form) > 1 or len(request.form) == 0:
# only one update-per-document supported
Expand Down Expand Up @@ -80,7 +83,8 @@ def patch(resource, **lookup):

# metadata
response_item['etag'] = etag
response_item['link'] = document_link(resource, object_id)
response_item['_links'] = {'self': document_link(resource,
object_id)}
else:
issues.extend(validator.errors)
except ValidationError, e:
Expand Down
9 changes: 7 additions & 2 deletions eve/methods/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ def post(resource):
is returned.
:param resource: name of the resource involved.
.. versionchanged:: 0.0.3
JSON links. Superflous ``response`` container removed.
"""

if len(request.form) == 0:
Expand Down Expand Up @@ -54,8 +57,10 @@ def post(resource):
response_item[config.ID_FIELD] = document[config.ID_FIELD]
response_item[config.LAST_UPDATED] = \
document[config.LAST_UPDATED]
response_item['link'] = \
document_link(resource, response_item[config.ID_FIELD])
response_item['_links'] = \
{'self': document_link(resource,
response_item[config.ID_FIELD])}

else:
issues.extend(validator.errors)
except ValidationError as e:
Expand Down
84 changes: 48 additions & 36 deletions eve/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def setUp(self):
super(TestMethodsBase, self).setUp()
self.setupDB()
response, status = self.get('contacts', '?max_results=2')
contact = response['items'][0]
contact = response['_items'][0]
self.item_id = contact[self.app.config['ID_FIELD']]
self.item_name = contact['ref']
self.item_tid = contact['tid']
Expand All @@ -75,10 +75,10 @@ def setUp(self):
self.item_name_url = ('/%s/%s/' %
(self.domain[self.known_resource]['url'],
self.item_name))
self.alt_ref = response['items'][1]['ref']
self.alt_ref = response['_items'][1]['ref']

response, status = self.get('payments', '?max_results=1')
self.readonly_id = response['items'][0]['_id']
self.readonly_id = response['_items'][0]['_id']
self.readonly_id_url = ('%s%s/' % (self.readonly_resource_url,
self.readonly_id))

Expand Down Expand Up @@ -155,49 +155,61 @@ def assertItem(self, item):
self.fail('Cannot convert field "%s" to datetime: %s' %
(self.app.config['LAST_UPDATED'], e))

link = item.get('link')
self.assertTrue(link is not None)
link = item.get('_links')
self.assertItemLink(link, _id)

def assertHomeLink(self, links):
found = False
for link in links:
if "title='home'" in link and \
"href='%s'" % self.app.config['SERVER_NAME'] in link:
found = True
break
self.assertTrue(found)
self.assertTrue('parent' in links)
link = links['parent']
self.assertTrue('title' in link)
self.assertTrue('href' in link)
self.assertEqual('home', link['title'])
self.assertEqual("%s" % self.app.config['SERVER_NAME'], link['href'])

def assertResourceLink(self, links, resource):
self.assertTrue('self' in links)
link = links['self']
self.assertTrue('title' in link)
self.assertTrue('href' in link)
url = self.domain[resource]['url']
found = False
for link in links:
if "title='%s'" % url in link and \
"href='%s/%s/" % (self.app.config['SERVER_NAME'], url) in link:
found = True
break
self.assertTrue(found)
self.assertEqual(url, link['title'])
self.assertEqual("%s/%s/" % (self.app.config['SERVER_NAME'], url),
link['href'])

def assertCollectionLink(self, links, resource):
self.assertTrue('collection' in links)
link = links['collection']
self.assertTrue('title' in link)
self.assertTrue('href' in link)
url = self.domain[resource]['url']
self.assertEqual(url, link['title'])
self.assertEqual("%s/%s/" % (self.app.config['SERVER_NAME'], url),
link['href'])

def assertNextLink(self, links, page):
found = False
for link in links:
if "title='next page'" in link and "rel='next'" in link and \
'page=%d' % page in link:
found = True
self.assertTrue(found)
self.assertTrue('next' in links)
link = links['next']
self.assertTrue('title' in link)
self.assertTrue('href' in link)
self.assertEqual('next page', link['title'])
self.assertTrue("page=%d" % page in link['href'])

def assertPrevLink(self, links, page):
found = False
for link in links:
if "title='previous page'" in link and "rel='prev'" in link:
if page > 1:
found = 'page=%d' % page in link
else:
found = True
self.assertTrue(found)

def assertItemLink(self, link, item_id):
self.assertTrue("rel='self'" in link and '/%s/' % item_id in link)
self.assertTrue('prev' in links)
link = links['prev']
self.assertTrue('title' in link)
self.assertTrue('href' in link)
self.assertEqual('previous page', link['title'])
if page > 1:
self.assertTrue("page=%d" % page in link['href'])

def assertItemLink(self, links, item_id):
self.assertTrue('self' in links)
link = links['self']
#TODO we are too deep here to get a hold of the due title. Should fix.
self.assertTrue('title' in link)
self.assertTrue('href' in link)
self.assertTrue('/%s/' % item_id in link['href'])

def assert400(self, status):
self.assertEqual(status, 400)
Expand Down
9 changes: 6 additions & 3 deletions eve/tests/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,13 @@ def assertValidateConfig(self, expected):

def test_set_defaults(self):
self.domain.clear()
self.domain['empty_resource'] = {}
resource = 'plurals'
self.domain[resource] = {}

self.app.set_defaults()

settings = self.domain['empty_resource']
self.assertEqual(settings['url'], 'empty_resource')
settings = self.domain[resource]
self.assertEqual(settings['url'], resource)
self.assertEqual(settings['methods'],
self.app.config['RESOURCE_METHODS'])
self.assertEqual(settings['cache_control'],
Expand All @@ -134,6 +135,8 @@ def test_set_defaults(self):
self.app.config['ITEM_LOOKUP_FIELD'])
self.assertEqual(settings['item_url'],
self.app.config['ITEM_URL'])
self.assertEqual(settings['item_title'],
resource.rstrip('s').capitalize())
self.assertEqual(settings['item_cache_control'],
self.app.config['ITEM_CACHE_CONTROL'])
self.assertNotEqual(settings['schema'], None)
Expand Down
2 changes: 1 addition & 1 deletion eve/tests/io/mongo.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def test_bad_Expr(self):
self.assertRaises(ParseError, parse, 'a | 2')


class TestMongoValidator(TestMethodsBase):
class TestMongoValidator(TestCase):
def test_unique_fail(self):
""" relying on POST and PATCH tests since we don't have an active
app_context running here """
Expand Down
2 changes: 1 addition & 1 deletion eve/tests/methods/delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def test_delete_from_resource_endpoint(self):
r, status = self.parse_response(self.test_client.get(
self.known_resource_url))
self.assert200(status)
self.assertEqual(len(r['items']), 0)
self.assertEqual(len(r['_items']), 0)
self.bulk_insert()

def test_delete_empty_resource(self):
Expand Down
Loading

0 comments on commit 4ed8818

Please sign in to comment.