Allow alternate_id to be accessed directly
When a Resource has an alternate_id but not a primary id, its usage is a bit inconsistent. While we make things work internally, such as when you do a Resource.get call where we find the alternate_id, you still can't just call foo.id and get a valid ID back. The Keypair resource in compute is one of these examples, and its functional tests are currently failing because they depend on passing in "sot.id". While they could be engineered around that, it's certainly a valid usecase to support, and this change enables it. It does so by implementing __getattribute__ on the Resource class and then special-casing a request for the "id" name. First we try to get the primary "id" directly, and if nothing happens there then we try to get an alternate_id. Additionally, this changes the special case in the Resource.create method that was handling any create call with an existing ID as requiring that it go through the PUT branch when id exists. Relying on the id value itself being set for determining that branching is problematic in that these alternate_id resources would then trigger that path, sending creates which must happen by POST into the PUT branch just because their alternate_id becomes additionally known as the canonical id. Instead, we now have put_create as the special case, which is a fairly rare case as it stands. At least object_store will need to have its resources set with that flag once it is converted over, though there may be others as well. Change-Id: Ia1fd1782f4ad8fa856c16d75bba4d8931307a6e7
This commit is contained in:
@@ -243,6 +243,8 @@ class Resource(object):
|
||||
allow_head = False
|
||||
#: Use PATCH for update operations on this resource.
|
||||
patch_update = False
|
||||
#: Use PUT for create operations on this resource.
|
||||
put_create = False
|
||||
|
||||
def __init__(self, synchronized=False, **attrs):
|
||||
# NOTE: _collect_attrs modifies **attrs in place, removing
|
||||
@@ -276,6 +278,21 @@ class Resource(object):
|
||||
self._header.attributes == comparand._header.attributes,
|
||||
self._uri.attributes == comparand._uri.attributes])
|
||||
|
||||
def __getattribute__(self, name):
|
||||
"""Return an attribute on this instance
|
||||
|
||||
This is mostly a pass-through except for a specialization on
|
||||
the 'id' name, as this can exist under a different name via the
|
||||
`alternate_id` argument to resource.Body.
|
||||
"""
|
||||
if name == "id":
|
||||
if name in self._body:
|
||||
return self._body[name]
|
||||
else:
|
||||
return self._body[self._alternate_id()]
|
||||
else:
|
||||
return object.__getattribute__(self, name)
|
||||
|
||||
def _update(self, **attrs):
|
||||
"""Given attributes, update them on this instance
|
||||
|
||||
@@ -495,16 +512,16 @@ class Resource(object):
|
||||
if not self.allow_create:
|
||||
raise exceptions.MethodNotSupported(self, "create")
|
||||
|
||||
if self.id is None:
|
||||
request = self._prepare_request(requires_id=False,
|
||||
prepend_key=True)
|
||||
response = session.post(request.uri, endpoint_filter=self.service,
|
||||
json=request.body, headers=request.headers)
|
||||
else:
|
||||
if self.put_create:
|
||||
request = self._prepare_request(requires_id=True,
|
||||
prepend_key=True)
|
||||
response = session.put(request.uri, endpoint_filter=self.service,
|
||||
json=request.body, headers=request.headers)
|
||||
else:
|
||||
request = self._prepare_request(requires_id=False,
|
||||
prepend_key=True)
|
||||
response = session.post(request.uri, endpoint_filter=self.service,
|
||||
json=request.body, headers=request.headers)
|
||||
|
||||
self._translate_response(response)
|
||||
return self
|
||||
|
||||
@@ -409,6 +409,7 @@ class TestResource(base.TestCase):
|
||||
self.assertFalse(sot.allow_list)
|
||||
self.assertFalse(sot.allow_head)
|
||||
self.assertFalse(sot.patch_update)
|
||||
self.assertFalse(sot.put_create)
|
||||
|
||||
def test_repr(self):
|
||||
a = {"a": 1}
|
||||
@@ -576,6 +577,11 @@ class TestResource(base.TestCase):
|
||||
|
||||
self.assertTrue("alt", Test._alternate_id())
|
||||
|
||||
value = "lol"
|
||||
sot = Test(alt=value)
|
||||
self.assertEqual(sot.alt, value)
|
||||
self.assertEqual(sot.id, value)
|
||||
|
||||
def test__get_id_instance(self):
|
||||
class Test(resource2.Resource):
|
||||
id = resource2.Body("id")
|
||||
@@ -800,13 +806,15 @@ class TestResourceActions(base.TestCase):
|
||||
self.session.delete = mock.Mock(return_value=self.response)
|
||||
self.session.head = mock.Mock(return_value=self.response)
|
||||
|
||||
def _test_create(self, requires_id=False, prepend_key=False):
|
||||
if not requires_id:
|
||||
self.sot.id = None
|
||||
def _test_create(self, cls, requires_id=False, prepend_key=False):
|
||||
id = "id" if requires_id else None
|
||||
sot = cls(id=id)
|
||||
sot._prepare_request = mock.Mock(return_value=self.request)
|
||||
sot._translate_response = mock.Mock()
|
||||
|
||||
result = self.sot.create(self.session)
|
||||
result = sot.create(self.session)
|
||||
|
||||
self.sot._prepare_request.assert_called_once_with(
|
||||
sot._prepare_request.assert_called_once_with(
|
||||
requires_id=requires_id, prepend_key=prepend_key)
|
||||
if requires_id:
|
||||
self.session.put.assert_called_once_with(
|
||||
@@ -819,14 +827,26 @@ class TestResourceActions(base.TestCase):
|
||||
endpoint_filter=self.service_name,
|
||||
json=self.request.body, headers=self.request.headers)
|
||||
|
||||
self.sot._translate_response.assert_called_once_with(self.response)
|
||||
self.assertEqual(result, self.sot)
|
||||
sot._translate_response.assert_called_once_with(self.response)
|
||||
self.assertEqual(result, sot)
|
||||
|
||||
def test_create_with_id(self):
|
||||
self._test_create(requires_id=True, prepend_key=True)
|
||||
def test_put_create(self):
|
||||
class Test(resource2.Resource):
|
||||
service = self.service_name
|
||||
base_path = self.base_path
|
||||
allow_create = True
|
||||
put_create = True
|
||||
|
||||
def test_create_without_id(self):
|
||||
self._test_create(requires_id=False, prepend_key=True)
|
||||
self._test_create(Test, requires_id=True, prepend_key=True)
|
||||
|
||||
def test_post_create(self):
|
||||
class Test(resource2.Resource):
|
||||
service = self.service_name
|
||||
base_path = self.base_path
|
||||
allow_create = True
|
||||
put_create = False
|
||||
|
||||
self._test_create(Test, requires_id=False, prepend_key=True)
|
||||
|
||||
def test_get(self):
|
||||
result = self.sot.get(self.session)
|
||||
|
||||
Reference in New Issue
Block a user