Repository: tomplus/kubernetes_asyncio (language: python)

Issue:
`application/apply-patch+yaml` content type is no longer supported since release 30.1.0
Hi 

The following code:
```python
import asyncio
from pprint import pprint

from kubernetes_asyncio.client import ApiClient, CoreV1Api
from kubernetes_asyncio.config import load_kube_config


async def create_update_cm():
    await load_kube_config()
    async with ApiClient() as client:
        corev1 = CoreV1Api(client)
        try:
            await corev1.patch_namespaced_config_map(
                _content_type="application/apply-patch+yaml",
                field_manager="test",
                name="test",
                namespace="default",
                body=dict(
                    apiVersion="v1",
                    kind="ConfigMap",
                    metadata=dict(
                        namespace="default",
                        name="test",
                    ),
                    data={"hello": "world!"},
                ),
            )
            pprint(
                await corev1.read_namespaced_config_map(
                    name="test",
                    namespace="default",
                )
            )
        finally:
            await corev1.delete_namespaced_config_map(name="test", namespace="default")


if __name__ == "__main__":
    asyncio.run(create_update_cm())
```

Now fails with
```
Traceback (most recent call last):
  File "site-packages/kubernetes_asyncio/client/api_client.py", line 185, in __call_api
    response_data = await self.request(
                    ^^^^^^^^^^^^^^^^^^^
  File "site-packages/kubernetes_asyncio/client/rest.py", line 253, in PATCH
    return (await self.request("PATCH", url,
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "site-packages/kubernetes_asyncio/client/rest.py", line 177, in request
    raise ApiException(status=0, reason=msg)
kubernetes_asyncio.client.exceptions.ApiException: (0)
Reason: Cannot prepare a request message for provided
                         arguments. Please check that your arguments match
                         declared content type.
```

The issue seems related to changes in #303, especially https://github.com/tomplus/kubernetes_asyncio/pull/303/files#diff-481a59905e58931309ea5b06ec23b1a38bf6618415a0378a03585a407f7b16f1L145





Discussion / hints:

Fix patch:
diff --git a/kubernetes_asyncio/client/rest.py b/kubernetes_asyncio/client/rest.py
index 8b50711f..f49c9de4 100644
--- a/kubernetes_asyncio/client/rest.py
+++ b/kubernetes_asyncio/client/rest.py
@@ -142,7 +142,10 @@ async def request(self, method, url, query_params=None, headers=None,
 
         # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
         if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
-            if re.search('json', headers['Content-Type'], re.IGNORECASE):
+            if (
+                    re.search('json', headers['Content-Type'], re.IGNORECASE)
+                    or headers['Content-Type'] in ["application/apply-patch+yaml"]
+            ):
                 if body is not None:
                     body = json.dumps(body)
                 args["data"] = body
diff --git a/scripts/rest_client_apply_patch_patch.diff b/scripts/rest_client_apply_patch_patch.diff
new file mode 100644
index 00000000..3348836d
--- /dev/null
+++ b/scripts/rest_client_apply_patch_patch.diff
@@ -0,0 +1,15 @@
+diff --git a/kubernetes_asyncio/client/rest.py b/kubernetes_asyncio/client/rest.py
+--- a/kubernetes_asyncio/client/rest.py
++++ b/kubernetes_asyncio/client/rest.py
+@@ -142,7 +142,10 @@
+
+         # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
+         if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
+-            if re.search('json', headers['Content-Type'], re.IGNORECASE):
++            if (
++                    re.search('json', headers['Content-Type'], re.IGNORECASE)
++                    or headers['Content-Type'] in ["application/apply-patch+yaml"]
++            ):
+                 if body is not None:
+                     body = json.dumps(body)
+                 args["data"] = body
diff --git a/scripts/update-client.sh b/scripts/update-client.sh
index 98526506..73ce16e9 100755
--- a/scripts/update-client.sh
+++ b/scripts/update-client.sh
@@ -66,6 +66,8 @@ sed -i'' "s,^DEVELOPMENT_STATUS = .*,DEVELOPMENT_STATUS = \\\"${DEVELOPMENT_STAT
 
 echo ">>> fix generated api client for patching with strategic merge..."
 patch "${CLIENT_ROOT}/client/api_client.py" "${SCRIPT_ROOT}/api_client_strategic_merge_patch.diff"
+echo ">>> fix generated rest client by accepting application/apply-patch+yaml content type"
+patch "${CLIENT_ROOT}/client/rest.py" "${SCRIPT_ROOT}/rest_client_apply_patch_patch.diff"
 echo ">>> fix generated rest client by increasing aiohttp read buffer to 2MiB..."
 patch "${CLIENT_ROOT}/client/rest.py" "${SCRIPT_ROOT}/rest_client_patch_read_bufsize.diff"
 echo ">>> fix generated rest client and configuration to support customer server hostname TLS verification..."


Test patch:
diff --git a/kubernetes_asyncio/e2e_test/test_apply_patch.py b/kubernetes_asyncio/e2e_test/test_apply_patch.py
new file mode 100644
index 00000000..2c9deac3
--- /dev/null
+++ b/kubernetes_asyncio/e2e_test/test_apply_patch.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import uuid
+from unittest import IsolatedAsyncioTestCase
+
+from kubernetes_asyncio.client import api_client
+from kubernetes_asyncio.client.api import core_v1_api
+from kubernetes_asyncio.e2e_test import base
+
+
+class TestApplyPatch(IsolatedAsyncioTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.config = base.get_e2e_configuration()
+
+    async def test_apply_patch(self):
+        client = api_client.ApiClient(configuration=self.config)
+        api = core_v1_api.CoreV1Api(client)
+
+        name = "cm-test" + str(uuid.uuid4())
+        manifest = dict(
+            apiVersion="v1",
+            kind="ConfigMap",
+            metadata=dict(
+                namespace="default",
+                name=name,
+            ),
+            data={"hello": "world!"},
+        )
+
+        resp = await api.patch_namespaced_config_map(
+            _content_type="application/apply-patch+yaml",
+            field_manager="test",
+            body=manifest,
+            name=name,
+            namespace="default",
+        )
+        self.assertEqual(name, resp.metadata.name)
+
+        resp = await api.read_namespaced_config_map(name=name, namespace="default")
+        self.assertEqual(name, resp.metadata.name)
+
+        resp = await api.delete_namespaced_config_map(
+            name=name, body={}, namespace="default"
+        )