Created
September 3, 2009 01:05
-
-
Save mmalone/180064 to your computer and use it in GitHub Desktop.
hateoasis
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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