Repository: opensearch-project/opensearch-py (python)

Issue:
[BUG] v2.7.0 brings undocumented breaking change to put_index()
### What is the bug?
With versions prior to version 2.7.0 it was possible to call `client.indices.put_alias("index_name", "alias_name")` and have it create the alias.

With version 2.7.0 you instead get a cryptic error message along the lines of:

> opensearchpy.exceptions.RequestError: RequestError(400, 'json_parse_exception', "Unrecognized token 'alias_name': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 13]")

### How can one reproduce the bug?
1. Create an index
2. Attempt to create an alias for it with `client.indices.put_alias("index_name", "alias_name")`

### What is the expected behavior?
The alias should be created for the index like in previous opensearch-py versions.

### What is your host/environment?
N/A

### Do you have any screenshots?
N/A

### Do you have any additional context?
This is caused by an inadvertently breaking change in https://github.com/opensearch-project/opensearch-py/pull/777 which changed the order of the arguments to the `put_alias()` method, meaning anybody who called the method without naming the arguments now supply their values to different arguments.

Pull request:
Add override for code generator to change `indices.put_alias` argument order
### Description
This solves an issue introduced in v2.7.0 where the argument order of the `indices.put_alias()` method were altered, resulting in a breaking change for any user that called the function without naming the arguments provided.

### Issues Resolved
Closes #803.

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).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3d28ec81..0a7b93fd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
 - Fixed the use of `minimum_should_match` with `Bool` to allow the use of string-based value (percent string, combination). ([#780](https://github.com/opensearch-project/opensearch-py/pull/780))
 - Fixed incorrect `retry_on_conflict` type ([#795](https://github.com/opensearch-project/opensearch-py/pull/795))
 ### Updated APIs
+- Updated opensearch-py APIs to reflect [opensearch-api-specification@b28c1cf](https://github.com/opensearch-project/opensearch-api-specification/commit/b28c1cfaf4a76d786c789929eaa7fabffd09cb99)
 - Updated opensearch-py APIs to reflect [opensearch-api-specification@9d3bc34](https://github.com/opensearch-project/opensearch-api-specification/commit/9d3bc340ccd7d049e7d6e14a4aff2293780cb446)
 ### Security
 ### Dependencies
diff --git a/opensearchpy/_async/client/indices.py b/opensearchpy/_async/client/indices.py
index e0006889..2c692fe8 100644
--- a/opensearchpy/_async/client/indices.py
+++ b/opensearchpy/_async/client/indices.py
@@ -852,63 +852,6 @@ class IndicesClient(NamespacedClient):
             headers=headers,
         )
 
-    @query_params(
-        "cluster_manager_timeout",
-        "error_trace",
-        "filter_path",
-        "human",
-        "master_timeout",
-        "pretty",
-        "source",
-        "timeout",
-    )
-    async def put_alias(
-        self,
-        body: Any = None,
-        index: Any = None,
-        name: Any = None,
-        params: Any = None,
-        headers: Any = None,
-    ) -> Any:
-        """
-        Creates or updates an alias.
-
-
-        :arg body: The settings for the alias, such as `routing` or
-            `filter`
-        :arg index: Comma-separated list of data streams or indices to
-            add. Supports wildcards (`*`). Wildcard patterns that match both data
-            streams and indices return an error.
-        :arg name: Alias to update. If the alias doesn't exist, the
-            request creates it. Index alias names support date math.
-        :arg cluster_manager_timeout: Operation timeout for connection
-            to cluster-manager node.
-        :arg error_trace: Whether to include the stack trace of returned
-            errors.
-        :arg filter_path: Comma-separated list of filters used to reduce
-            the response.
-        :arg human: Whether to return human readable values for
-            statistics.
-        :arg master_timeout (Deprecated: To promote inclusive language,
-            use 'cluster_manager_timeout' instead.): Period to wait for a connection
-            to the master node. If no response is received before the timeout
-            expires, the request fails and returns an error.
-        :arg pretty: Whether to pretty format the returned JSON
-            response.
-        :arg source: The URL-encoded request definition. Useful for
-            libraries that do not accept a request body for non-POST requests.
-        :arg timeout: Period to wait for a response. If no response is
-            received before the timeout expires, the request fails and returns an
-            error.
-        """
-        return await self.transport.perform_request(
-            "PUT",
-            _make_path(index, "_alias", name),
-            params=params,
-            headers=headers,
-            body=body,
-        )
-
     @query_params(
         "allow_no_indices",
         "error_trace",
@@ -2766,3 +2709,60 @@ class IndicesClient(NamespacedClient):
             params=params,
             headers=headers,
         )
+
+    @query_params(
+        "cluster_manager_timeout",
+        "error_trace",
+        "filter_path",
+        "human",
+        "master_timeout",
+        "pretty",
+        "source",
+        "timeout",
+    )
+    async def put_alias(
+        self,
+        index: Any = None,
+        name: Any = None,
+        body: Any = None,
+        params: Any = None,
+        headers: Any = None,
+    ) -> Any:
+        """
+        Creates or updates an alias.
+
+
+        :arg body: The settings for the alias, such as `routing` or
+            `filter`
+        :arg index: Comma-separated list of data streams or indices to
+            add. Supports wildcards (`*`). Wildcard patterns that match both data
+            streams and indices return an error.
+        :arg name: Alias to update. If the alias doesn't exist, the
+            request creates it. Index alias names support date math.
+        :arg cluster_manager_timeout: Operation timeout for connection
+            to cluster-manager node.
+        :arg error_trace: Whether to include the stack trace of returned
+            errors.
+        :arg filter_path: Comma-separated list of filters used to reduce
+            the response.
+        :arg human: Whether to return human readable values for
+            statistics.
+        :arg master_timeout (Deprecated: To promote inclusive language,
+            use 'cluster_manager_timeout' instead.): Period to wait for a connection
+            to the master node. If no response is received before the timeout
+            expires, the request fails and returns an error.
+        :arg pretty: Whether to pretty format the returned JSON
+            response.
+        :arg source: The URL-encoded request definition. Useful for
+            libraries that do not accept a request body for non-POST requests.
+        :arg timeout: Period to wait for a response. If no response is
+            received before the timeout expires, the request fails and returns an
+            error.
+        """
+        return await self.transport.perform_request(
+            "PUT",
+            _make_path(index, "_alias", name),
+            params=params,
+            headers=headers,
+            body=body,
+        )
diff --git a/opensearchpy/client/indices.py b/opensearchpy/client/indices.py
index a98944f7..e4ed6fa5 100644
--- a/opensearchpy/client/indices.py
+++ b/opensearchpy/client/indices.py
@@ -852,63 +852,6 @@ class IndicesClient(NamespacedClient):
             headers=headers,
         )
 
-    @query_params(
-        "cluster_manager_timeout",
-        "error_trace",
-        "filter_path",
-        "human",
-        "master_timeout",
-        "pretty",
-        "source",
-        "timeout",
-    )
-    def put_alias(
-        self,
-        body: Any = None,
-        index: Any = None,
-        name: Any = None,
-        params: Any = None,
-        headers: Any = None,
-    ) -> Any:
-        """
-        Creates or updates an alias.
-
-
-        :arg body: The settings for the alias, such as `routing` or
-            `filter`
-        :arg index: Comma-separated list of data streams or indices to
-            add. Supports wildcards (`*`). Wildcard patterns that match both data
-            streams and indices return an error.
-        :arg name: Alias to update. If the alias doesn't exist, the
-            request creates it. Index alias names support date math.
-        :arg cluster_manager_timeout: Operation timeout for connection
-            to cluster-manager node.
-        :arg error_trace: Whether to include the stack trace of returned
-            errors.
-        :arg filter_path: Comma-separated list of filters used to reduce
-            the response.
-        :arg human: Whether to return human readable values for
-            statistics.
-        :arg master_timeout (Deprecated: To promote inclusive language,
-            use 'cluster_manager_timeout' instead.): Period to wait for a connection
-            to the master node. If no response is received before the timeout
-            expires, the request fails and returns an error.
-        :arg pretty: Whether to pretty format the returned JSON
-            response.
-        :arg source: The URL-encoded request definition. Useful for
-            libraries that do not accept a request body for non-POST requests.
-        :arg timeout: Period to wait for a response. If no response is
-            received before the timeout expires, the request fails and returns an
-            error.
-        """
-        return self.transport.perform_request(
-            "PUT",
-            _make_path(index, "_alias", name),
-            params=params,
-            headers=headers,
-            body=body,
-        )
-
     @query_params(
         "allow_no_indices",
         "error_trace",
@@ -2766,3 +2709,60 @@ class IndicesClient(NamespacedClient):
             params=params,
             headers=headers,
         )
+
+    @query_params(
+        "cluster_manager_timeout",
+        "error_trace",
+        "filter_path",
+        "human",
+        "master_timeout",
+        "pretty",
+        "source",
+        "timeout",
+    )
+    def put_alias(
+        self,
+        index: Any = None,
+        name: Any = None,
+        body: Any = None,
+        params: Any = None,
+        headers: Any = None,
+    ) -> Any:
+        """
+        Creates or updates an alias.
+
+
+        :arg body: The settings for the alias, such as `routing` or
+            `filter`
+        :arg index: Comma-separated list of data streams or indices to
+            add. Supports wildcards (`*`). Wildcard patterns that match both data
+            streams and indices return an error.
+        :arg name: Alias to update. If the alias doesn't exist, the
+            request creates it. Index alias names support date math.
+        :arg cluster_manager_timeout: Operation timeout for connection
+            to cluster-manager node.
+        :arg error_trace: Whether to include the stack trace of returned
+            errors.
+        :arg filter_path: Comma-separated list of filters used to reduce
+            the response.
+        :arg human: Whether to return human readable values for
+            statistics.
+        :arg master_timeout (Deprecated: To promote inclusive language,
+            use 'cluster_manager_timeout' instead.): Period to wait for a connection
+            to the master node. If no response is received before the timeout
+            expires, the request fails and returns an error.
+        :arg pretty: Whether to pretty format the returned JSON
+            response.
+        :arg source: The URL-encoded request definition. Useful for
+            libraries that do not accept a request body for non-POST requests.
+        :arg timeout: Period to wait for a response. If no response is
+            received before the timeout expires, the request fails and returns an
+            error.
+        """
+        return self.transport.perform_request(
+            "PUT",
+            _make_path(index, "_alias", name),
+            params=params,
+            headers=headers,
+            body=body,
+        )
diff --git a/utils/templates/base b/utils/templates/base
index 7e6be875..c6cebe60 100644
--- a/utils/templates/base
+++ b/utils/templates/base
@@ -1,6 +1,6 @@
 
     @query_params({{ api.query_params|map("tojson")|join(", ")}})   
-    async def {{ api.name }}(self, {% include "func_params" %}) -> Any:
+    async def {{ api.name }}(self, {% block func_params %}{% include "func_params" %}{% endblock %}) -> Any:
         """
         {% if api.description %}
         {{ api.description|replace("\n", " ")|wordwrap(wrapstring="\n        ") }}
diff --git a/utils/templates/overrides/indices/put_alias b/utils/templates/overrides/indices/put_alias
new file mode 100644
index 00000000..f055c6bf
--- /dev/null
+++ b/utils/templates/overrides/indices/put_alias
@@ -0,0 +1,2 @@
+{% extends "base" %}
+{% block func_params %}{% include "overrides/indices/put_alias-func_params" %}{% endblock %}
diff --git a/utils/templates/overrides/indices/put_alias-func_params b/utils/templates/overrides/indices/put_alias-func_params
new file mode 100644
index 00000000..d47aebad
--- /dev/null
+++ b/utils/templates/overrides/indices/put_alias-func_params
@@ -0,0 +1,24 @@
+{# Work around for https://github.com/opensearch-project/opensearch-py/issues/803 #}
+
+{% set first_params = ["index", "name"] %}
+
+{% for p, info in api.all_parts.items() %}
+  {% if info.required %}{{ p }}: {{ info.type }}, {% endif %}
+{% endfor %}
+
+{% for p, info in api.all_parts.items() if p in first_params %}
+  {% if not info.type == 'Any' %}{{ p }}: Optional[{{ info.type }}]=None, {% endif %}
+  {% if info.type == 'Any' %}{{ p }}: {{ info.type }}=None, {% endif %}
+{% endfor %}
+
+{% if api.body %}
+  body{% if not api.body.required %}: Any=None{% else %}: Any{% endif %},
+{% endif %}
+
+{% for p, info in api.all_parts.items() if p not in first_params %}
+  {% if not info.required and not info.type == 'Any' %}{{ p }}: Optional[{{ info.type }}]=None, {% endif %}
+  {% if not info.required and info.type == 'Any' %}{{ p }}: {{ info.type }}=None, {% endif %}
+{% endfor %}
+
+params: Any=None,
+headers: Any=None,


Test patch:
diff --git a/test_opensearchpy/test_client/test_indices.py b/test_opensearchpy/test_client/test_indices.py
index d45405e5..8705a490 100644
--- a/test_opensearchpy/test_client/test_indices.py
+++ b/test_opensearchpy/test_client/test_indices.py
@@ -45,3 +45,8 @@ class TestIndices(OpenSearchTestCase):
         self.assertRaises(ValueError, self.client.indices.exists, index=None)
         self.assertRaises(ValueError, self.client.indices.exists, index=[])
         self.assertRaises(ValueError, self.client.indices.exists, index="")
+
+    def test_create_alias(self) -> None:
+        self.client.indices.create("test-index")
+        self.client.indices.put_alias("test-index", "test-alias")
+        self.assert_url_called("PUT", "/test-index/_alias/test-alias")