Resource Properties
Provide a helper method for defining known properties that are a part of a Resource. This will provide a developer friendly way to access resource properties without needing to know the format of message. It provides a basic example of validation via the type= parameter. It is meant as a reference and may be removed later in favour of a proper validation framework. Access to the underlying message is still available by the item accessors, ie setting obj['prop'] will set 'prop' within the resource and ignore any validation or attribute safety net. Change-Id: Iccfa4fad3e2f444aac0392a776f5948cefb13791
This commit is contained in:
parent
54f91a596d
commit
8f5a677921
@ -23,6 +23,69 @@ class MethodNotSupported(Exception):
|
||||
"""The resource does not support this operation type."""
|
||||
|
||||
|
||||
class prop(object):
|
||||
"""A helper for defining properties on a Resource.
|
||||
|
||||
A Resource.prop defines some known attributes within a resource's values.
|
||||
For example we know a User resource will have a name:
|
||||
|
||||
>>> class User(Resource):
|
||||
... name = prop('name')
|
||||
...
|
||||
>>> u = User()
|
||||
>>> u.name = 'John Doe'
|
||||
>>> print u['name']
|
||||
John Doe
|
||||
|
||||
User objects can now be accessed via the User().name attribute. The 'name'
|
||||
value we pass as an attribute is the name of the attribute in the message.
|
||||
This means that you don't need to use the same name for your attribute as
|
||||
will be set within the object. For example:
|
||||
|
||||
>>> class User(Resource):
|
||||
... name = prop('userName')
|
||||
...
|
||||
>>> u = User()
|
||||
>>> u.name = 'John Doe'
|
||||
>>> print u['userName']
|
||||
John Doe
|
||||
|
||||
There is limited validation ability in props.
|
||||
|
||||
You can validate the type of values that are set:
|
||||
|
||||
>>> class User(Resource):
|
||||
... name = prop('userName')
|
||||
... age = prop('age', type=int)
|
||||
...
|
||||
>>> u = User()
|
||||
>>> u.age = 'thirty'
|
||||
TypeError: Invalid type for attr age
|
||||
"""
|
||||
|
||||
def __init__(self, name, type=None):
|
||||
self.name = name
|
||||
self.type = type
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
try:
|
||||
return instance._attrs[self.name]
|
||||
except KeyError:
|
||||
raise AttributeError('Unset property: %s', self.name)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if self.type and not isinstance(value, self.type):
|
||||
raise TypeError('Invalid type for attr %s' % self.name)
|
||||
|
||||
instance._attrs[self.name] = value
|
||||
|
||||
def __delete__(self, instance):
|
||||
try:
|
||||
del instance._attrs[self.name]
|
||||
except KeyError:
|
||||
raise AttributeError('Unset property: %s', self.name)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Resource(collections.MutableMapping):
|
||||
"""A base class that represents a remote resource.
|
||||
|
@ -43,6 +43,10 @@ class FakeResource(resource.Resource):
|
||||
allow_create = allow_retrieve = allow_update = True
|
||||
allow_delete = allow_list = True
|
||||
|
||||
name = resource.prop('name')
|
||||
first = resource.prop('attr1')
|
||||
second = resource.prop('attr2')
|
||||
|
||||
|
||||
class ResourceTests(base.TestTransportBase):
|
||||
|
||||
@ -59,7 +63,9 @@ class ResourceTests(base.TestTransportBase):
|
||||
self.stub_url(httpretty.POST, path=fake_path, json=fake_body)
|
||||
|
||||
obj = FakeResource.new(name=fake_name,
|
||||
attr1=fake_attr1, attr2=fake_attr2)
|
||||
attr1=fake_attr1,
|
||||
attr2=fake_attr2)
|
||||
|
||||
obj.create(self.session)
|
||||
self.assertFalse(obj.is_dirty)
|
||||
|
||||
@ -75,19 +81,28 @@ class ResourceTests(base.TestTransportBase):
|
||||
self.assertEqual(fake_attr1, obj['attr1'])
|
||||
self.assertEqual(fake_attr2, obj['attr2'])
|
||||
|
||||
self.assertEqual(fake_name, obj.name)
|
||||
self.assertEqual(fake_attr1, obj.first)
|
||||
self.assertEqual(fake_attr2, obj.second)
|
||||
|
||||
@httpretty.activate
|
||||
def test_get(self):
|
||||
self.stub_url(httpretty.GET, path=[fake_path, fake_id], json=fake_body)
|
||||
obj = FakeResource.get_by_id(self.session, fake_id)
|
||||
|
||||
self.assertEqual(fake_name, obj['name'])
|
||||
self.assertEqual(fake_id, obj.id)
|
||||
self.assertEqual(fake_name, obj['name'])
|
||||
self.assertEqual(fake_attr1, obj['attr1'])
|
||||
self.assertEqual(fake_attr2, obj['attr2'])
|
||||
|
||||
self.assertEqual(fake_name, obj.name)
|
||||
self.assertEqual(fake_attr1, obj.first)
|
||||
self.assertEqual(fake_attr2, obj.second)
|
||||
|
||||
@httpretty.activate
|
||||
def test_update(self):
|
||||
new_attr1 = 'attr5'
|
||||
new_attr2 = 'attr6'
|
||||
fake_body1 = fake_body.copy()
|
||||
fake_body1[fake_resource]['attr1'] = new_attr1
|
||||
|
||||
@ -98,12 +113,13 @@ class ResourceTests(base.TestTransportBase):
|
||||
|
||||
obj = FakeResource.new(name=fake_name,
|
||||
attr1=new_attr1,
|
||||
attr2=fake_attr2)
|
||||
attr2=new_attr2)
|
||||
obj.create(self.session)
|
||||
self.assertFalse(obj.is_dirty)
|
||||
self.assertEqual(new_attr1, obj['attr1'])
|
||||
|
||||
obj['attr1'] = fake_attr1
|
||||
obj.second = fake_attr2
|
||||
self.assertTrue(obj.is_dirty)
|
||||
|
||||
obj.update(self.session)
|
||||
@ -118,6 +134,10 @@ class ResourceTests(base.TestTransportBase):
|
||||
self.assertEqual(fake_attr1, obj['attr1'])
|
||||
self.assertEqual(fake_attr2, obj['attr2'])
|
||||
|
||||
self.assertEqual(fake_name, obj.name)
|
||||
self.assertEqual(fake_attr1, obj.first)
|
||||
self.assertEqual(fake_attr2, obj.second)
|
||||
|
||||
@httpretty.activate
|
||||
def test_delete(self):
|
||||
self.stub_url(httpretty.GET, path=[fake_path, fake_id], json=fake_body)
|
||||
@ -149,4 +169,22 @@ class ResourceTests(base.TestTransportBase):
|
||||
for obj in objs:
|
||||
self.assertIn(obj.id, range(fake_id, fake_id + 3))
|
||||
self.assertEqual(fake_name, obj['name'])
|
||||
self.assertEqual(fake_name, obj.name)
|
||||
self.assertIsInstance(obj, FakeResource)
|
||||
|
||||
def test_attrs(self):
|
||||
obj = FakeResource()
|
||||
|
||||
try:
|
||||
obj.name
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
self.fail("Didn't raise attribute error")
|
||||
|
||||
try:
|
||||
del obj.name
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
self.fail("Didn't raise attribute error")
|
||||
|
Loading…
Reference in New Issue
Block a user