2019-12-17 22:15:52 +01:00
|
|
|
#!/usr/bin/env python
|
2021-08-06 12:59:39 +05:30
|
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
#
|
|
|
|
|
# The OpenSearch Contributors require contributions made to
|
|
|
|
|
# this file be licensed under the Apache-2.0 license or a
|
|
|
|
|
# compatible open source license.
|
2023-10-26 22:55:50 -04:00
|
|
|
#
|
|
|
|
|
# Modifications Copyright OpenSearch Contributors. See
|
|
|
|
|
# GitHub history for details.
|
|
|
|
|
|
|
|
|
|
|
2021-08-06 12:59:39 +05:30
|
|
|
#
|
|
|
|
|
# Modifications Copyright OpenSearch Contributors. See
|
|
|
|
|
# GitHub history for details.
|
|
|
|
|
#
|
2023-11-21 13:04:39 -05:00
|
|
|
# Licensed to Elasticsearch b.V. under one or more contributor
|
2020-07-02 13:15:25 -05:00
|
|
|
# license agreements. See the NOTICE file distributed with
|
|
|
|
|
# this work for additional information regarding copyright
|
2023-11-21 13:04:39 -05:00
|
|
|
# ownership. Elasticsearch b.V. licenses this file to you under
|
2020-07-02 13:15:25 -05:00
|
|
|
# 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.
|
2020-04-23 11:20:33 -05:00
|
|
|
|
2023-10-26 11:31:13 -04:00
|
|
|
import json
|
2021-01-13 14:21:04 -06:00
|
|
|
import os
|
2019-12-17 22:15:52 +01:00
|
|
|
import re
|
2020-04-23 11:20:33 -05:00
|
|
|
from functools import lru_cache
|
2023-06-27 11:56:55 -07:00
|
|
|
from itertools import chain, groupby
|
|
|
|
|
from operator import itemgetter
|
2021-01-13 14:21:04 -06:00
|
|
|
from pathlib import Path
|
2023-11-09 10:51:20 -05:00
|
|
|
from typing import Any, Dict
|
2019-12-17 22:15:52 +01:00
|
|
|
|
|
|
|
|
import black
|
2023-10-26 11:31:13 -04:00
|
|
|
import deepmerge
|
2023-06-27 11:56:55 -07:00
|
|
|
import requests
|
2021-01-13 14:21:04 -06:00
|
|
|
import unasync
|
|
|
|
|
import urllib3
|
2019-12-17 22:15:52 +01:00
|
|
|
from click.testing import CliRunner
|
2021-09-09 18:41:44 +05:30
|
|
|
from jinja2 import Environment, FileSystemLoader, TemplateNotFound, select_autoescape
|
2019-12-17 22:15:52 +01:00
|
|
|
|
2020-04-23 11:20:33 -05:00
|
|
|
http = urllib3.PoolManager()
|
|
|
|
|
|
2019-12-17 22:15:52 +01:00
|
|
|
# line to look for in the original source file
|
|
|
|
|
SEPARATOR = " # AUTO-GENERATED-API-DEFINITIONS #"
|
|
|
|
|
# global substitutions for python keywords
|
|
|
|
|
SUBSTITUTIONS = {"type": "doc_type", "from": "from_"}
|
2023-06-27 11:56:55 -07:00
|
|
|
|
|
|
|
|
|
2019-12-17 22:15:52 +01:00
|
|
|
# api path(s)
|
2020-04-23 11:20:33 -05:00
|
|
|
BRANCH_NAME = "7.x"
|
2019-12-22 14:47:42 +01:00
|
|
|
CODE_ROOT = Path(__file__).absolute().parent.parent
|
2020-08-31 11:07:24 -05:00
|
|
|
GLOBAL_QUERY_PARAMS = {
|
|
|
|
|
"pretty": "Optional[bool]",
|
|
|
|
|
"human": "Optional[bool]",
|
|
|
|
|
"error_trace": "Optional[bool]",
|
|
|
|
|
"format": "Optional[str]",
|
|
|
|
|
"filter_path": "Optional[Union[str, Collection[str]]]",
|
|
|
|
|
"request_timeout": "Optional[Union[int, float]]",
|
|
|
|
|
"ignore": "Optional[Union[int, Collection[int]]]",
|
|
|
|
|
"opaque_id": "Optional[str]",
|
2021-04-19 16:56:25 -05:00
|
|
|
"http_auth": "Optional[Union[str, Tuple[str, str]]]",
|
|
|
|
|
"api_key": "Optional[Union[str, Tuple[str, str]]]",
|
2020-08-31 11:07:24 -05:00
|
|
|
}
|
2019-12-17 22:15:52 +01:00
|
|
|
|
|
|
|
|
jinja_env = Environment(
|
2021-09-09 18:41:44 +05:30
|
|
|
autoescape=select_autoescape(["html", "xml"]),
|
2019-12-22 15:15:00 +01:00
|
|
|
loader=FileSystemLoader([CODE_ROOT / "utils" / "templates"]),
|
2019-12-17 22:15:52 +01:00
|
|
|
trim_blocks=True,
|
|
|
|
|
lstrip_blocks=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-11-09 10:51:20 -05:00
|
|
|
def blacken(filename: Any) -> None:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
runs 'black' https://pypi.org/project/black/ on the given file
|
|
|
|
|
:param filename: file to reformant
|
|
|
|
|
"""
|
2019-12-17 22:15:52 +01:00
|
|
|
runner = CliRunner()
|
|
|
|
|
result = runner.invoke(black.main, [str(filename)])
|
|
|
|
|
assert result.exit_code == 0, result.output
|
|
|
|
|
|
|
|
|
|
|
2020-04-23 11:20:33 -05:00
|
|
|
@lru_cache()
|
2023-11-09 10:51:20 -05:00
|
|
|
def is_valid_url(url: str) -> bool:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
makes a call to the url
|
|
|
|
|
:param url: url to check
|
|
|
|
|
:return: True if status code is between HTTP 200 inclusive and 400 exclusive; False otherwise
|
|
|
|
|
"""
|
2020-08-31 11:07:24 -05:00
|
|
|
return 200 <= http.request("HEAD", url).status < 400
|
2020-04-23 11:20:33 -05:00
|
|
|
|
|
|
|
|
|
2019-12-17 22:15:52 +01:00
|
|
|
class Module:
|
2023-11-09 10:51:20 -05:00
|
|
|
def __init__(self, namespace: str) -> None:
|
|
|
|
|
self.namespace: Any = namespace
|
|
|
|
|
self._apis: Any = []
|
2019-12-17 22:15:52 +01:00
|
|
|
self.parse_orig()
|
|
|
|
|
|
2023-11-09 10:51:20 -05:00
|
|
|
def add(self, api: Any) -> None:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
add an API to the list of modules
|
|
|
|
|
:param api: an API object
|
|
|
|
|
"""
|
2019-12-17 22:15:52 +01:00
|
|
|
self._apis.append(api)
|
|
|
|
|
|
2023-11-09 10:51:20 -05:00
|
|
|
def parse_orig(self) -> None:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
reads the written module and updates with important code specific to this client
|
|
|
|
|
"""
|
2019-12-17 22:15:52 +01:00
|
|
|
self.orders = []
|
2023-11-06 13:08:19 -05:00
|
|
|
self.header = "from typing import Any, Collection, Optional, Tuple, Union\n\n"
|
2023-08-02 15:00:20 -07:00
|
|
|
|
|
|
|
|
namespace_new = "".join(word.capitalize() for word in self.namespace.split("_"))
|
2023-11-06 13:08:19 -05:00
|
|
|
self.header += "class " + namespace_new + "Client(NamespacedClient):"
|
2020-05-15 09:36:47 -05:00
|
|
|
if os.path.exists(self.filepath):
|
2024-01-19 13:36:05 -05:00
|
|
|
with open(self.filepath, encoding="utf-8") as file:
|
|
|
|
|
content = file.read()
|
2019-12-17 22:15:52 +01:00
|
|
|
header_lines = []
|
|
|
|
|
for line in content.split("\n"):
|
|
|
|
|
header_lines.append(line)
|
|
|
|
|
if line == SEPARATOR:
|
|
|
|
|
break
|
|
|
|
|
# no separator found
|
|
|
|
|
else:
|
|
|
|
|
header_lines = []
|
|
|
|
|
for line in content.split("\n"):
|
|
|
|
|
header_lines.append(line)
|
2019-12-22 15:15:00 +01:00
|
|
|
if line.startswith("class"):
|
2023-11-06 13:08:19 -05:00
|
|
|
if "security.py" in str(self.filepath):
|
|
|
|
|
# TODO: FIXME, import code
|
2023-09-26 09:46:18 -07:00
|
|
|
header_lines.append(
|
2024-01-19 13:36:05 -05:00
|
|
|
" from ._patch import health_check, update_audit_config # type: ignore" # pylint: disable=line-too-long
|
2023-09-26 09:46:18 -07:00
|
|
|
)
|
2019-12-17 22:15:52 +01:00
|
|
|
break
|
|
|
|
|
self.header = "\n".join(header_lines)
|
2020-03-13 13:44:46 -05:00
|
|
|
self.orders = re.findall(
|
2020-06-24 14:25:28 -05:00
|
|
|
r"\n (?:async )?def ([a-z_]+)\(", content, re.MULTILINE
|
2019-12-17 22:15:52 +01:00
|
|
|
)
|
|
|
|
|
|
2023-11-09 10:51:20 -05:00
|
|
|
def _position(self, api: Any) -> Any:
|
2019-12-17 22:15:52 +01:00
|
|
|
try:
|
|
|
|
|
return self.orders.index(api.name)
|
|
|
|
|
except ValueError:
|
|
|
|
|
return len(self.orders)
|
|
|
|
|
|
2023-11-06 13:08:19 -05:00
|
|
|
def sort(self) -> None:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
sorts the list of APIs by the Module._position key
|
|
|
|
|
"""
|
2019-12-17 22:15:52 +01:00
|
|
|
self._apis.sort(key=self._position)
|
|
|
|
|
|
2023-11-06 13:08:19 -05:00
|
|
|
def dump(self) -> None:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
writes the module out to disk
|
|
|
|
|
"""
|
2019-12-17 22:15:52 +01:00
|
|
|
self.sort()
|
2023-08-02 15:00:20 -07:00
|
|
|
|
2024-01-19 13:36:05 -05:00
|
|
|
# This code snippet adds headers to each generated module indicating
|
|
|
|
|
# that the code is generated.The separator is the last line in the
|
|
|
|
|
# "THIS CODE IS AUTOMATICALLY GENERATED" header.
|
|
|
|
|
header_separator = "# -----------------------------------------------------------------------------------------+" # pylint: disable=line-too-long
|
2023-11-21 13:04:39 -05:00
|
|
|
license_header_end_1 = "# GitHub history for details."
|
|
|
|
|
license_header_end_2 = "# under the License."
|
2023-08-02 15:00:20 -07:00
|
|
|
|
|
|
|
|
update_header = True
|
2023-11-21 13:04:39 -05:00
|
|
|
license_position = 0
|
2023-08-02 15:00:20 -07:00
|
|
|
|
2023-11-21 13:04:39 -05:00
|
|
|
# Identifying the insertion point for the "THIS CODE IS AUTOMATICALLY GENERATED" header.
|
2023-08-02 15:00:20 -07:00
|
|
|
if os.path.exists(self.filepath):
|
2024-01-19 13:36:05 -05:00
|
|
|
with open(self.filepath, "r", encoding="utf-8") as file:
|
|
|
|
|
content = file.read()
|
2023-08-02 15:00:20 -07:00
|
|
|
if header_separator in content:
|
|
|
|
|
update_header = False
|
|
|
|
|
header_end_position = (
|
|
|
|
|
content.find(header_separator) + len(header_separator) + 2
|
|
|
|
|
)
|
|
|
|
|
header_position = content.rfind("\n", 0, header_end_position) + 1
|
2023-11-21 13:04:39 -05:00
|
|
|
if license_header_end_1 in content:
|
|
|
|
|
if license_header_end_2 in content:
|
2023-08-02 15:00:20 -07:00
|
|
|
position = (
|
2023-11-21 13:04:39 -05:00
|
|
|
content.find(license_header_end_2)
|
|
|
|
|
+ len(license_header_end_2)
|
2023-08-02 15:00:20 -07:00
|
|
|
+ 2
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
position = (
|
2023-11-21 13:04:39 -05:00
|
|
|
content.find(license_header_end_1)
|
|
|
|
|
+ len(license_header_end_1)
|
2023-08-02 15:00:20 -07:00
|
|
|
+ 2
|
|
|
|
|
)
|
2023-11-21 13:04:39 -05:00
|
|
|
license_position = content.rfind("\n", 0, position) + 1
|
2023-08-02 15:00:20 -07:00
|
|
|
|
|
|
|
|
current_script_folder = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
generated_file_header_path = os.path.join(
|
|
|
|
|
current_script_folder, "generated_file_headers.txt"
|
|
|
|
|
)
|
2024-01-19 13:36:05 -05:00
|
|
|
with open(generated_file_header_path, "r", encoding="utf-8") as header_file:
|
2023-08-02 15:00:20 -07:00
|
|
|
header_content = header_file.read()
|
|
|
|
|
|
2024-01-19 13:36:05 -05:00
|
|
|
# Imports are temporarily removed from the header and are regenerated
|
|
|
|
|
# later to ensure imports are updated after code generation.
|
2023-08-02 15:00:20 -07:00
|
|
|
self.header = "\n".join(
|
|
|
|
|
line for line in self.header.split("\n") if "from .utils import" not in line
|
|
|
|
|
)
|
|
|
|
|
|
2024-01-19 13:36:05 -05:00
|
|
|
with open(self.filepath, "w", encoding="utf-8") as file:
|
2023-08-02 15:00:20 -07:00
|
|
|
if update_header is True:
|
2024-01-19 13:36:05 -05:00
|
|
|
file.write(
|
2023-11-21 13:04:39 -05:00
|
|
|
self.header[:license_position]
|
2023-08-02 15:00:20 -07:00
|
|
|
+ "\n"
|
|
|
|
|
+ header_content
|
|
|
|
|
+ "\n\n"
|
|
|
|
|
+ "#replace_token#\n"
|
2023-11-21 13:04:39 -05:00
|
|
|
+ self.header[license_position:]
|
2023-08-02 15:00:20 -07:00
|
|
|
)
|
|
|
|
|
else:
|
2024-01-19 13:36:05 -05:00
|
|
|
file.write(
|
2023-08-02 15:00:20 -07:00
|
|
|
self.header[:header_position]
|
|
|
|
|
+ "\n"
|
|
|
|
|
+ "#replace_token#\n"
|
|
|
|
|
+ self.header[header_position:]
|
|
|
|
|
)
|
2019-12-17 22:15:52 +01:00
|
|
|
for api in self._apis:
|
2024-01-19 13:36:05 -05:00
|
|
|
file.write(api.to_python())
|
2020-08-31 11:07:24 -05:00
|
|
|
|
2023-08-02 15:00:20 -07:00
|
|
|
# Generating imports for each module
|
|
|
|
|
utils_imports = ""
|
|
|
|
|
file_content = ""
|
2024-01-19 13:36:05 -05:00
|
|
|
with open(self.filepath, "r", encoding="utf-8") as file:
|
|
|
|
|
content = file.read()
|
2023-08-02 15:00:20 -07:00
|
|
|
keywords = [
|
|
|
|
|
"SKIP_IN_PATH",
|
|
|
|
|
"_normalize_hosts",
|
|
|
|
|
"_escape",
|
|
|
|
|
"_make_path",
|
|
|
|
|
"query_params",
|
|
|
|
|
"_bulk_body",
|
|
|
|
|
"_base64_auth_header",
|
|
|
|
|
"NamespacedClient",
|
|
|
|
|
"AddonClient",
|
|
|
|
|
]
|
|
|
|
|
present_keywords = [keyword for keyword in keywords if keyword in content]
|
|
|
|
|
|
|
|
|
|
if present_keywords:
|
|
|
|
|
utils_imports = "from .utils import"
|
|
|
|
|
result = f"{utils_imports} {', '.join(present_keywords)}"
|
|
|
|
|
utils_imports = result
|
|
|
|
|
file_content = content.replace("#replace_token#", utils_imports)
|
|
|
|
|
|
2024-01-19 13:36:05 -05:00
|
|
|
with open(self.filepath, "w", encoding="utf-8") as file:
|
|
|
|
|
file.write(file_content)
|
2023-08-02 15:00:20 -07:00
|
|
|
|
2020-05-15 09:36:47 -05:00
|
|
|
@property
|
2023-11-09 10:51:20 -05:00
|
|
|
def filepath(self) -> Any:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
:return: absolute path to the module
|
|
|
|
|
"""
|
2023-11-06 13:08:19 -05:00
|
|
|
return CODE_ROOT / f"opensearchpy/_async/client/{self.namespace}.py"
|
2019-12-17 22:15:52 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class API:
|
2023-11-09 10:51:20 -05:00
|
|
|
def __init__(self, namespace: str, name: str, definition: Any) -> None:
|
2019-12-17 22:15:52 +01:00
|
|
|
self.namespace = namespace
|
|
|
|
|
self.name = name
|
|
|
|
|
|
|
|
|
|
# overwrite the dict to maintain key order
|
|
|
|
|
definition["params"] = {
|
|
|
|
|
SUBSTITUTIONS.get(p, p): v for p, v in definition.get("params", {}).items()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self._def = definition
|
|
|
|
|
self.description = ""
|
|
|
|
|
self.doc_url = ""
|
2020-10-28 13:49:05 -05:00
|
|
|
self.stability = self._def.get("stability", "stable")
|
2023-10-09 13:47:26 -07:00
|
|
|
self.deprecation_message = self._def.get("deprecation_message")
|
2020-10-28 13:49:05 -05:00
|
|
|
|
2019-12-17 22:15:52 +01:00
|
|
|
if isinstance(definition["documentation"], str):
|
|
|
|
|
self.doc_url = definition["documentation"]
|
|
|
|
|
else:
|
2021-05-25 14:44:41 +02:00
|
|
|
# set as attribute so it may be overridden by Module.add
|
2020-07-29 15:51:19 -05:00
|
|
|
self.description = (
|
|
|
|
|
definition["documentation"].get("description", "").strip()
|
|
|
|
|
)
|
2019-12-17 22:15:52 +01:00
|
|
|
self.doc_url = definition["documentation"].get("url", "")
|
|
|
|
|
|
2020-03-13 13:44:46 -05:00
|
|
|
# Filter out bad URL refs like 'TODO'
|
|
|
|
|
# and serve all docs over HTTPS.
|
|
|
|
|
if self.doc_url:
|
|
|
|
|
if not self.doc_url.startswith("http"):
|
|
|
|
|
self.doc_url = ""
|
|
|
|
|
if self.doc_url.startswith("http://"):
|
|
|
|
|
self.doc_url = self.doc_url.replace("http://", "https://")
|
|
|
|
|
|
2020-04-23 11:20:33 -05:00
|
|
|
# Try setting doc refs like 'current' and 'master' to our branches ref.
|
|
|
|
|
if BRANCH_NAME is not None:
|
2020-06-24 14:25:28 -05:00
|
|
|
revised_url = re.sub(
|
2021-09-16 14:59:29 +05:30
|
|
|
"/opensearchpy/reference/[^/]+/",
|
|
|
|
|
f"/opensearchpy/reference/{BRANCH_NAME}/",
|
2020-06-24 14:25:28 -05:00
|
|
|
self.doc_url,
|
|
|
|
|
)
|
2020-04-23 11:20:33 -05:00
|
|
|
if is_valid_url(revised_url):
|
|
|
|
|
self.doc_url = revised_url
|
|
|
|
|
else:
|
|
|
|
|
print(f"URL {revised_url!r}, falling back on {self.doc_url!r}")
|
|
|
|
|
|
2019-12-22 15:22:19 +01:00
|
|
|
@property
|
2023-11-09 10:51:20 -05:00
|
|
|
def all_parts(self) -> Dict[str, str]:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
updates the url parts from the specification
|
|
|
|
|
:return: dict of updated parts
|
|
|
|
|
"""
|
2019-12-17 22:15:52 +01:00
|
|
|
parts = {}
|
|
|
|
|
for url in self._def["url"]["paths"]:
|
|
|
|
|
parts.update(url.get("parts", {}))
|
|
|
|
|
|
2024-01-19 13:36:05 -05:00
|
|
|
for part in parts:
|
|
|
|
|
if "required" not in parts[part]:
|
|
|
|
|
parts[part]["required"] = all(
|
|
|
|
|
part in url.get("parts", {}) for url in self._def["url"]["paths"]
|
2023-10-26 07:58:33 -07:00
|
|
|
)
|
2024-01-19 13:36:05 -05:00
|
|
|
parts[part]["type"] = "Any"
|
2019-12-17 22:15:52 +01:00
|
|
|
|
2020-03-13 16:20:54 -05:00
|
|
|
# This piece of logic corresponds to calling
|
|
|
|
|
# client.tasks.get() w/o a task_id which was erroneously
|
|
|
|
|
# allowed in the 7.1 client library. This functionality
|
|
|
|
|
# is deprecated and will be removed in 8.x.
|
|
|
|
|
if self.namespace == "tasks" and self.name == "get":
|
|
|
|
|
parts["task_id"]["required"] = False
|
|
|
|
|
|
2019-12-17 22:15:52 +01:00
|
|
|
for k, sub in SUBSTITUTIONS.items():
|
|
|
|
|
if k in parts:
|
|
|
|
|
parts[sub] = parts.pop(k)
|
|
|
|
|
|
2024-01-25 18:17:09 -05:00
|
|
|
_, components = self.url_parts
|
2019-12-17 22:15:52 +01:00
|
|
|
|
2023-11-09 10:51:20 -05:00
|
|
|
def ind(item: Any) -> Any:
|
2019-12-17 22:15:52 +01:00
|
|
|
try:
|
|
|
|
|
return components.index(item[0])
|
|
|
|
|
except ValueError:
|
|
|
|
|
return len(components)
|
|
|
|
|
|
|
|
|
|
parts = dict(sorted(parts.items(), key=ind))
|
|
|
|
|
return parts
|
|
|
|
|
|
|
|
|
|
@property
|
2023-11-09 10:51:20 -05:00
|
|
|
def params(self) -> Any:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
:return: itertools.chain of required parts of the API
|
|
|
|
|
"""
|
2019-12-22 15:22:19 +01:00
|
|
|
parts = self.all_parts
|
2020-03-20 14:21:11 +01:00
|
|
|
params = self._def.get("params", {})
|
2019-12-17 22:15:52 +01:00
|
|
|
return chain(
|
2023-11-09 10:51:20 -05:00
|
|
|
((p, parts[p]) for p in parts if parts[p]["required"]), # type: ignore
|
2020-03-20 14:21:11 +01:00
|
|
|
(("body", self.body),) if self.body else (),
|
2020-06-24 14:25:28 -05:00
|
|
|
(
|
|
|
|
|
(p, parts[p])
|
|
|
|
|
for p in parts
|
2023-11-09 10:51:20 -05:00
|
|
|
if not parts[p]["required"] and p not in params # type: ignore
|
2020-06-24 14:25:28 -05:00
|
|
|
),
|
2020-03-20 14:21:11 +01:00
|
|
|
sorted(params.items(), key=lambda x: (x[0] not in parts, x[0])),
|
2019-12-17 22:15:52 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@property
|
2023-11-09 10:51:20 -05:00
|
|
|
def body(self) -> Any:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
:return: body of the API spec
|
|
|
|
|
"""
|
|
|
|
|
body_api_spec = self._def.get("body", {})
|
|
|
|
|
if body_api_spec:
|
|
|
|
|
body_api_spec.setdefault("required", False)
|
|
|
|
|
return body_api_spec
|
2019-12-17 22:15:52 +01:00
|
|
|
|
|
|
|
|
@property
|
2023-11-09 10:51:20 -05:00
|
|
|
def query_params(self) -> Any:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
:return: any query string parameters from the specification
|
|
|
|
|
"""
|
2019-12-22 15:15:00 +01:00
|
|
|
return (
|
2024-01-19 13:36:05 -05:00
|
|
|
key
|
|
|
|
|
for key in sorted(self._def.get("params", {}).keys())
|
|
|
|
|
if key not in self.all_parts
|
2019-12-22 15:15:00 +01:00
|
|
|
)
|
2019-12-17 22:15:52 +01:00
|
|
|
|
2020-08-31 11:07:24 -05:00
|
|
|
@property
|
2023-11-09 10:51:20 -05:00
|
|
|
def all_func_params(self) -> Any:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
Parameters that will be in the '@query_params' decorator list
|
2020-08-31 11:07:24 -05:00
|
|
|
and parameters that will be in the function signature.
|
|
|
|
|
"""
|
|
|
|
|
params = list(self._def.get("params", {}).keys())
|
|
|
|
|
for url in self._def["url"]["paths"]:
|
|
|
|
|
params.extend(url.get("parts", {}).keys())
|
|
|
|
|
if self.body:
|
|
|
|
|
params.append("body")
|
|
|
|
|
return params
|
|
|
|
|
|
2019-12-17 22:15:52 +01:00
|
|
|
@property
|
2023-11-09 10:51:20 -05:00
|
|
|
def path(self) -> Any:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
:return: the first lexically ordered path in url.paths
|
|
|
|
|
"""
|
2019-12-17 22:15:52 +01:00
|
|
|
return max(
|
|
|
|
|
(path for path in self._def["url"]["paths"]),
|
|
|
|
|
key=lambda p: len(re.findall(r"\{([^}]+)\}", p["path"])),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@property
|
2023-11-09 10:51:20 -05:00
|
|
|
def method(self) -> Any:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
To adhere to the HTTP RFC we shouldn't send
|
|
|
|
|
bodies in GET requests.
|
|
|
|
|
:return: an updated HTTP method to use to communicate with the OpenSearch API
|
|
|
|
|
"""
|
|
|
|
|
|
2020-03-13 13:44:46 -05:00
|
|
|
default_method = self.path["methods"][0]
|
2023-09-26 09:46:18 -07:00
|
|
|
if self.name == "refresh" or self.name == "flush":
|
|
|
|
|
return "POST"
|
2020-03-13 13:44:46 -05:00
|
|
|
if self.body and default_method == "GET" and "POST" in self.path["methods"]:
|
|
|
|
|
return "POST"
|
2023-09-26 09:46:18 -07:00
|
|
|
if "POST" and "PUT" in self.path["methods"] and self.name != "bulk":
|
|
|
|
|
return "PUT"
|
2020-03-13 13:44:46 -05:00
|
|
|
return default_method
|
2019-12-17 22:15:52 +01:00
|
|
|
|
|
|
|
|
@property
|
2023-11-09 10:51:20 -05:00
|
|
|
def url_parts(self) -> Any:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
:return tuple of boolean (if the path is dynamic), list of url parts
|
|
|
|
|
"""
|
2019-12-17 22:15:52 +01:00
|
|
|
path = self.path["path"]
|
|
|
|
|
|
|
|
|
|
dynamic = "{" in path
|
|
|
|
|
if not dynamic:
|
|
|
|
|
return dynamic, path
|
|
|
|
|
|
|
|
|
|
parts = []
|
|
|
|
|
for part in path.split("/"):
|
|
|
|
|
if not part:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if part[0] == "{":
|
|
|
|
|
part = part[1:-1]
|
|
|
|
|
parts.append(SUBSTITUTIONS.get(part, part))
|
|
|
|
|
else:
|
|
|
|
|
parts.append(f"'{part}'")
|
|
|
|
|
|
|
|
|
|
return dynamic, parts
|
|
|
|
|
|
|
|
|
|
@property
|
2023-11-09 10:51:20 -05:00
|
|
|
def required_parts(self) -> Any:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
:return: list of parts of the url that are required plus the body
|
|
|
|
|
"""
|
2019-12-22 15:22:19 +01:00
|
|
|
parts = self.all_parts
|
2023-11-09 10:51:20 -05:00
|
|
|
required = [p for p in parts if parts[p]["required"]] # type: ignore
|
2019-12-22 15:35:00 +01:00
|
|
|
if self.body.get("required"):
|
|
|
|
|
required.append("body")
|
|
|
|
|
return required
|
2019-12-17 22:15:52 +01:00
|
|
|
|
2023-11-09 10:51:20 -05:00
|
|
|
def to_python(self) -> Any:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
:return: rendered Jinja template
|
|
|
|
|
"""
|
2023-11-06 13:08:19 -05:00
|
|
|
try:
|
2024-01-19 13:36:05 -05:00
|
|
|
template = jinja_env.get_template(f"overrides/{self.namespace}/{self.name}")
|
2023-11-06 13:08:19 -05:00
|
|
|
except TemplateNotFound:
|
2024-01-19 13:36:05 -05:00
|
|
|
template = jinja_env.get_template("base")
|
2020-08-31 11:07:24 -05:00
|
|
|
|
2024-01-19 13:36:05 -05:00
|
|
|
return template.render(
|
2020-08-31 11:07:24 -05:00
|
|
|
api=self,
|
|
|
|
|
substitutions={v: k for k, v in SUBSTITUTIONS.items()},
|
|
|
|
|
global_query_params=GLOBAL_QUERY_PARAMS,
|
2019-12-17 22:15:52 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-11-09 10:51:20 -05:00
|
|
|
def read_modules() -> Any:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
checks the opensearch-api spec at
|
|
|
|
|
https://raw.githubusercontent.com/opensearch-project/opensearch-api-specification/main/OpenSearch.openapi.json
|
|
|
|
|
and parses it into one or more API modules
|
|
|
|
|
:return: a dict of API objects
|
|
|
|
|
"""
|
2023-06-27 11:56:55 -07:00
|
|
|
modules = {}
|
|
|
|
|
|
|
|
|
|
# Load the OpenAPI specification file
|
|
|
|
|
response = requests.get(
|
2024-01-19 13:36:05 -05:00
|
|
|
"https://raw.githubusercontent.com/opensearch-project/opensearch-api-"
|
|
|
|
|
"specification/main/OpenSearch.openapi.json"
|
2021-04-19 14:39:21 -05:00
|
|
|
)
|
2023-06-27 11:56:55 -07:00
|
|
|
data = response.json()
|
|
|
|
|
|
|
|
|
|
list_of_dicts = []
|
|
|
|
|
|
|
|
|
|
for path in data["paths"]:
|
2024-01-19 13:36:05 -05:00
|
|
|
for param in data["paths"][path]: # pylint: disable=invalid-name
|
|
|
|
|
if data["paths"][path][param]["x-operation-group"] == "nodes.hot_threads":
|
|
|
|
|
if "deprecated" in data["paths"][path][param]:
|
2023-09-28 20:18:53 -07:00
|
|
|
continue
|
2024-01-19 13:36:05 -05:00
|
|
|
data["paths"][path][param].update({"path": path, "method": param})
|
|
|
|
|
list_of_dicts.append(data["paths"][path][param])
|
2023-06-27 11:56:55 -07:00
|
|
|
|
|
|
|
|
# Update parameters in each endpoint
|
2024-01-19 13:36:05 -05:00
|
|
|
for param_dict in list_of_dicts:
|
|
|
|
|
if "parameters" in param_dict:
|
2023-06-27 11:56:55 -07:00
|
|
|
params = []
|
|
|
|
|
parts = []
|
|
|
|
|
|
|
|
|
|
# Iterate over the list of parameters and update them
|
2024-01-19 13:36:05 -05:00
|
|
|
for param in param_dict["parameters"]:
|
|
|
|
|
if "schema" in param and "$ref" in param["schema"]:
|
|
|
|
|
schema_path_ref = param["schema"]["$ref"].split("/")[-1]
|
|
|
|
|
param["schema"] = data["components"]["schemas"][schema_path_ref]
|
|
|
|
|
params.append(param)
|
2023-06-27 11:56:55 -07:00
|
|
|
else:
|
2024-01-19 13:36:05 -05:00
|
|
|
params.append(param)
|
2021-04-19 14:39:21 -05:00
|
|
|
|
2023-06-27 11:56:55 -07:00
|
|
|
# Iterate over the list of updated parameters to separate "parts" from "params"
|
2024-01-19 13:36:05 -05:00
|
|
|
params_copy = params.copy()
|
|
|
|
|
for param in params_copy:
|
|
|
|
|
if param["in"] == "path":
|
|
|
|
|
parts.append(param)
|
|
|
|
|
params.remove(param)
|
2021-04-19 14:39:21 -05:00
|
|
|
|
2023-06-27 11:56:55 -07:00
|
|
|
# Convert "params" and "parts" into the structure required for generator.
|
|
|
|
|
params_new = {}
|
|
|
|
|
parts_new = {}
|
2021-04-19 14:39:21 -05:00
|
|
|
|
2024-01-19 13:36:05 -05:00
|
|
|
for m in params: # pylint: disable=invalid-name
|
|
|
|
|
a = dict( # pylint: disable=invalid-name
|
|
|
|
|
type=m["schema"]["type"], description=m["description"]
|
|
|
|
|
) # pylint: disable=invalid-name
|
2023-10-03 07:43:05 -07:00
|
|
|
|
|
|
|
|
if "default" in m["schema"]:
|
2023-11-21 13:04:39 -05:00
|
|
|
a.update({"default": m["schema"]["default"]})
|
2023-10-03 07:43:05 -07:00
|
|
|
|
2023-06-27 11:56:55 -07:00
|
|
|
if "enum" in m["schema"]:
|
2023-11-21 13:04:39 -05:00
|
|
|
a.update({"type": "enum"})
|
|
|
|
|
a.update({"options": m["schema"]["enum"]})
|
2021-04-19 14:39:21 -05:00
|
|
|
|
2023-09-28 20:18:53 -07:00
|
|
|
if "deprecated" in m["schema"]:
|
2023-11-21 13:04:39 -05:00
|
|
|
a.update({"deprecated": m["schema"]["deprecated"]})
|
|
|
|
|
a.update(
|
2023-09-28 20:18:53 -07:00
|
|
|
{"deprecation_message": m["schema"]["x-deprecation-message"]}
|
|
|
|
|
)
|
2023-11-21 13:04:39 -05:00
|
|
|
params_new.update({m["name"]: a})
|
2021-04-19 14:39:21 -05:00
|
|
|
|
2023-06-27 11:56:55 -07:00
|
|
|
# Removing the deprecated "type"
|
2024-01-19 13:36:05 -05:00
|
|
|
if (
|
|
|
|
|
param_dict["x-operation-group"] != "nodes.hot_threads"
|
|
|
|
|
and "type" in params_new
|
|
|
|
|
):
|
2023-06-27 11:56:55 -07:00
|
|
|
params_new.pop("type")
|
2019-12-17 22:15:52 +01:00
|
|
|
|
2023-10-09 14:57:09 -07:00
|
|
|
if (
|
2024-01-19 13:36:05 -05:00
|
|
|
param_dict["x-operation-group"] == "cluster.health"
|
2023-10-09 14:57:09 -07:00
|
|
|
and "ensure_node_commissioned" in params_new
|
|
|
|
|
):
|
|
|
|
|
params_new.pop("ensure_node_commissioned")
|
|
|
|
|
|
2023-06-27 11:56:55 -07:00
|
|
|
if bool(params_new):
|
2024-01-19 13:36:05 -05:00
|
|
|
param_dict.update({"params": params_new})
|
2019-12-17 22:15:52 +01:00
|
|
|
|
2024-01-19 13:36:05 -05:00
|
|
|
param_dict.pop("parameters")
|
2023-06-27 11:56:55 -07:00
|
|
|
|
2024-01-19 13:36:05 -05:00
|
|
|
for n in parts: # pylint: disable=invalid-name
|
|
|
|
|
b = dict(type=n["schema"]["type"]) # pylint: disable=invalid-name
|
2023-06-27 11:56:55 -07:00
|
|
|
|
|
|
|
|
if "description" in n:
|
2023-11-21 13:04:39 -05:00
|
|
|
b.update({"description": n["description"]})
|
2023-06-27 11:56:55 -07:00
|
|
|
|
2023-10-03 07:43:05 -07:00
|
|
|
if "x-enum-options" in n["schema"]:
|
2023-11-21 13:04:39 -05:00
|
|
|
b.update({"options": n["schema"]["x-enum-options"]})
|
2023-10-03 07:43:05 -07:00
|
|
|
|
2023-06-27 11:56:55 -07:00
|
|
|
deprecated_new = {}
|
|
|
|
|
if "deprecated" in n:
|
2023-11-21 13:04:39 -05:00
|
|
|
b.update({"deprecated": n["deprecated"]})
|
2023-06-27 11:56:55 -07:00
|
|
|
|
|
|
|
|
if "x-deprecation-version" in n:
|
|
|
|
|
deprecated_new.update({"version": n["x-deprecation-version"]})
|
2019-12-17 22:15:52 +01:00
|
|
|
|
2023-06-27 11:56:55 -07:00
|
|
|
if "x-deprecation-description" in n:
|
|
|
|
|
deprecated_new.update(
|
|
|
|
|
{"description": n["x-deprecation-description"]}
|
|
|
|
|
)
|
2019-12-17 22:15:52 +01:00
|
|
|
|
2023-11-21 13:04:39 -05:00
|
|
|
parts_new.update({n["name"]: b})
|
2023-06-27 11:56:55 -07:00
|
|
|
|
|
|
|
|
if bool(parts_new):
|
2024-01-19 13:36:05 -05:00
|
|
|
param_dict.update({"parts": parts_new})
|
2023-06-27 11:56:55 -07:00
|
|
|
|
|
|
|
|
# Sort the input list by the value of the "x-operation-group" key
|
|
|
|
|
list_of_dicts = sorted(list_of_dicts, key=itemgetter("x-operation-group"))
|
|
|
|
|
|
|
|
|
|
# Group the input list by the value of the "x-operation-group" key
|
|
|
|
|
for key, value in groupby(list_of_dicts, key=itemgetter("x-operation-group")):
|
|
|
|
|
api = {}
|
|
|
|
|
|
|
|
|
|
# Extract the namespace and name from the 'x-operation-group'
|
|
|
|
|
if "." in key:
|
|
|
|
|
namespace, name = key.rsplit(".", 1)
|
|
|
|
|
else:
|
2019-12-17 22:15:52 +01:00
|
|
|
namespace = "__init__"
|
2023-06-27 11:56:55 -07:00
|
|
|
name = key
|
|
|
|
|
|
|
|
|
|
# Group the data in the current group by the "path" key
|
|
|
|
|
paths = []
|
2023-10-26 07:58:33 -07:00
|
|
|
all_paths_have_deprecation = True
|
|
|
|
|
|
2023-06-27 11:56:55 -07:00
|
|
|
for key2, value2 in groupby(value, key=itemgetter("path")):
|
|
|
|
|
# Extract the HTTP methods from the data in the current subgroup
|
|
|
|
|
methods = []
|
|
|
|
|
parts_final = {}
|
2024-01-19 13:36:05 -05:00
|
|
|
for z in value2: # pylint: disable=invalid-name
|
2023-06-27 11:56:55 -07:00
|
|
|
methods.append(z["method"].upper())
|
|
|
|
|
|
|
|
|
|
# Update 'api' dictionary
|
|
|
|
|
if "documentation" not in api:
|
|
|
|
|
documentation = {"description": z["description"]}
|
|
|
|
|
api.update({"documentation": documentation})
|
|
|
|
|
|
2023-10-26 07:58:33 -07:00
|
|
|
if "x-deprecation-message" in z:
|
|
|
|
|
x_deprecation_message = z["x-deprecation-message"]
|
|
|
|
|
else:
|
|
|
|
|
all_paths_have_deprecation = False
|
2023-10-09 13:47:26 -07:00
|
|
|
|
2023-06-27 11:56:55 -07:00
|
|
|
if "params" not in api and "params" in z:
|
|
|
|
|
api.update({"params": z["params"]})
|
|
|
|
|
|
|
|
|
|
if "body" not in api and "requestBody" in z:
|
|
|
|
|
body = {"required": False}
|
|
|
|
|
if "required" in z["requestBody"]:
|
|
|
|
|
body.update({"required": z["requestBody"]["required"]})
|
2024-01-19 13:36:05 -05:00
|
|
|
q = z["requestBody"]["content"][ # pylint: disable=invalid-name
|
|
|
|
|
"application/json"
|
|
|
|
|
]["schema"]["$ref"].split("/")[-1]
|
2023-08-02 15:00:20 -07:00
|
|
|
if "description" in data["components"]["schemas"][q]:
|
|
|
|
|
body.update(
|
|
|
|
|
{
|
|
|
|
|
"description": data["components"]["schemas"][q][
|
|
|
|
|
"description"
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
)
|
2023-06-27 11:56:55 -07:00
|
|
|
if "x-serialize" in data["components"]["schemas"][q]:
|
|
|
|
|
body.update(
|
|
|
|
|
{
|
|
|
|
|
"serialize": data["components"]["schemas"][q][
|
|
|
|
|
"x-serialize"
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
api.update({"body": body})
|
|
|
|
|
|
|
|
|
|
if "parts" in z:
|
|
|
|
|
parts_final.update(z["parts"])
|
|
|
|
|
|
|
|
|
|
if "POST" in methods or "PUT" in methods:
|
|
|
|
|
api.update(
|
|
|
|
|
{
|
2023-11-09 10:51:20 -05:00
|
|
|
"stability": "stable", # type: ignore
|
|
|
|
|
"visibility": "public", # type: ignore
|
2023-06-27 11:56:55 -07:00
|
|
|
"headers": {
|
|
|
|
|
"accept": ["application/json"],
|
|
|
|
|
"content_type": ["application/json"],
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
api.update(
|
|
|
|
|
{
|
2023-11-09 10:51:20 -05:00
|
|
|
"stability": "stable", # type: ignore
|
|
|
|
|
"visibility": "public", # type: ignore
|
2023-06-27 11:56:55 -07:00
|
|
|
"headers": {"accept": ["application/json"]},
|
|
|
|
|
}
|
|
|
|
|
)
|
2019-12-17 22:15:52 +01:00
|
|
|
|
2023-06-27 11:56:55 -07:00
|
|
|
if bool(deprecated_new) and bool(parts_final):
|
|
|
|
|
paths.append(
|
|
|
|
|
{
|
|
|
|
|
"path": key2,
|
|
|
|
|
"methods": methods,
|
|
|
|
|
"parts": parts_final,
|
|
|
|
|
"deprecated": deprecated_new,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
elif bool(parts_final):
|
|
|
|
|
paths.append({"path": key2, "methods": methods, "parts": parts_final})
|
|
|
|
|
else:
|
|
|
|
|
paths.append({"path": key2, "methods": methods})
|
|
|
|
|
|
|
|
|
|
api.update({"url": {"paths": paths}})
|
2023-10-26 07:58:33 -07:00
|
|
|
if all_paths_have_deprecation and x_deprecation_message is not None:
|
|
|
|
|
api.update({"deprecation_message": x_deprecation_message})
|
|
|
|
|
|
2023-10-26 11:31:13 -04:00
|
|
|
api = apply_patch(namespace, name, api)
|
2020-03-13 13:44:46 -05:00
|
|
|
|
2023-06-27 11:56:55 -07:00
|
|
|
if namespace not in modules:
|
|
|
|
|
modules[namespace] = Module(namespace)
|
2019-12-17 22:15:52 +01:00
|
|
|
|
2023-06-27 11:56:55 -07:00
|
|
|
modules[namespace].add(API(namespace, name, api))
|
2019-12-17 22:15:52 +01:00
|
|
|
|
|
|
|
|
return modules
|
|
|
|
|
|
|
|
|
|
|
2023-11-09 10:51:20 -05:00
|
|
|
def apply_patch(namespace: str, name: str, api: Any) -> Any:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
applies patches as specified in {name}.json
|
|
|
|
|
:param namespace: directory containing overrides
|
|
|
|
|
:param name: file to be prepended to ".json" containing override instructions
|
|
|
|
|
:param api: specific api to override
|
|
|
|
|
:return: modified api
|
|
|
|
|
"""
|
2023-10-26 11:31:13 -04:00
|
|
|
override_file_path = (
|
|
|
|
|
CODE_ROOT / "utils/templates/overrides" / namespace / f"{name}.json"
|
|
|
|
|
)
|
|
|
|
|
if os.path.exists(override_file_path):
|
2024-01-19 13:36:05 -05:00
|
|
|
with open(override_file_path, encoding="utf-8") as file:
|
|
|
|
|
override_json = json.load(file)
|
2023-10-26 11:31:13 -04:00
|
|
|
api = deepmerge.always_merger.merge(api, override_json)
|
|
|
|
|
return api
|
|
|
|
|
|
|
|
|
|
|
2023-11-09 10:51:20 -05:00
|
|
|
def dump_modules(modules: Any) -> None:
|
2024-01-19 13:36:05 -05:00
|
|
|
"""
|
|
|
|
|
writes out modules to disk
|
|
|
|
|
:param modules: a list of python modules
|
|
|
|
|
"""
|
2019-12-17 22:15:52 +01:00
|
|
|
for mod in modules.values():
|
|
|
|
|
mod.dump()
|
|
|
|
|
|
2020-05-15 09:36:47 -05:00
|
|
|
# Unasync all the generated async code
|
|
|
|
|
additional_replacements = {
|
|
|
|
|
# We want to rewrite to 'Transport' instead of 'SyncTransport', etc
|
|
|
|
|
"AsyncTransport": "Transport",
|
2021-08-13 15:51:50 +05:30
|
|
|
"AsyncOpenSearch": "OpenSearch",
|
2020-05-15 09:36:47 -05:00
|
|
|
# We don't want to rewrite this class
|
|
|
|
|
"AsyncSearchClient": "AsyncSearchClient",
|
|
|
|
|
}
|
|
|
|
|
rules = [
|
|
|
|
|
unasync.Rule(
|
2021-09-16 14:59:29 +05:30
|
|
|
fromdir="/opensearchpy/_async/client/",
|
|
|
|
|
todir="/opensearchpy/client/",
|
2020-06-24 14:25:28 -05:00
|
|
|
additional_replacements=additional_replacements,
|
2020-05-15 09:36:47 -05:00
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
filepaths = []
|
2021-09-16 14:59:29 +05:30
|
|
|
for root, _, filenames in os.walk(CODE_ROOT / "opensearchpy/_async"):
|
2020-05-15 09:36:47 -05:00
|
|
|
for filename in filenames:
|
2023-11-06 13:08:19 -05:00
|
|
|
if filename.rpartition(".")[-1] in ("py",) and not filename.startswith(
|
|
|
|
|
"utils.py"
|
|
|
|
|
):
|
2020-05-15 09:36:47 -05:00
|
|
|
filepaths.append(os.path.join(root, filename))
|
|
|
|
|
|
|
|
|
|
unasync.unasync_files(filepaths, rules)
|
2021-09-16 14:59:29 +05:30
|
|
|
blacken(CODE_ROOT / "opensearchpy")
|
2020-05-15 09:36:47 -05:00
|
|
|
|
2019-12-17 22:15:52 +01:00
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2023-06-27 11:56:55 -07:00
|
|
|
dump_modules(read_modules())
|