diff --git a/openstack/resource.py b/openstack/resource.py index 3a66b273e..a551cf62c 100644 --- a/openstack/resource.py +++ b/openstack/resource.py @@ -230,6 +230,22 @@ class Resource(collections.MutableMapping): """ return cls(kwargs, loaded=True) + @classmethod + def from_id(cls, value): + """Create an instance from an ID or return an existing instance. + + Instance creation is done via cls.new. + """ + # This method is useful in the higher level, in cases where operations + # need to depend on having Resource objects, but the API is flexible + # in taking text values which represent those objects. + if isinstance(value, cls): + return value + elif isinstance(value, six.string_types): + return cls.new(**{cls.id_attribute: value}) + else: + raise ValueError("value must be %s instance or id" % cls.__name__) + ## # MUTABLE MAPPING IMPLEMENTATION ## diff --git a/openstack/tests/test_resource.py b/openstack/tests/test_resource.py index f8e673ec2..60bf8e3b1 100644 --- a/openstack/tests/test_resource.py +++ b/openstack/tests/test_resource.py @@ -370,6 +370,26 @@ class ResourceTests(base.TestTransportBase): del t.id self.assertTrue(Test.id_attribute not in t._attrs) + def test_from_id_with_name(self): + name = "Sandy Koufax" + + obj = FakeResource.from_id(name) + self.assertEqual(obj.id, name) + + def test_from_id_with_object(self): + name = "Mickey Mantle" + obj = FakeResource.new(name=name) + + new_obj = FakeResource.from_id(obj) + self.assertIs(new_obj, obj) + self.assertEqual(new_obj.name, obj.name) + + def test_from_id_with_bad_value(self): + def should_raise(): + FakeResource.from_id(3.14) + + self.assertThat(should_raise, matchers.raises(ValueError)) + class FakeResponse: def __init__(self, response):