Repository: opensearch-project/opensearch-py
Pull request #813 (2024-08-26 19:24:32)

Fix: generator adds new namespace.
### Description

Fixes `nox -rs generate` by properly adding any new namespace in-place of other ones.

<img width="1023" alt="Screenshot 2024-08-26 at 3 24 21 PM" src="https://github.com/user-attachments/assets/0806519c-8474-4058-89eb-894607b658cc">

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check [here](https://github.com/opensearch-project/OpenSearch/blob/main/CONTRIBUTING.md#developer-certificate-of-origin).


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/opensearch-project/opensearch-py/pull/813?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensearch-project) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 70.30%. Comparing base [(`ba715b9`)](https://app.codecov.io/gh/opensearch-project/opensearch-py/commit/ba715b9d3f0609d25bfb0210b4b52234601e4dd8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensearch-project) to head [(`218bc15`)](https://app.codecov.io/gh/opensearch-project/opensearch-py/commit/218bc1511fa0f8e6703b2658b92d4074a19330e7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensearch-project).
> Report is 57 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #813      +/-   ##
==========================================
- Coverage   71.95%   70.30%   -1.65%     
==========================================
  Files          91      113      +22     
  Lines        8001     8877     +876     
==========================================
+ Hits         5757     6241     +484     
- Misses       2244     2636     +392     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/opensearch-project/opensearch-py/pull/813?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensearch-project).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opensearch-project).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0a604fb3..672fd065 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
 ### Deprecated
 ### Removed
 ### Fixed
+- Fix `Transport.perform_request`'s arguments `timeout` and `ignore` variable usage ([810](https://github.com/opensearch-project/opensearch-py/pull/810))
 ### Security
 
 ### Dependencies
diff --git a/opensearchpy/_async/client/plugins.py b/opensearchpy/_async/client/plugins.py
index 526e1cab..acf1ec57 100644
--- a/opensearchpy/_async/client/plugins.py
+++ b/opensearchpy/_async/client/plugins.py
@@ -28,6 +28,15 @@ from .utils import NamespacedClient
 class PluginsClient(NamespacedClient):
     alerting: Any
     index_management: Any
+    knn: Any
+    ml: Any
+    notifications: Any
+    observability: Any
+    ppl: Any
+    query: Any
+    rollups: Any
+    sql: Any
+    transforms: Any
 
     def __init__(self, client: Client) -> None:
         super().__init__(client)
@@ -50,13 +59,17 @@ class PluginsClient(NamespacedClient):
         # Issue : https://github.com/opensearch-project/opensearch-py/issues/90#issuecomment-1003396742
 
         plugins = [
-            # "query_workbench",
-            # "reporting",
-            # "notebooks",
             "alerting",
-            # "anomaly_detection",
-            # "trace_analytics",
             "index_management",
+            "knn",
+            "ml",
+            "notifications",
+            "observability",
+            "ppl",
+            "query",
+            "rollups",
+            "sql",
+            "transforms",
         ]
         for plugin in plugins:
             if not hasattr(client, plugin):
diff --git a/opensearchpy/_async/transport.py b/opensearchpy/_async/transport.py
index bc9e257f..2d631ee7 100644
--- a/opensearchpy/_async/transport.py
+++ b/opensearchpy/_async/transport.py
@@ -371,11 +371,13 @@ class AsyncTransport(Transport):
             underlying :class:`~opensearchpy.Connection` class for serialization
         :arg body: body of the request, will be serialized using serializer and
             passed to the connection
+        :arg timeout: timeout of the request. If it is not presented as argument
+            will be extracted from `params`
         """
         await self._async_call()
 
         method, params, body, ignore, timeout = self._resolve_request_args(
-            method, params, body
+            method, params, body, ignore, timeout
         )
 
         for attempt in range(self.max_retries + 1):
diff --git a/opensearchpy/transport.py b/opensearchpy/transport.py
index 2385b2b8..559d0387 100644
--- a/opensearchpy/transport.py
+++ b/opensearchpy/transport.py
@@ -404,9 +404,11 @@ class Transport:
             underlying :class:`~opensearchpy.Connection` class for serialization
         :arg body: body of the request, will be serialized using serializer and
             passed to the connection
+        :arg timeout: timeout of the request. If it is not presented as argument
+            will be extracted from `params`
         """
         method, params, body, ignore, timeout = self._resolve_request_args(
-            method, params, body
+            method, params, body, ignore, timeout
         )
 
         for attempt in range(self.max_retries + 1):
@@ -473,7 +475,14 @@ class Transport:
         """
         return self.connection_pool.close()
 
-    def _resolve_request_args(self, method: str, params: Any, body: Any) -> Any:
+    def _resolve_request_args(
+        self,
+        method: str,
+        params: Any,
+        body: Any,
+        ignore: Collection[int],
+        timeout: Optional[Union[int, float]],
+    ) -> Any:
         """Resolves parameters for .perform_request()"""
         if body is not None:
             body = self.serializer.dumps(body)
@@ -498,13 +507,13 @@ class Transport:
                 # bytes/str - no need to re-encode
                 pass
 
-        ignore = ()
-        timeout = None
         if params:
-            timeout = params.pop("request_timeout", None)
             if not timeout:
-                timeout = params.pop("timeout", None)
-            ignore = params.pop("ignore", ())
+                timeout = params.pop("request_timeout", None) or params.pop(
+                    "timeout", None
+                )
+            if not ignore:
+                ignore = params.pop("ignore", ())
             if isinstance(ignore, int):
                 ignore = (ignore,)
 
diff --git a/utils/generate_api.py b/utils/generate_api.py
index 1385b3ad..c85ed8d2 100644
--- a/utils/generate_api.py
+++ b/utils/generate_api.py
@@ -178,18 +178,26 @@ class Module:
                     "opensearchpy/_async/client/plugins.py", "r+", encoding="utf-8"
                 ) as file:
                     content = file.read()
-                    file_content = content.replace(
-                        "super(PluginsClient, self).__init__(client)",
-                        f"super(PluginsClient, self).__init__(client)\n        self.{self.namespace} = {self.namespace_new}Client(client)",  # pylint: disable=line-too-long
+                    content = content.replace(
+                        "super().__init__(client)\n",
+                        f"super().__init__(client)\n\n        self.{self.namespace} = {self.namespace_new}Client(client)",  # pylint: disable=line-too-long
                         1,
                     )
-                    new_file_content = file_content.replace(
+                    content = content.replace(
                         "from .client import Client",
                         f"from ..plugins.{self.namespace} import {self.namespace_new}Client\nfrom .client import Client",  # pylint: disable=line-too-long
                         1,
                     )
+                    content = content.replace(
+                        "class PluginsClient(NamespacedClient):\n",
+                        f"class PluginsClient(NamespacedClient): \n    {self.namespace}: Any\n",  # pylint: disable=line-too-long
+                        1,
+                    )
+                    content = content.replace(
+                        "plugins = [", f'plugins = [\n            "{self.namespace}",\n'
+                    )
                     file.seek(0)
-                    file.write(new_file_content)
+                    file.write(content)
                     file.truncate()
 
             else:


Test patch:
diff --git a/test_opensearchpy/test_async/test_transport.py b/test_opensearchpy/test_async/test_transport.py
index e3048a48..51388ddd 100644
--- a/test_opensearchpy/test_async/test_transport.py
+++ b/test_opensearchpy/test_async/test_transport.py
@@ -175,6 +175,20 @@ class TestTransport:
             "headers": {"x-opaque-id": "request-1"},
         } == t.get_connection().calls[1][1]
 
+    async def test_perform_request_all_arguments_passed_correctly(self) -> None:
+        t: Any = AsyncTransport([{}], connection_class=DummyConnection)
+        method, url, params, body = "GET", "/test_url", {"params": "test"}, "test_body"
+        timeout, ignore, headers = 11, ("ignore",), {"h": "test"}
+
+        await t.perform_request(method, url, params, body, timeout, ignore, headers)
+
+        assert t.get_connection().calls == [
+            (
+                (method, url, params, body.encode()),
+                {"headers": headers, "ignore": ignore, "timeout": timeout},
+            )
+        ]
+
     async def test_request_with_custom_user_agent_header(self) -> None:
         t: Any = AsyncTransport([{}], connection_class=DummyConnection)
 
diff --git a/test_opensearchpy/test_transport.py b/test_opensearchpy/test_transport.py
index ccef21df..96422a3f 100644
--- a/test_opensearchpy/test_transport.py
+++ b/test_opensearchpy/test_transport.py
@@ -260,6 +260,23 @@ class TestTransport(TestCase):
             "http://google.com:1234", t.connection_pool.connections[1].host
         )
 
+    def test_perform_request_all_arguments_passed_correctly(self) -> None:
+        t: Any = Transport([{}], connection_class=DummyConnection)
+        method, url, params, body = "GET", "/test_url", {"params": "test"}, "test_body"
+        timeout, ignore, headers = 11, ("ignore",), {"h": "test"}
+
+        t.perform_request(method, url, params, body, timeout, ignore, headers)
+
+        self.assertEqual(
+            t.get_connection().calls,
+            [
+                (
+                    (method, url, params, body.encode()),
+                    {"headers": headers, "ignore": ignore, "timeout": timeout},
+                )
+            ],
+        )
+
     def test_request_will_fail_after_x_retries(self) -> None:
         t: Any = Transport(
             [{"exception": ConnectionError(None, "abandon ship", Exception())}],