Skip to content

Instantly share code, notes, and snippets.

@mmalone
Created September 3, 2009 01:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mmalone/180064 to your computer and use it in GitHub Desktop.
Save mmalone/180064 to your computer and use it in GitHub Desktop.
hateoasis
from remoteobjects.fields import *
class Resource(Field):
def __init__(self, cls, params=None, **kwargs):
super(Resource, self).__init__(**kwargs)
self.cls = cls
if params is None:
self.params = lambda x: {}
else:
self.params = params
def __get__(self, instance, owner):
return self.cls.get(**self.params(instance))
def decode(self, value):
if value is None:
if callable(self.default):
return self.default()
return self.default
return self.cls.from_dict(value)
def encode(self, value):
return value.to_dict()
from django import http
class RequestMethodException(Exception): pass
class Handler(object):
methods = ('get', 'head')
@classmethod
def dispatch(cls, request, *args, **kwargs):
handler = cls()
method = request.method.lower()
if method not in (allowed.lower() for allowed in handler.methods):
return http.HttpResponseNotAllowed(handler.methods)
try:
view = getattr(handler, method)
except AttributeError:
raise RequestMethodExecption("Allowed method `%s` is not defined." % method)
if not callable(view):
raise RequestMethodExecption("Allowed method `%s` is not callable." % method)
resource = view(request, *args, **kwargs)
return handler._make_response(resource)
def _make_response(self, resource):
return resource
def get(self, request, *args, **kwargs):
return http.HttpResponse()
def head(self, request, *args, **kwargs):
resource = self.get(request, *args, **kwargs)
response = self._make_response(resource)
response.content = ''
return response
class ResourceHandler(Handler):
resource_class = None
def get(self, request, *args, **kwargs):
return self.resource_class.get(*args, **kwargs)
def _make_response(self, resource):
if isinstance(resource, http.HttpResponse):
return resource
return resource.representation('json')
def resource_handler(resource):
class TheResourceHandler(ResourceHandler):
resource_class = resource
return TheResourceHandler
import datetime
from django.db import models
class Frob(models.Model):
name = models.CharField(max_length=255)
created = models.DateTimeField(default=datetime.datetime.now)
class Fritz(models.Model):
age = models.IntegerField(default=5)
smell = models.CharField(max_length=32)
frob = models.ForeignKey(Frob, related_name='fritzes')
import simplejson as json
import remoteobjects.dataobject
from hateoasis import fields
from django import http
from django.db import models
class UnsupportedMediaType(Exception): pass
class ResourceDoesNotExist(Exception): pass
class RepresentationRegistry(object):
# Should also be able to register a Representation for a particular
# Resource.
def __init__(self):
self.all = {}
def register(self, content_type, cls):
self.all.setdefault(content_type, []).append(cls)
def get(self, content_type):
try:
return self.all[content_type][0]
except KeyError, IndexError:
raise UnsupportedMediaTypeError(content_type)
representations = RepresentationRegistry()
class Representation(http.HttpResponse):
def __init__(self, resource, *args, **kwargs):
super(Representation, self).__init__(*args, **kwargs)
self.write(self.render(resource))
def render(self, resource):
raise NotImplementedError
class JSONRepresentation(Representation):
def render(self, resource):
return json.dumps(resource.to_dict())
representations.register('json', JSONRepresentation)
class Resource(remoteobjects.dataobject.DataObject):
def representation(self, content_type):
try:
Representation = representations.get(content_type)
except UnsupportedMediaType:
raise
return Representation(self)
@classmethod
def get(cls, *args, **kwargs):
return cls(*args, **kwargs)
def get_field(field):
if isinstance(field, models.ForeignKey):
params = lambda x: {'%s__exact' % field.rel.field_name: getattr(x.Meta.model_inst, field.attname)}
return fields.Resource(ModelResourceFor(field.rel.to), params=params)
cls = {
models.DateTimeField: fields.Datetime,
}.get(type(field), fields.Field)
return cls(default=field.get_default())
def fields_for_model(model, fields=None, exclude=None, field_callback=get_field):
"""
Returns a dictionary containing the resource fields for a given model.
``fields`` is an optional list containing field names. If provided, only the
named fields will be included in the returned fields.
``exclude`` is an optional list of field names. If provided, the named
fields will be excluded from the returned fields, even if they are listed in
the ``fields`` argument.
"""
model_fields = []
opts = model._meta
for f in opts.fields + opts.many_to_many:
if fields and not f.name in fields:
print 'Not in fields', f.name
continue
if exclude and f.name in exclude:
continue
field = field_callback(f)
if field:
model_fields.append((f.name, field))
return model_fields
class ModelResourceFor(Resource.__metaclass__):
_baseclass = None
def __new__(cls, name, bases=None, attrs=None):
direct = attrs is None
if direct:
model = name
name = cls.__name__ + model.__name__
bases = (cls._baseclass,)
model = model
meta = type('Meta', (), {'model': model})
attrs = {'Meta': meta}
meta = attrs['Meta']
model = getattr(meta, 'model', None)
fields = getattr(meta, 'fields', None)
exclude = getattr(meta, 'exclude', None)
field_callback = attrs.pop('field_callback', get_field)
if meta.model is not None:
model_fields = fields_for_model(model, fields, exclude, field_callback)
for name, field in model_fields:
attrs.setdefault(name, field)
meta.model_fields = model_fields
newcls = super(ModelResourceFor, cls).__new__(cls, name, bases, attrs)
if not direct and cls._baseclass is None:
cls._baseclass = newcls
return newcls
class ModelResource(Resource):
__metaclass__ = ModelResourceFor
@classmethod
def get(cls, *args, **kwargs):
try:
model = cls.Meta.model.objects.get(*args, **kwargs)
except cls.Meta.model.DoesNotExist:
raise ResourceDoesNotExist("No resource by that name. Please try again.")
obj = cls()
for name, field in cls.Meta.model_fields:
value = getattr(model, name, None)
if value is not None:
setattr(obj, name, value)
obj.Meta.model_inst = model
return obj
class Meta:
model = None
fields = None
exclude = None
import datetime
from hateoasis import resource
from hateoasis.resource import fields
from api.models import Frob, Fritz
class FritzResource(resource.ModelResource):
type = fields.Constant('my.api.name::FritzResource')
class Meta:
model = Fritz
class Thing(resource.Resource):
name = fields.Field()
created = fields.Datetime(default=datetime.datetime.now)
frob = fields.Object(Frob)
def __init__(self, *args, **kwargs):
self.name = kwargs.pop('name', None)
self.created = kwargs.pop('created', datetime.datetime.now())
self.frob = kwargs.pop('frob', None)
super(Thing, self).__init__(*args, **kwargs)
from django.conf.urls.defaults import *
from api.models import Fritz
from api.resources import Thing
from hateoasis.urls import resturl
urlpatterns = patterns('',
resturl(r'^thing/', Thing),
resturl(r'^fritz/(?P<id>\d+)/', Fritz),
)
from hateoasis.resource import Resource, ModelResourceFor
from hateoasis.handler import resource_handler
from django.conf.urls.defaults import url
def resturl(regex, model_or_resource, kwargs=None, name=None, prefix=''):
if issubclass(model_or_resource, Resource):
resource = model_or_resource
else:
resource = ModelResourceFor(model_or_resource)
handler = resource_handler(resource)
return url(regex, handler.dispatch, kwargs, name, prefix)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment