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

Commit

Permalink
Merge 6708e54 into 566f1b8
Browse files Browse the repository at this point in the history
  • Loading branch information
Yasser Idris committed Feb 24, 2015
2 parents 566f1b8 + 6708e54 commit a36137a
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Changelog

Development
-----------
- Add sqlalchemy extension.
- Change the way links are added to resource representation.

- Fix issue #6: HTTP 500 error when using POST verb on Item resources.

Expand Down
1 change: 1 addition & 0 deletions requirements-test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ coveralls

pyramid_mongokit >= 0.2
voluptuous
sqlalchemy >= 0.9
Empty file added royal/ext/__init__.py
Empty file.
163 changes: 163 additions & 0 deletions royal/ext/sqla.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import logging

from pyramid.location import lineage
import royal
from sqlalchemy.orm.collections import MappedCollection

log = logging.getLogger(__name__)


def includeme(config):
config.scan(__name__)


class Collection(royal.Collection):

sa_model = None
entity_cls = None

def __init__(self, name, parent, entities=None):
super(Collection, self).__init__(name, parent)
self.entities = entities

def __repr__(self):
return '<%s collection at %s named %r>' % (self.__class__.__name__,
id(self),
self.name)

def load_entities(self):
self.entities = self.entity_cls.all()
# TODO pagination

def index(self, params):
if self.entities is None:
self.load_entities()
return self

def create(self, params):
entity = self.entity_cls(**params)
entity.save()
try:
self.sa_model.flush()
except Exception:
log.exception('create resource=%r params=%r', self, params)
raise
item = self[entity.id]
item.entity = entity
self.sa_model.commit()
return item


class Item(royal.Item):

sa_model = None

# In derived Item classes, specify a model class for singular resources
# that don't belong to a collection. Otherwise, it will be determined from
# the parent resource.
entity_cls = None

def __init__(self, name, parent, entity=None):
super(Item, self).__init__(name, parent)
self.entity = entity
if self.entity_cls is None and self.parent is not None:
self.entity_cls = self.parent.entity_cls

def __repr__(self):
return '<%s item at %s named %r>' % (self.__class__.__name__,
id(self),
self.name)

def on_traversing(self, key):
self.load_entity()

def load_entity(self):
if self.entity is None:
if self.entity_cls is None:
raise royal.exceptions.NotFound(self)

# FIXME Naively assume that entity's PK is the list of resource
# __name__ in reversed lineage so PK of /slots/123/symbols/456 is
# (123, 456). Should also be adapted to support resources
# identified by name.
pk = [item.name for item in lineage(self)
if hasattr(item, 'name')
and item.name
and not isinstance(item, Collection)]
pk.reverse()
try:
self.entity = self.entity_cls.get(pk)
except KeyError:
raise royal.exceptions.NotFound(self)

return self.entity

def show(self, params):
self.load_entity()
return self

def delete(self):
self.load_entity().delete()
self.sa_model.commit()

def update(self, params):
params_copy = params.copy()
entity = self.load_entity()
# Ignore parameters that are part of primary key.
[params_copy.pop(pk.name, '') for pk in entity.__mapper__.primary_key]
for param in params_copy:
try:
getattr(entity, param)
setattr(entity, param, params_copy[param])
except AttributeError:
pass
try:
self.sa_model.commit()
except Exception:
log.exception('update resource=%r params=%r', self, params)
raise
return entity


@royal.renderer_adapter(Collection)
def adapt_collection(collection, request):
items = []
if collection.entities is None:
collection.load_entities()
if isinstance(collection.entities, MappedCollection):
for item_id, entity in collection.entities.items():
item = collection[item_id]
item.entity = entity
items.append(item)
else:
for entity in collection.entities:
item = collection[entity.id]
item.entity = entity
items.append(item)
return {
u'items': items,
u'links': collection.links,
}


def render_hyperlink(item):
try:
return {'id': int(item.name)}
except ValueError:
return {'id': item.name}


def render_model(item):
entity = item.load_entity()
columns = entity.__table__.columns
return {column.name: getattr(entity, column.name) for column in columns}


@royal.renderer_adapter(Item)
def adapt_item(item, request):
if request.is_nested(item):
result = render_hyperlink(item)
else:
result = render_model(item)
result['links'] = item.links
return result
2 changes: 1 addition & 1 deletion royal/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def callback(context, name, ob):
@renderer_adapter('datetime.date')
@renderer_adapter('datetime.datetime')
def adapt_datetime(o, request):
return o.isoformat()
return o.strftime('%Y-%m-%dT%H:%M:%SZ') # ISO8601 with Zulu marker


@renderer_adapter('decimal.Decimal')
Expand Down
9 changes: 4 additions & 5 deletions royal/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ def resource_url(self, resource, request=None, **query_params):
request = self.root.request
return request.resource_url(resource, **kw)


def url(self, request=None, **query_params):
return self.resource_url(self, request, **query_params)

Expand All @@ -81,10 +80,10 @@ def name(self):

@property
def links(self):
_links = {name: {'href': cls(name, self).url()}
for name, cls in self.children.iteritems()}
_links['href'] = self.url()
return _links
links = {name: self.root.request.resource_url(self, name)
for name in self.children}
links['self'] = self.url()
return links

def on_traversing(self, key):
pass
Expand Down
4 changes: 2 additions & 2 deletions royal/tests/functional/test_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ def test_root(self):
result = response.json
self.assertIn('users', result)
self.assertIn('photos', result)
self.assertEqual('http://localhost/users/', result['users']['href'])
self.assertEqual('http://localhost/photos/', result['photos']['href'])
self.assertEqual('http://localhost/users', result['users'])
self.assertEqual('http://localhost/photos', result['photos'])

def test_users_index(self):
self._add_users()
Expand Down

0 comments on commit a36137a

Please sign in to comment.