Repository: zhmcclient/python-zhmcclient

Issue:
Return newly created resources as a resource object not as a URI
In the current design, any `create()` methods return the new resource with their URI string.

This is inconsistent with the strategy of the zhmcclient project to encapsulate resources as Python objects.

**Proposal:**

Return a new Python object for the resource, in which only the URI property is set. That creates 3 different degrees of "property presence":

* only the URI property (only for resources that can be created by users)
* the short set of properties (as returned by list operations)
* the full set of properties (as returned by get properties operations)

We are already in the current design hiding the degree of property presence, for example, we are caching the properties after pulling them from the HMC, and `get_property()` pulls the properties as needed, so returning such a resource object seems to be consistent with that strategy.

We need to discuss what the `__str__()` and `__repr__()` functions should show (they will probably show the current state, i.e. make the three degrees of property presence visible to users).

We could drop the external availability of the `full_properties` flag, and make that internal only.

Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/7794079/badge)](https://coveralls.io/builds/7794079)

Coverage increased (+0.4%) to 88.352% when pulling **447c885d4954aacf55f533d18e209b331d2edee9 on andy/create-return-object** into **01674398fed64b49ebbf94ac49236cee4a02b3d3 on master**.


Patch:
diff --git a/zhmcclient/_adapter.py b/zhmcclient/_adapter.py
index cf76ac0..615ddc9 100644
--- a/zhmcclient/_adapter.py
+++ b/zhmcclient/_adapter.py
@@ -145,7 +145,9 @@ class AdapterManager(BaseManager):
 
         Returns:
 
-          string: The resource URI of the new adapter.
+          Adapter: The resource object for the new HiperSockets adapter.
+            The object will have its 'object-uri' property set as returned by
+            the HMC, and will also have the input properties set.
 
         Raises:
 
@@ -156,7 +158,11 @@ class AdapterManager(BaseManager):
         """
         cpc_uri = self.cpc.get_property('object-uri')
         result = self.session.post(cpc_uri + '/adapters', body=properties)
-        return result['object-uri']
+        # There should not be overlaps, but just in case there are, the
+        # returned props should overwrite the input props:
+        props = properties.copy()
+        props.update(result)
+        return Adapter(self, props['object-uri'], props)
 
 
 class Adapter(BaseResource):
diff --git a/zhmcclient/_hba.py b/zhmcclient/_hba.py
index 880d3a5..1042c23 100644
--- a/zhmcclient/_hba.py
+++ b/zhmcclient/_hba.py
@@ -79,9 +79,6 @@ class HbaManager(BaseManager):
           :exc:`~zhmcclient.AuthError`
           :exc:`~zhmcclient.ConnectionError`
         """
-        if not self.partition.full_properties:
-            self.partition.pull_full_properties()
-
         hbas_res = self.partition.get_property('hba-uris')
         hba_list = []
         if hbas_res:
@@ -105,7 +102,9 @@ class HbaManager(BaseManager):
 
         Returns:
 
-          string: The resource URI of the new HBA.
+          Hba: The resource object for the new HBA.
+            The object will have its 'element-uri' property set as returned by
+            the HMC, and will also have the input properties set.
 
         Raises:
 
@@ -116,7 +115,11 @@ class HbaManager(BaseManager):
         """
         partition_uri = self.partition.get_property('object-uri')
         result = self.session.post(partition_uri + '/hbas', body=properties)
-        return result['element-uri']
+        # There should not be overlaps, but just in case there are, the
+        # returned props should overwrite the input props:
+        props = properties.copy()
+        props.update(result)
+        return Hba(self, props['element-uri'], props)
 
 
 class Hba(BaseResource):
diff --git a/zhmcclient/_nic.py b/zhmcclient/_nic.py
index 4cc8779..720ee14 100644
--- a/zhmcclient/_nic.py
+++ b/zhmcclient/_nic.py
@@ -79,9 +79,6 @@ class NicManager(BaseManager):
           :exc:`~zhmcclient.AuthError`
           :exc:`~zhmcclient.ConnectionError`
         """
-        if not self.partition.full_properties:
-            self.partition.pull_full_properties()
-
         nics_res = self.partition.get_property('nic-uris')
         nic_list = []
         if nics_res:
@@ -105,7 +102,9 @@ class NicManager(BaseManager):
 
         Returns:
 
-          string: The resource URI of the new NIC.
+          Nic: The resource object for the new NIC.
+            The object will have its 'element-uri' property set as returned by
+            the HMC, and will also have the input properties set.
 
         Raises:
 
@@ -116,7 +115,11 @@ class NicManager(BaseManager):
         """
         partition_uri = self.partition.get_property('object-uri')
         result = self.session.post(partition_uri + '/nics', body=properties)
-        return result['element-uri']
+        # There should not be overlaps, but just in case there are, the
+        # returned props should overwrite the input props:
+        props = properties.copy()
+        props.update(result)
+        return Nic(self, props['element-uri'], props)
 
 
 class Nic(BaseResource):
diff --git a/zhmcclient/_partition.py b/zhmcclient/_partition.py
index 697883e..7c21dd7 100644
--- a/zhmcclient/_partition.py
+++ b/zhmcclient/_partition.py
@@ -110,7 +110,9 @@ class PartitionManager(BaseManager):
 
         Returns:
 
-          string: The resource URI of the new partition.
+          Partition: The resource object for the new partition.
+            The object will have its 'object-uri' property set as returned by
+            the HMC, and will also have the input properties set.
 
         Raises:
 
@@ -121,7 +123,11 @@ class PartitionManager(BaseManager):
         """
         cpc_uri = self.cpc.get_property('object-uri')
         result = self.session.post(cpc_uri + '/partitions', body=properties)
-        return result['object-uri']
+        # There should not be overlaps, but just in case there are, the
+        # returned props should overwrite the input props:
+        props = properties.copy()
+        props.update(result)
+        return Partition(self, props['object-uri'], props)
 
 
 class Partition(BaseResource):
diff --git a/zhmcclient/_resource.py b/zhmcclient/_resource.py
index 169da83..4554132 100644
--- a/zhmcclient/_resource.py
+++ b/zhmcclient/_resource.py
@@ -110,9 +110,11 @@ class BaseResource(object):
     @property
     def full_properties(self):
         """
-        A boolean indicating whether the resource properties in this object
-        are the full set of resource properties, vs. just the short set of
-        resource properties as obtained by list functions.
+        A boolean indicating whether or not the resource properties in this
+        object are the full set of resource properties.
+
+        Note that listing resources and creating new resources produces objects
+        that have less than the full set of properties.
         """
         return self._full_properties
 
@@ -170,7 +172,7 @@ class BaseResource(object):
         try:
             return self._properties[name]
         except KeyError:
-            if self.full_properties:
+            if self._full_properties:
                 raise
             self.pull_full_properties()
             return self._properties[name]
diff --git a/zhmcclient/_virtual_function.py b/zhmcclient/_virtual_function.py
index 02e3127..f4b75d3 100644
--- a/zhmcclient/_virtual_function.py
+++ b/zhmcclient/_virtual_function.py
@@ -78,9 +78,6 @@ class VirtualFunctionManager(BaseManager):
           :exc:`~zhmcclient.AuthError`
           :exc:`~zhmcclient.ConnectionError`
         """
-        if not self.partition.full_properties:
-            self.partition.pull_full_properties()
-
         vfs_res = self.partition.get_property('virtual-function-uris')
         vf_list = []
         if vfs_res:
@@ -105,7 +102,9 @@ class VirtualFunctionManager(BaseManager):
 
         Returns:
 
-          string: The resource URI of the new Virtual Function.
+          VirtualFunction: The resource object for the new virtual function.
+            The object will have its 'element-uri' property set as returned by
+            the HMC, and will also have the input properties set.
 
         Raises:
 
@@ -117,7 +116,11 @@ class VirtualFunctionManager(BaseManager):
         partition_uri = self.partition.get_property('object-uri')
         result = self.session.post(partition_uri + '/virtual-functions',
                                    body=properties)
-        return result['element-uri']
+        # There should not be overlaps, but just in case there are, the
+        # returned props should overwrite the input props:
+        props = properties.copy()
+        props.update(result)
+        return VirtualFunction(self, props['element-uri'], props)
 
 
 class VirtualFunction(BaseResource):


Test patch:
diff --git a/tests/test_adapter.py b/tests/test_adapter.py
index 7a1a4a3..9cfb62b 100644
--- a/tests/test_adapter.py
+++ b/tests/test_adapter.py
@@ -22,7 +22,7 @@ from __future__ import absolute_import
 import unittest
 import requests_mock
 
-from zhmcclient import Session, Client
+from zhmcclient import Session, Client, Adapter
 
 
 class AdapterTests(unittest.TestCase):
@@ -184,8 +184,11 @@ class AdapterTests(unittest.TestCase):
             }
             m.post('/api/cpcs/adapter-cpc-id-1/adapters', json=result)
 
-            status = adapter_mgr.create_hipersocket(properties={})
-            self.assertEqual(status, result['object-uri'])
+            adapter = adapter_mgr.create_hipersocket(properties={})
+
+            self.assertTrue(isinstance(adapter, Adapter))
+            self.assertEqual(adapter.properties, result)
+            self.assertEqual(adapter.uri, result['object-uri'])
 
     def test_delete(self):
         """
diff --git a/tests/test_hba.py b/tests/test_hba.py
index c9549fe..e4edf34 100644
--- a/tests/test_hba.py
+++ b/tests/test_hba.py
@@ -22,7 +22,7 @@ from __future__ import absolute_import
 import unittest
 import requests_mock
 
-from zhmcclient import Session, Client
+from zhmcclient import Session, Client, Hba
 
 
 class HbaTests(unittest.TestCase):
@@ -194,8 +194,11 @@ class HbaTests(unittest.TestCase):
             }
             m.post('/api/partitions/fake-part-id-1/hbas', json=result)
 
-            status = hba_mgr.create(properties={})
-            self.assertEqual(status, result['element-uri'])
+            hba = hba_mgr.create(properties={})
+
+            self.assertTrue(isinstance(hba, Hba))
+            self.assertEqual(hba.properties, result)
+            self.assertEqual(hba.uri, result['element-uri'])
 
     def test_delete(self):
         """
diff --git a/tests/test_nic.py b/tests/test_nic.py
index b0a77fe..a5d01e7 100644
--- a/tests/test_nic.py
+++ b/tests/test_nic.py
@@ -22,7 +22,7 @@ from __future__ import absolute_import
 import unittest
 import requests_mock
 
-from zhmcclient import Session, Client
+from zhmcclient import Session, Client, Nic
 
 
 class NicTests(unittest.TestCase):
@@ -194,8 +194,11 @@ class NicTests(unittest.TestCase):
             }
             m.post('/api/partitions/fake-part-id-1/nics', json=result)
 
-            status = nic_mgr.create(properties={})
-            self.assertEqual(status, result['element-uri'])
+            nic = nic_mgr.create(properties={})
+
+            self.assertTrue(isinstance(nic, Nic))
+            self.assertEqual(nic.properties, result)
+            self.assertEqual(nic.uri, result['element-uri'])
 
     def test_delete(self):
         """
diff --git a/tests/test_partition.py b/tests/test_partition.py
index 501fc0a..e7e468e 100644
--- a/tests/test_partition.py
+++ b/tests/test_partition.py
@@ -22,7 +22,7 @@ from __future__ import absolute_import
 import unittest
 import requests_mock
 
-from zhmcclient import Session, Client
+from zhmcclient import Session, Client, Partition
 
 
 class PartitionTests(unittest.TestCase):
@@ -165,8 +165,11 @@ class PartitionTests(unittest.TestCase):
             }
             m.post('/api/cpcs/fake-cpc-id-1/partitions', json=result)
 
-            status = partition_mgr.create(properties={})
-            self.assertEqual(status, result['object-uri'])
+            partition = partition_mgr.create(properties={})
+
+            self.assertTrue(isinstance(partition, Partition))
+            self.assertEqual(partition.properties, result)
+            self.assertEqual(partition.uri, result['object-uri'])
 
     def test_start(self):
         """
diff --git a/tests/test_virtual_function.py b/tests/test_virtual_function.py
index dbe8ef2..27e8140 100644
--- a/tests/test_virtual_function.py
+++ b/tests/test_virtual_function.py
@@ -22,7 +22,7 @@ from __future__ import absolute_import
 import unittest
 import requests_mock
 
-from zhmcclient import Session, Client
+from zhmcclient import Session, Client, VirtualFunction
 
 
 class VirtualFunctionTests(unittest.TestCase):
@@ -206,8 +206,11 @@ class VirtualFunctionTests(unittest.TestCase):
             m.post('/api/partitions/fake-part-id-1/virtual-functions',
                    json=result)
 
-            status = vf_mgr.create(properties={})
-            self.assertEqual(status, result['element-uri'])
+            vf = vf_mgr.create(properties={})
+
+            self.assertTrue(isinstance(vf, VirtualFunction))
+            self.assertEqual(vf.properties, result)
+            self.assertEqual(vf.uri, result['element-uri'])
 
     def test_delete(self):
         """