Source code for yarhp.dynamo
import logging
from pyramid.httpexceptions import (
HTTPOk, HTTPCreated,
HTTPBadRequest, HTTPNotFound, HTTPConflict,
)
from boto.dynamodb.exceptions import DynamoDBKeyNotFoundError
from dynamodb_mapper.model import OverwriteError
from yarhp.view import BaseView
log = logging.getLogger(__name__)
[docs]class DynamoDBView(BaseView):
"""Simple REST view used to serve dymamodb-mapper objects at the top level.
For hash_key-indexed classes, subclass this and fill in the __model_class__
attribute with the class you want to serve. That's it!
The default __model_class__ value is
"your_project.model.resource_name.ResourceName"
Example usage::
class UserView(DynamoDBView):
#__model_class__ = 'your_project.model.user.User'
pass
"""
_default_actions = frozenset('create update index show delete'.split())
def validation_schema(self):
self.__validation_schema__ =\
{n: dict(type=v, required=False) for n,v in self.__model_class__.__schema__.items()}
return self.__validation_schema__
def _get_or_404(self, id):
"""Load the item with hash_key == hash_key_type(id)
if it exists in the DB. Otherwise, raise HTTP 404.
"""
# id is always a string -- convert it the hash_key's real type
schema = self.__model_class__.__schema__
hash_key_name = self.__model_class__.__hash_key__
hash_key = schema[hash_key_name](id)
try:
return self.__model_class__.get(hash_key)
except DynamoDBKeyNotFoundError:
log.info(
"Class=%s: id=%s not found", self.__model_class__, id)
raise HTTPNotFound()
[docs] def default_create(self, **kw):
"""POST /items: create a new item."""
try:
data = {
key: self.request.params[key]
for key in self.__model_class__.__schema__
}
except KeyError as e:
log.info(
"Create class=%s: missing key=%s (params=%s)",
self.__model_class__, e, self.request.params)
raise HTTPBadRequest()
item = self.__model_class__.from_dict(data)
hash_key = getattr(item, item.__hash_key__)
try:
item.save(allow_overwrite=False)
except OverwriteError:
log.info(
"Create class=%s: hash_key=%s already exists (params=%s).",
self.__model_class__, hash_key, self.request.params)
raise HTTPConflict()
# Provide the newly-created resource's URI.
new_uri = self.request.route_url(
self.request.matched_route.name,
hash_key)
return HTTPCreated(headers={'Location': new_uri})
[docs] def default_update(self, **kw):
"""PUT /items/hash_key: update an existing item."""
# Check that the item exists, but since we'll overwrite it
# entirely, we don't actually need it.
id = kw['id']
self._get_or_404(id)
hash_key_name = self.__model_class__.__hash_key__
if self.request.params.get(hash_key_name) not in [id, None]:
log.info(
"Update class=%s: can't change id=%s to id=%s (params=%s)",
id, self.request.params[hash_key_name],
self.request.params)
raise HTTPBadRequest()
try:
data = {
key: self.request.params[key]
for key in self.__model_class__.__schema__
if key != hash_key_name
}
except KeyError as e:
log.info(
"Update class=%s: missing key=%s (params=%s)",
self.__model_class__, e, self.request.params)
raise HTTPBadRequest()
data[hash_key_name] = id
# Easier to recreate a new item entirely.
self.__model_class__.from_dict(data).save()
return HTTPOk()
[docs] def default_delete(self, **kw):
"""DELETE /items/hash_key: delete a single item."""
item = self._get_or_404(kw['id'])
item.delete()
return HTTPOk()
[docs] def default_index(self, **kw):
"""GET /items: list all items in the collection.
Warning: Uses the scan operation. Please avoid calling this
on large collections.
"""
return [i.to_json_dict() for i in self.__model_class__.scan()]
[docs] def default_show(self, **kw):
"""GET /items/hash_key: get a single item."""
item = self._get_or_404(kw['id'])
return item.to_json_dict()