diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad4a299..57b845df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Added GHA release ([#614](https://github.com/opensearch-project/opensearch-py/pull/614)) - Incorporated API generation into CI workflow and fixed 'generate' nox session ([#660](https://github.com/opensearch-project/opensearch-py/pull/660)) - Added an automated api update bot for opensearch-py ([#664](https://github.com/opensearch-project/opensearch-py/pull/664)) +- Enhance generator to update changelog only if generated code differs from existing ([#684](https://github.com/opensearch-project/opensearch-py/pull/684)) ### Changed - Updated the `get_policy` API in the index_management plugin to allow the policy_id argument as optional ([#633](https://github.com/opensearch-project/opensearch-py/pull/633)) - Updated the `point_in_time.md` guide with examples demonstrating the usage of the new APIs as alternatives to the deprecated ones. ([#661](https://github.com/opensearch-project/opensearch-py/pull/661)) diff --git a/noxfile.py b/noxfile.py index 1b261fe0..5c932699 100644 --- a/noxfile.py +++ b/noxfile.py @@ -146,4 +146,5 @@ def generate(session: Any) -> None: """ session.install("-rdev-requirements.txt") session.run("python", "utils/generate_api.py") - session.notify("format") + session.run("nox", "-s", "format", external=True) + session.run("python", "utils/changelog_updater.py") diff --git a/utils/changelog_updater.py b/utils/changelog_updater.py new file mode 100644 index 00000000..fde4947f --- /dev/null +++ b/utils/changelog_updater.py @@ -0,0 +1,100 @@ +# 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. +# +# Modifications Copyright OpenSearch Contributors. See +# GitHub history for details. + +import filecmp +import os +import shutil + +import requests + + +def main() -> None: + """ + Update CHANGELOG.md when API generator produces new code differing from existing. + """ + root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + after_paths = [ + os.path.join(root_dir, f"opensearchpy/{folder}") + for folder in ["client", "_async/client"] + ] + + before_paths = [ + os.path.join(root_dir, f"before_generate/{folder}") + for folder in ["client", "async_client"] + ] + + # Compare only .py files and take their union for client and async_client directories + before_files_client = set( + file for file in os.listdir(before_paths[0]) if file.endswith(".py") + ) + after_files_client = set( + file for file in os.listdir(after_paths[0]) if file.endswith(".py") + ) + + before_files_async_client = set( + file for file in os.listdir(before_paths[1]) if file.endswith(".py") + ) + after_files_async_client = set( + file for file in os.listdir(after_paths[1]) if file.endswith(".py") + ) + + all_files_union_client = before_files_client.union(after_files_client) + all_files_union_async_client = before_files_async_client.union( + after_files_async_client + ) + + # Compare files and check for mismatches or errors for client and async_client directories + mismatch_client, errors_client = filecmp.cmpfiles( + before_paths[0], after_paths[0], all_files_union_client, shallow=True + )[1:] + mismatch_async_client, errors_async_client = filecmp.cmpfiles( + before_paths[1], after_paths[1], all_files_union_async_client, shallow=True + )[1:] + + if mismatch_client or errors_client or mismatch_async_client or errors_async_client: + print("Changes detected") + response = requests.get( + "https://api.github.com/repos/opensearch-project/opensearch-api-specification/commits" + ) + if response.ok: + commit_info = response.json()[0] + commit_url = commit_info["html_url"] + latest_commit_sha = commit_info.get("sha") + else: + raise Exception( + f"Failed to fetch opensearch-api-specification commit information. Status code: {response.status_code}" + ) + + with open("CHANGELOG.md", "r+", encoding="utf-8") as file: + content = file.read() + if commit_url not in content: + if "### Updated APIs" in content: + file_content = content.replace( + "### Updated APIs", + f"### Updated APIs\n- Updated opensearch-py APIs to reflect [opensearch-api-specification@{latest_commit_sha[:7]}]({commit_url})", + 1, + ) + file.seek(0) + file.write(file_content) + file.truncate() + else: + raise Exception( + "'Updated APIs' section is not present in CHANGELOG.md" + ) + else: + print("No changes detected") + + # Clean up + for path in before_paths: + shutil.rmtree(path) + + +if __name__ == "__main__": + main() diff --git a/utils/generate_api.py b/utils/generate_api.py index c14c03c1..76f6353a 100644 --- a/utils/generate_api.py +++ b/utils/generate_api.py @@ -33,6 +33,7 @@ import json import os import re +import shutil from functools import lru_cache from itertools import chain, groupby from operator import itemgetter @@ -764,34 +765,22 @@ def dump_modules(modules: Any) -> None: unasync.unasync_files(filepaths, rules) blacken(CODE_ROOT / "opensearchpy") - # Updating the CHANGELOG.md - response = requests.get( - "https://api.github.com/repos/opensearch-project/opensearch-api-specification/commits" - ) - if response.ok: - commit_info = response.json()[0] - commit_url = commit_info["html_url"] - latest_commit_sha = commit_info.get("sha") - else: - raise Exception( - f"Failed to fetch opensearch-api-specification commit information. Status code: {response.status_code}" - ) - with open("CHANGELOG.md", "r+", encoding="utf-8") as file: - content = file.read() - if commit_url not in content: - if "### Updated APIs" in content: - file_content = content.replace( - "### Updated APIs", - f"### Updated APIs\n- Updated opensearch-py APIs to reflect [opensearch-api-specification@{latest_commit_sha[:7]}]({commit_url})", - 1, - ) - file.seek(0) - file.write(file_content) - file.truncate() - else: - raise Exception("'Updated APIs' section is not present in CHANGELOG.md") +if __name__ == "__main__": + # Store directories for comparison pre-generation vs post-generation. + root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + before_paths = [ + os.path.join(root_dir, f"before_generate/{folder}") + for folder in ["client", "async_client"] + ] + for path in before_paths: + if os.path.exists(path): + shutil.rmtree(path) + + shutil.copytree(os.path.join(root_dir, "opensearchpy/client"), before_paths[0]) + shutil.copytree( + os.path.join(root_dir, "opensearchpy/_async/client"), before_paths[1] + ) -if __name__ == "__main__": dump_modules(read_modules())