Repository: tomplus/kubernetes_asyncio
Pull request #143 (2021-03-20 23:55:04)

feat: add function to create objects from dict
It closes https://github.com/tomplus/kubernetes_asyncio/issues/142

codecov[bot]: # [Codecov](https://codecov.io/gh/tomplus/kubernetes_asyncio/pull/143?src=pr&el=h1) Report
> Merging [#143](https://codecov.io/gh/tomplus/kubernetes_asyncio/pull/143?src=pr&el=desc) (ef7109b) into [master](https://codecov.io/gh/tomplus/kubernetes_asyncio/commit/7bf1100b6acc1b01ed803623b01cab2cba71d748?el=desc) (7bf1100) will **increase** coverage by `0.01%`.
> The diff coverage is `63.41%`.

[![Impacted file tree graph](https://codecov.io/gh/tomplus/kubernetes_asyncio/pull/143/graphs/tree.svg?width=650&height=150&src=pr&token=0Dvm4fqopq)](https://codecov.io/gh/tomplus/kubernetes_asyncio/pull/143?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #143      +/-   ##
==========================================
+ Coverage   31.15%   31.17%   +0.01%     
==========================================
  Files         817      817              
  Lines       93181    93206      +25     
==========================================
+ Hits        29034    29055      +21     
- Misses      64147    64151       +4     
```


| [Impacted Files](https://codecov.io/gh/tomplus/kubernetes_asyncio/pull/143?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [kubernetes\_asyncio/utils/\_\_init\_\_.py](https://codecov.io/gh/tomplus/kubernetes_asyncio/pull/143/diff?src=pr&el=tree#diff-a3ViZXJuZXRlc19hc3luY2lvL3V0aWxzL19faW5pdF9fLnB5) | `100.00% <ø> (ø)` | |
| [kubernetes\_asyncio/utils/create\_from\_yaml.py](https://codecov.io/gh/tomplus/kubernetes_asyncio/pull/143/diff?src=pr&el=tree#diff-a3ViZXJuZXRlc19hc3luY2lvL3V0aWxzL2NyZWF0ZV9mcm9tX3lhbWwucHk=) | `61.76% <50.00%> (+4.07%)` | :arrow_up: |
| [kubernetes\_asyncio/utils/create\_from\_yaml\_test.py](https://codecov.io/gh/tomplus/kubernetes_asyncio/pull/143/diff?src=pr&el=tree#diff-a3ViZXJuZXRlc19hc3luY2lvL3V0aWxzL2NyZWF0ZV9mcm9tX3lhbWxfdGVzdC5weQ==) | `90.00% <100.00%> (+8.18%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/tomplus/kubernetes_asyncio/pull/143?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/tomplus/kubernetes_asyncio/pull/143?src=pr&el=footer). Last update [7bf1100...ef7109b](https://codecov.io/gh/tomplus/kubernetes_asyncio/pull/143?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/kubernetes_asyncio/utils/__init__.py b/kubernetes_asyncio/utils/__init__.py
index 3c183332..b1244b9a 100644
--- a/kubernetes_asyncio/utils/__init__.py
+++ b/kubernetes_asyncio/utils/__init__.py
@@ -15,5 +15,6 @@
 from __future__ import absolute_import
 
 from .create_from_yaml import (
-    FailToCreateError, create_from_yaml, create_from_yaml_single_item,
+    FailToCreateError, create_from_dict, create_from_yaml,
+    create_from_yaml_single_item,
 )
diff --git a/kubernetes_asyncio/utils/create_from_yaml.py b/kubernetes_asyncio/utils/create_from_yaml.py
index a7bc4e5f..3ef14b09 100644
--- a/kubernetes_asyncio/utils/create_from_yaml.py
+++ b/kubernetes_asyncio/utils/create_from_yaml.py
@@ -61,35 +61,94 @@ async def create_from_yaml(
     with open(path.abspath(yaml_file)) as f:
         yml_document_all = yaml.safe_load_all(f)
         api_exceptions = []
+        k8s_objects = []
         # Load all documents from a single YAML file
         for yml_document in yml_document_all:
-            # If it is a list type, will need to iterate its items
-            if "List" in yml_document["kind"]:
-                # Could be "List" or "Pod/Service/...List"
-                # This is a list type. iterate within its items
-                kind = yml_document["kind"].replace("List", "")
-                for yml_object in yml_document["items"]:
-                    # Mitigate cases when server returns a xxxList object
-                    # See kubernetes-client/python#586
-                    if kind != "":
-                        yml_object["apiVersion"] = yml_document["apiVersion"]
-                        yml_object["kind"] = kind
-                    try:
-                        await create_from_yaml_single_item(
-                            k8s_client, yml_object, verbose, namespace, **kwargs)
-                    except client.rest.ApiException as api_exception:
-                        api_exceptions.append(api_exception)
-            else:
-                # This is a single object. Call the single item method
-                try:
-                    await create_from_yaml_single_item(
-                        k8s_client, yml_document, verbose, namespace, **kwargs)
-                except client.rest.ApiException as api_exception:
-                    api_exceptions.append(api_exception)
+            try:
+                created = await create_from_dict(k8s_client, yml_document,
+                                                 verbose, namespace=namespace,
+                                                 **kwargs)
+                k8s_objects.append(created)
+            except FailToCreateError as failure:
+                api_exceptions.extend(failure)
+
     # In case we have exceptions waiting for us, raise them
     if api_exceptions:
         raise FailToCreateError(api_exceptions)
 
+    return k8s_objects
+
+
+async def create_from_dict(
+        k8s_client,
+        data,
+        verbose=False,
+        namespace="default",
+        **kwargs):
+    """
+    Perform an action from a yaml file. Pass True for verbose to
+    print confirmation information.
+    Input:
+    yaml_file: string. Contains the path to yaml file.
+    data: a dictionary holding valid kubernetes objects
+    verbose: If True, print confirmation from the create action.
+        Default is False.
+    namespace: string. Contains the namespace to create all
+        resources inside. The namespace must preexist otherwise
+        the resource creation will fail. If the API object in
+        the yaml file already contains a namespace definition
+        this parameter has no effect.
+    Returns:
+    An k8s api object or list of apis objects created from dict.
+    When a single object is generated, return type is dependent
+    on output_list.
+    Throws a FailToCreateError exception if creation of any object
+    fails with helpful messages from the server.
+    Available parameters for creating <kind>:
+    :param async_req bool
+    :param bool include_uninitialized: If true, partially initialized
+        resources are included in the response.
+    :param str pretty: If 'true', then the output is pretty printed.
+    :param str dry_run: When present, indicates that modifications
+        should not be persisted. An invalid or unrecognized dryRun
+        directive will result in an error response and no further
+        processing of the request.
+        Valid values are: - All: all dry run stages will be processed
+    """
+    api_exceptions = []
+    k8s_objects = []
+
+    # If it is a list type, will need to iterate its items
+    if "List" in data["kind"]:
+        # Could be "List" or "Pod/Service/...List"
+        # This is a list type. iterate within its items
+        kind = data["kind"].replace("List", "")
+        for yml_object in data["items"]:
+            # Mitigate cases when server returns a xxxList object
+            # See kubernetes-client/python#586
+            if kind != "":
+                yml_object["apiVersion"] = data["apiVersion"]
+                yml_object["kind"] = kind
+            try:
+                created = await create_from_yaml_single_item(
+                    k8s_client, yml_object, verbose, namespace, **kwargs)
+                k8s_objects.append(created)
+            except client.rest.ApiException as api_exception:
+                api_exceptions.append(api_exception)
+    else:
+        # This is a single object. Call the single item method
+        try:
+            created = await create_from_yaml_single_item(
+                k8s_client, data, verbose, namespace, **kwargs)
+            k8s_objects.append(created)
+        except client.rest.ApiException as api_exception:
+            api_exceptions.append(api_exception)
+
+    if api_exceptions:
+        raise FailToCreateError(api_exceptions)
+
+    return k8s_objects
+
 
 async def create_from_yaml_single_item(
         k8s_client,
@@ -126,6 +185,7 @@ async def create_from_yaml_single_item(
             body=yml_object, **kwargs)
     if verbose:
         print("{0} created. status='{1}'".format(kind, str(resp.status)))
+    return resp
 
 
 class FailToCreateError(Exception):


Test patch:
diff --git a/kubernetes_asyncio/utils/create_from_yaml_test.py b/kubernetes_asyncio/utils/create_from_yaml_test.py
index 85588855..67198442 100644
--- a/kubernetes_asyncio/utils/create_from_yaml_test.py
+++ b/kubernetes_asyncio/utils/create_from_yaml_test.py
@@ -14,7 +14,7 @@
 
 from asynctest import CoroutineMock, TestCase
 
-from kubernetes_asyncio.utils import create_from_yaml
+from kubernetes_asyncio.utils import create_from_dict, create_from_yaml
 
 
 class CreateFromYamlTest(TestCase):
@@ -22,13 +22,39 @@ class CreateFromYamlTest(TestCase):
     async def test_create_from_yaml(self):
         api_client = CoroutineMock()
         api_client.call_api = CoroutineMock()
+        api_client.call_api.return_value = 'mock-value'
 
-        await create_from_yaml(api_client, 'examples/nginx-deployment.yaml')
+        created = await create_from_yaml(api_client, 'examples/nginx-deployment.yaml')
 
         # simple check for api call
         self.assertEqual(api_client.call_api.call_args[0][0],
                          '/apis/apps/v1/namespaces/{namespace}/deployments')
 
+        # returned values
+        self.assertEqual(created, [['mock-value']])
+
+    async def test_create_from_dict(self):
+        api_client = CoroutineMock()
+        api_client.call_api = CoroutineMock()
+        api_client.call_api.return_value = 'mock-value'
+
+        created = await create_from_dict(api_client, {
+            'apiVersion': 'apps/v1',
+            'kind': 'Deployment',
+            'metadata': {
+                'name': 'nginx-deployment'},
+            'spec': {
+                'replicas': 3,
+            }
+        })
+
+        # simple check for api call
+        self.assertEqual(api_client.call_api.call_args[0][0],
+                         '/apis/apps/v1/namespaces/{namespace}/deployments')
+
+        # returned values
+        self.assertEqual(created, ['mock-value'])
+
 
 if __name__ == '__main__':
     import asynctest