From eed77990b63643af2044aeef6bffecdabe843fe8 Mon Sep 17 00:00:00 2001 From: Adam Ling Date: Wed, 10 Sep 2025 00:28:47 -0700 Subject: [PATCH 1/8] release preparation scripts --- scripts/release/prepare_release_branch.py | 253 ++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100755 scripts/release/prepare_release_branch.py diff --git a/scripts/release/prepare_release_branch.py b/scripts/release/prepare_release_branch.py new file mode 100755 index 0000000000..7bb4995785 --- /dev/null +++ b/scripts/release/prepare_release_branch.py @@ -0,0 +1,253 @@ +# +# Copyright (c) 2012-2025 Snowflake Computing Inc. All rights reserved. +# + +import os +import sys +import re +import subprocess +from datetime import date +import glob + + +def check_snowpark_directory(): + """Check if we're in a snowpark-python directory""" + current_dir = os.getcwd() + if "snowpark-python" not in current_dir: + print( # noqa: T201 + "Error: This script must be run from within a snowpark-python directory" + ) + sys.exit(1) + + +def get_version_input(): + """Get version input from user and validate format""" + print("Enter the next release version (format: x.y.z, e.g., 1.39.0)") # noqa: T201 + version_str = input("Version: ").strip() + + # Validate version format (x.y.z) + if not re.match(r"^\d+\.\d+\.\d+$", version_str): + print("Error: Version must be in format x.y.z (e.g., 1.39.0)") # noqa: T201 + sys.exit(1) + + parts = version_str.split(".") + major, minor, patch = int(parts[0]), int(parts[1]), int(parts[2]) + + return version_str, major, minor, patch + + +def get_base_reference(): + """Get optional base commit/branch for the release branch""" + print("\nOptional: Specify a base for the release branch") # noqa: T201 + print("- Enter a commit ID (e.g., abc1234)") # noqa: T201 + print("- Enter a local branch name (e.g., feature-branch)") # noqa: T201 + print("- Press Enter to use origin/main (default)") # noqa: T201 + base_ref = input("Base reference (default: origin/main): ").strip() + + if not base_ref: + return "origin/main", "origin/main" + elif re.match(r"^[a-f0-9]{7,40}$", base_ref): + return base_ref, f"commit {base_ref}" + else: + return base_ref, f"branch {base_ref}" + + +def run_git_command(command): + """Run git command and handle errors""" + try: + result = subprocess.run( + command, shell=True, check=True, capture_output=True, text=True + ) + return result.stdout + except subprocess.CalledProcessError as e: + print(f"Git command failed: {command}") # noqa: T201 + print(f"Error: {e.stderr}") # noqa: T201 + sys.exit(1) + + +def update_version_py(version_str, major, minor, patch): + """Update src/snowflake/snowpark/version.py""" + version_file = "src/snowflake/snowpark/version.py" + + if not os.path.exists(version_file): + print(f"Error: {version_file} not found") # noqa: T201 + sys.exit(1) + + with open(version_file) as f: + content = f.read() + + # Replace VERSION tuple + new_content = re.sub( + r"VERSION = \(\d+, \d+, \d+\)", + f"VERSION = ({major}, {minor}, {patch})", + content, + ) + + with open(version_file, "w") as f: + f.write(new_content) + + print(f"āœ“ Updated {version_file}") # noqa: T201 + + +def update_meta_yaml(version_str): + """Update recipe/meta.yaml""" + meta_file = "recipe/meta.yaml" + + if not os.path.exists(meta_file): + print(f"Error: {meta_file} not found") # noqa: T201 + sys.exit(1) + + with open(meta_file) as f: + content = f.read() + + # Replace version string + new_content = re.sub( + r'{% set version = "[^"]*" %}', + f'{{% set version = "{version_str}" %}}', + content, + ) + + with open(meta_file, "w") as f: + f.write(new_content) + + print(f"āœ“ Updated {meta_file}") # noqa: T201 + + +def update_changelog(version_str): + """Update CHANGELOG.md by replacing the version date with today's date""" + changelog_file = "CHANGELOG.md" + today = date.today().strftime("%Y-%m-%d") + + if not os.path.exists(changelog_file): + print(f"Error: {changelog_file} not found") # noqa: T201 + sys.exit(1) + + with open(changelog_file) as f: + content = f.read() + + # Find the first version line and replace it with the new version and today's date + # Pattern matches the first: ## x.y.z (any_date) + first_version_pattern = r"## \d+\.\d+\.\d+ \([^)]+\)" + replacement = f"## {version_str} ({today})" + + new_content = re.sub(first_version_pattern, replacement, content, count=1) + + if new_content == content: + print(f"Warning: No version entry found in {changelog_file}") # noqa: T201 + else: + with open(changelog_file, "w") as f: + f.write(new_content) + print(f"āœ“ Updated {changelog_file}") # noqa: T201 + + +def update_test_files(major, minor, patch): + """Update all .test and .test.DISABLED files in tests/ast/data""" + test_dir = "tests/ast/data" + + if not os.path.exists(test_dir): + print(f"Error: {test_dir} not found") # noqa: T201 + sys.exit(1) + + # Get both .test and .test.DISABLED files + test_files = glob.glob(os.path.join(test_dir, "*.test")) + test_files.extend(glob.glob(os.path.join(test_dir, "*.test.DISABLED"))) + + if not test_files: + print( # noqa: T201 + f"Warning: No .test or .test.DISABLED files found in {test_dir}" + ) + return + + # Determine client_version format based on patch version + if patch == 0: + # Major/minor release - only major and minor fields + client_version_replacement = f"""client_version {{ + major: {major} + minor: {minor} +}}""" + else: + # Patch release - major, minor, and patch fields + client_version_replacement = f"""client_version {{ + major: {major} + minor: {minor} + patch: {patch} +}}""" + + updated_count = 0 + + for test_file in test_files: + with open(test_file) as f: + content = f.read() + + # Replace client_version blocks + # This regex handles both formats (with and without patch) + pattern = r"client_version\s*\{\s*major:\s*\d+\s*minor:\s*\d+\s*(?:patch:\s*\d+\s*)?\}" + + if re.search(pattern, content): + new_content = re.sub(pattern, client_version_replacement, content) + + if new_content != content: + with open(test_file, "w") as f: + f.write(new_content) + updated_count += 1 + + print( # noqa: T201 + f"āœ“ Updated {updated_count} .test and .test.DISABLED files in {test_dir}" + ) + + +def main(): + print("Snowpark Python Release Preparation Script") # noqa: T201 + print("==========================================") # noqa: T201 + + # Check if we're in the right directory + check_snowpark_directory() + + # Get version input + version_str, major, minor, patch = get_version_input() + + print(f"\nPreparing release for version {version_str}") # noqa: T201 + print(f"Major: {major}, Minor: {minor}, Patch: {patch}") # noqa: T201 + + is_patch_release = patch != 0 + print( # noqa: T201 + f"Release type: {'Patch' if is_patch_release else 'Major/Minor'}" + ) + + # Get base reference for release branch + base_ref, base_description = get_base_reference() + + # Checkout release branch + branch_name = f"release-v{version_str}" + print( # noqa: T201 + f"\nšŸ”€ Creating release branch: {branch_name} from {base_description}" + ) + + if base_ref == "origin/main": + # Fetch origin/main and create branch from it + print("Fetching latest origin/main...") # noqa: T201 + run_git_command("git fetch origin main") + run_git_command(f"git checkout -b {branch_name} origin/main") + else: + # Create branch from specified commit or branch + run_git_command(f"git checkout -b {branch_name} {base_ref}") + + print("\nšŸ“ Updating version files...") # noqa: T201 + + # Update files + update_version_py(version_str, major, minor, patch) + update_meta_yaml(version_str) + update_changelog(version_str) + update_test_files(major, minor, patch) + + print("\nšŸŽ‰ Release preparation completed successfully!") # noqa: T201 + print("\nNext steps:") # noqa: T201 + print("1. Review the changes with: git diff") # noqa: T201 + print( # noqa: T201 + f"2. Commit the changes with: git commit -am 'Prepare release v{version_str}'" + ) + print(f"3. Push the branch with: git push origin {branch_name}") # noqa: T201 + + +if __name__ == "__main__": + main() From de8a43f95babeb2301cc008acb8c3e3d8354abd6 Mon Sep 17 00:00:00 2001 From: Adam Ling Date: Wed, 10 Sep 2025 17:05:47 -0700 Subject: [PATCH 2/8] update --- scripts/release/readme.md | 180 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 scripts/release/readme.md diff --git a/scripts/release/readme.md b/scripts/release/readme.md new file mode 100644 index 0000000000..a1734146b3 --- /dev/null +++ b/scripts/release/readme.md @@ -0,0 +1,180 @@ +# Snowpark Python Release Preparation Script + +This script automates the process of preparing a release branch for Snowpark Python. It handles version updates, changelog management, and test file modifications in a single automated workflow. + +## What It Does + +The `prepare_release_branch.py` script performs the following tasks: + +1. **Validates Environment**: Ensures you're running from within a snowpark-python directory +2. **Creates Release Branch**: Creates a new git branch from a specified base (commit, branch, or origin/main) +3. **Updates Version Files**: + - Updates `src/snowflake/snowpark/version.py` with the new VERSION tuple + - Updates `recipe/meta.yaml` with the new version string +4. **Updates Changelog**: Replaces the first version entry in `CHANGELOG.md` with the new version and today's date +5. **Updates Test Files**: Updates all `.test` and `.test.DISABLED` files in `tests/ast/data/` with the correct `client_version` format + +## Prerequisites + +- You must be in a snowpark-python project directory +- Git must be available and the repository must be initialized +- Python 3.x with standard library modules + +## Usage + +### Basic Usage + +```bash +python ./scripts/release/prepare_release_branch.py +``` + +### What You'll Be Prompted For + +#### 1. Version Input +``` +Enter the next release version (format: x.y.z, e.g., 1.39.0) +Version: _ +``` +- Enter the version in `x.y.z` format (e.g., `1.40.0`, `2.1.3`) +- The script validates the format and determines if it's a patch release (patch > 0) or major/minor release (patch = 0) + +#### 2. Base Reference (Optional) +``` +Optional: Specify a base for the release branch +- Enter a commit ID (e.g., abc1234) +- Enter a local branch name (e.g., feature-branch) +- Press Enter to use origin/main (default) +Base reference (default: origin/main): _ +``` + +**Options:** +- **Press Enter**: Uses `origin/main` (fetches latest and creates branch from it) +- **Commit ID**: Enter a 7-40 character hex string (e.g., `abc1234`, `1a2b3c4d5e6f7890`) +- **Branch Name**: Enter any local branch name (e.g., `feature-branch`, `bugfix-123`) + +## Examples + +### Example 1: Standard Release from origin/main +```bash +./scripts/release/prepare_release_branch.py + +# Prompts: +Version: 1.40.0 +Base reference (default: origin/main): [Press Enter] + +# Creates: release-v1.40.0 branch from latest origin/main +``` + +### Example 2: Patch Release from Specific Commit +```bash +./scripts/release/prepare_release_branch.py + +# Prompts: +Version: 1.39.1 +Base reference (default: origin/main): abc123def + +# Creates: release-v1.39.1 branch from commit abc123def +``` + +### Example 3: Release from Feature Branch +```bash +./scripts/release/prepare_release_branch.py + +# Prompts: +Version: 2.0.0 +Base reference (default: origin/main): feature-new-api + +# Creates: release-v2.0.0 branch from feature-new-api branch +``` + +## What Gets Updated + +### 1. Version Files +- **`src/snowflake/snowpark/version.py`**: Updates the `VERSION = (major, minor, patch)` tuple +- **`recipe/meta.yaml`**: Updates the `{% set version = "x.y.z" %}` line + +### 2. Changelog +- **`CHANGELOG.md`**: Finds the first version entry and updates it with the new version and today's date +- Example: `## 1.39.0 (YYYY-MM-DD)` → `## 1.40.0 (2025-09-10)` + +### 3. AST Test Files +Updates all `.test` and `.test.DISABLED` files in `tests/ast/data/` with the correct `client_version` format: + +**For Major/Minor Releases (patch = 0):** +``` +client_version { + major: 1 + minor: 40 +} +``` + +**For Patch Releases (patch > 0):** +``` +client_version { + major: 1 + minor: 39 + patch: 1 +} +``` + +## Sample Output + +``` +Snowpark Python Release Preparation Script +========================================== + +Enter the next release version (format: x.y.z, e.g., 1.39.0) +Version: 1.40.0 + +Preparing release for version 1.40.0 +Major: 1, Minor: 40, Patch: 0 +Release type: Major/Minor + +Optional: Specify a base for the release branch +- Enter a commit ID (e.g., abc1234) +- Enter a local branch name (e.g., feature-branch) +- Press Enter to use origin/main (default) +Base reference (default: origin/main): + +šŸ”€ Creating release branch: release-v1.40.0 from origin/main +Fetching latest origin/main... + +šŸ“ Updating version files... +āœ“ Updated src/snowflake/snowpark/version.py +āœ“ Updated recipe/meta.yaml +āœ“ Updated CHANGELOG.md +āœ“ Updated 124 .test and .test.DISABLED files in tests/ast/data + +šŸŽ‰ Release preparation completed successfully! + +Next steps: +1. Review the changes with: git diff +2. Commit the changes with: git commit -am 'Prepare release v1.40.0' +3. Push the branch with: git push origin release-v1.40.0 +``` + +## Error Handling + +The script will exit with an error if: +- Not run from a snowpark-python directory +- Invalid version format provided +- Required files are missing (`version.py`, `meta.yaml`, `CHANGELOG.md`) +- Git commands fail +- Test directory doesn't exist + +## Next Steps After Running + +After the script completes successfully: + +1. **Review Changes**: `git diff` +2. **Commit Changes**: `git commit -am 'Prepare release v1.40.0'` +3. **Push Branch**: `git push origin release-v1.40.0` +4. Create pull request for the release branch +5. Follow your standard release process + +## Notes + +- The script uses only Python standard library modules (no external dependencies) +- All print statement linter warnings are suppressed with `# noqa: T201` +- The script creates a new branch and doesn't modify your current working branch until checkout +- If a release branch with the same name already exists, git will show an error From 665c834c6d7df5f8c67e47b1604bfef289b9a58a Mon Sep 17 00:00:00 2001 From: Adam Ling Date: Thu, 11 Sep 2025 11:48:15 -0700 Subject: [PATCH 3/8] update script --- scripts/release/prepare_release_branch.py | 45 +++++++++++++++++++---- scripts/release/readme.md | 6 +++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/scripts/release/prepare_release_branch.py b/scripts/release/prepare_release_branch.py index 7bb4995785..b5d0b83e9a 100755 --- a/scripts/release/prepare_release_branch.py +++ b/scripts/release/prepare_release_branch.py @@ -6,7 +6,7 @@ import sys import re import subprocess -from datetime import date +from datetime import date, datetime import glob @@ -52,6 +52,34 @@ def get_base_reference(): return base_ref, f"branch {base_ref}" +def get_release_date_input(): + """Get optional release date from user and validate format""" + print("\nOptional: Specify a release date (format: YYYY-MM-DD)") # noqa: T201 + print("- Press Enter to use today's date (default)") # noqa: T201 + date_str = input("Release date (default: today): ").strip() + + if not date_str: + return date.today().strftime("%Y-%m-%d") + + # Validate date format (YYYY-MM-DD) + if not re.match(r"^\d{4}-\d{2}-\d{2}$", date_str): + print( # noqa: T201 + "Error: Date must be in format YYYY-MM-DD (e.g., 2024-12-31)" + ) + sys.exit(1) + + # Validate that it's a valid date + try: + datetime.strptime(date_str, "%Y-%m-%d") + except ValueError: + print( # noqa: T201 + "Error: Invalid date. Please enter a valid date in format YYYY-MM-DD" + ) + sys.exit(1) + + return date_str + + def run_git_command(command): """Run git command and handle errors""" try: @@ -113,10 +141,9 @@ def update_meta_yaml(version_str): print(f"āœ“ Updated {meta_file}") # noqa: T201 -def update_changelog(version_str): - """Update CHANGELOG.md by replacing the version date with today's date""" +def update_changelog(version_str, release_date): + """Update CHANGELOG.md by replacing the version date with the specified date""" changelog_file = "CHANGELOG.md" - today = date.today().strftime("%Y-%m-%d") if not os.path.exists(changelog_file): print(f"Error: {changelog_file} not found") # noqa: T201 @@ -125,10 +152,10 @@ def update_changelog(version_str): with open(changelog_file) as f: content = f.read() - # Find the first version line and replace it with the new version and today's date + # Find the first version line and replace it with the new version and specified date # Pattern matches the first: ## x.y.z (any_date) first_version_pattern = r"## \d+\.\d+\.\d+ \([^)]+\)" - replacement = f"## {version_str} ({today})" + replacement = f"## {version_str} ({release_date})" new_content = re.sub(first_version_pattern, replacement, content, count=1) @@ -206,6 +233,10 @@ def main(): # Get version input version_str, major, minor, patch = get_version_input() + # Get release date + release_date = get_release_date_input() + print(f"\nRelease date: {release_date}") # noqa: T201 + print(f"\nPreparing release for version {version_str}") # noqa: T201 print(f"Major: {major}, Minor: {minor}, Patch: {patch}") # noqa: T201 @@ -237,7 +268,7 @@ def main(): # Update files update_version_py(version_str, major, minor, patch) update_meta_yaml(version_str) - update_changelog(version_str) + update_changelog(version_str, release_date) update_test_files(major, minor, patch) print("\nšŸŽ‰ Release preparation completed successfully!") # noqa: T201 diff --git a/scripts/release/readme.md b/scripts/release/readme.md index a1734146b3..c01588997a 100644 --- a/scripts/release/readme.md +++ b/scripts/release/readme.md @@ -178,3 +178,9 @@ After the script completes successfully: - All print statement linter warnings are suppressed with `# noqa: T201` - The script creates a new branch and doesn't modify your current working branch until checkout - If a release branch with the same name already exists, git will show an error + + +## Future Improvements + +- Auto sync dependency updates +- Use Python based unparser to update ast code From de6cfb0e178300e7a174ec1b72d17579cfca588a Mon Sep 17 00:00:00 2001 From: Adam Ling Date: Thu, 11 Sep 2025 11:52:09 -0700 Subject: [PATCH 4/8] update --- scripts/release/prepare_release_branch.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/release/prepare_release_branch.py b/scripts/release/prepare_release_branch.py index b5d0b83e9a..1cd18ab101 100755 --- a/scripts/release/prepare_release_branch.py +++ b/scripts/release/prepare_release_branch.py @@ -271,13 +271,14 @@ def main(): update_changelog(version_str, release_date) update_test_files(major, minor, patch) - print("\nšŸŽ‰ Release preparation completed successfully!") # noqa: T201 - print("\nNext steps:") # noqa: T201 - print("1. Review the changes with: git diff") # noqa: T201 print( # noqa: T201 - f"2. Commit the changes with: git commit -am 'Prepare release v{version_str}'" + f"\nšŸŽ‰ Release preparation completed successfully!" + f"\n\nNext steps:" + f"\n1. Sync the dependency updates in version.py and meta.yaml if they are inconsistent" + f"\n2. Review the changes with: git diff" + f"\n3. Commit the changes with: git commit -am 'Prepare release v{version_str}'" + f"\n4. Push the branch with: git push origin {branch_name}" ) - print(f"3. Push the branch with: git push origin {branch_name}") # noqa: T201 if __name__ == "__main__": From cba7cc415c296d3b4b587f0c6a35a37d931f44f7 Mon Sep 17 00:00:00 2001 From: Adam Ling Date: Thu, 11 Sep 2025 11:54:35 -0700 Subject: [PATCH 5/8] update --- scripts/release/readme.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/release/readme.md b/scripts/release/readme.md index c01588997a..81f0736598 100644 --- a/scripts/release/readme.md +++ b/scripts/release/readme.md @@ -148,9 +148,10 @@ Fetching latest origin/main... šŸŽ‰ Release preparation completed successfully! Next steps: -1. Review the changes with: git diff -2. Commit the changes with: git commit -am 'Prepare release v1.40.0' -3. Push the branch with: git push origin release-v1.40.0 +1. Sync the dependency updates in version.py and meta.yaml if they are inconsistent +2. Review the changes with: git diff +3. Commit the changes with: git commit -am 'Prepare release v1.40.0' +4. Push the branch with: git push origin release-v1.40.0 ``` ## Error Handling From aeccb3aae29c0fa91215e4e28df269c9b32dea39 Mon Sep 17 00:00:00 2001 From: Adam Ling Date: Fri, 12 Sep 2025 14:07:37 -0700 Subject: [PATCH 6/8] update --- .pre-commit-config.yaml | 1 + scripts/release/prepare_release_branch.py | 179 +++++++++++++++---- scripts/release/readme.md | 205 ++++++++++++++++++++-- 3 files changed, 336 insertions(+), 49 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f121cac2ad..54f92ab876 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -84,6 +84,7 @@ repos: - --per-file-ignores=tests/*.py:T201 # prints are allowed in test files # Ignore errors for generated protocol buffer stubs - --exclude=src/snowflake/snowpark/_internal/proto/generated/ast_pb2.py + - --exclude=scripts/release/prepare_release_branch.py # Use mypy for static type checking. - repo: https://github.com/pre-commit/mirrors-mypy rev: 'v0.991' diff --git a/scripts/release/prepare_release_branch.py b/scripts/release/prepare_release_branch.py index 1cd18ab101..55edaea33c 100755 --- a/scripts/release/prepare_release_branch.py +++ b/scripts/release/prepare_release_branch.py @@ -6,10 +6,57 @@ import sys import re import subprocess +import argparse from datetime import date, datetime import glob +def setup_argument_parser(): + """Set up command line argument parser""" + parser = argparse.ArgumentParser( + description="Snowpark Python Release Preparation Script", + epilog=""" +Examples: + # Interactive mode (no arguments - prompts for input) + python prepare_release_branch.py + + # Non-interactive mode (any arguments provided - requires --version) + python prepare_release_branch.py --version 1.39.0 --release-date 2024-12-31 --base-ref origin/main + + # Non-interactive mode with minimal options (uses defaults for date and base-ref) + python prepare_release_branch.py --version 1.39.0 + + # Use a specific commit as base + python prepare_release_branch.py --version 1.39.0 --base-ref abc1234 + + # Use custom release date with version + python prepare_release_branch.py --version 1.39.0 --release-date 2024-12-31 + """, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + parser.add_argument( + "--version", + help="Release version in format x.y.z (e.g., 1.39.0). Required in non-interactive mode.", + type=str, + ) + + parser.add_argument( + "--release-date", + help="Release date in format YYYY-MM-DD (default: today)", + type=str, + ) + + parser.add_argument( + "--base-ref", + help="Base reference for release branch - commit ID, branch name, or origin/main (default: origin/main)", + type=str, + default="origin/main", + ) + + return parser + + def check_snowpark_directory(): """Check if we're in a snowpark-python directory""" current_dir = os.getcwd() @@ -20,10 +67,10 @@ def check_snowpark_directory(): sys.exit(1) -def get_version_input(): - """Get version input from user and validate format""" - print("Enter the next release version (format: x.y.z, e.g., 1.39.0)") # noqa: T201 - version_str = input("Version: ").strip() +def validate_version(version_str): + """Validate version format and return parsed components""" + if not version_str: + return None # Validate version format (x.y.z) if not re.match(r"^\d+\.\d+\.\d+$", version_str): @@ -36,28 +83,8 @@ def get_version_input(): return version_str, major, minor, patch -def get_base_reference(): - """Get optional base commit/branch for the release branch""" - print("\nOptional: Specify a base for the release branch") # noqa: T201 - print("- Enter a commit ID (e.g., abc1234)") # noqa: T201 - print("- Enter a local branch name (e.g., feature-branch)") # noqa: T201 - print("- Press Enter to use origin/main (default)") # noqa: T201 - base_ref = input("Base reference (default: origin/main): ").strip() - - if not base_ref: - return "origin/main", "origin/main" - elif re.match(r"^[a-f0-9]{7,40}$", base_ref): - return base_ref, f"commit {base_ref}" - else: - return base_ref, f"branch {base_ref}" - - -def get_release_date_input(): - """Get optional release date from user and validate format""" - print("\nOptional: Specify a release date (format: YYYY-MM-DD)") # noqa: T201 - print("- Press Enter to use today's date (default)") # noqa: T201 - date_str = input("Release date (default: today): ").strip() - +def validate_date(date_str): + """Validate date format and return formatted date""" if not date_str: return date.today().strftime("%Y-%m-%d") @@ -80,6 +107,65 @@ def get_release_date_input(): return date_str +def validate_base_ref(base_ref): + """Validate and format base reference""" + if not base_ref or base_ref == "origin/main": + return "origin/main", "origin/main" + elif re.match(r"^[a-f0-9]{7,40}$", base_ref): + return base_ref, f"commit {base_ref}" + else: + return base_ref, f"branch {base_ref}" + + +def get_version_input(args=None, is_interactive=True): + """Get version input from CLI args or user and validate format""" + if args and args.version: + return validate_version(args.version) + + if not is_interactive: + # This shouldn't happen as we validate --version is required in non-interactive mode + print("Error: Version is required in non-interactive mode") # noqa: T201 + sys.exit(1) + + print("Enter the next release version (format: x.y.z, e.g., 1.39.0)") # noqa: T201 + version_str = input("Version: ").strip() + return validate_version(version_str) + + +def get_base_reference(args=None, is_interactive=True): + """Get optional base commit/branch for the release branch from CLI args or user input""" + if args and args.base_ref: + return validate_base_ref(args.base_ref) + + if not is_interactive: + # Use default value in non-interactive mode + return validate_base_ref("origin/main") + + print("\nOptional: Specify a base for the release branch") # noqa: T201 + print("- Enter a commit ID (e.g., abc1234)") # noqa: T201 + print("- Enter a local branch name (e.g., feature-branch)") # noqa: T201 + print("- Press Enter to use origin/main (default)") # noqa: T201 + base_ref = input("Base reference (default: origin/main): ").strip() + + return validate_base_ref(base_ref) + + +def get_release_date_input(args=None, is_interactive=True): + """Get optional release date from CLI args or user input and validate format""" + if args and args.release_date: + return validate_date(args.release_date) + + if not is_interactive: + # Use today's date as default in non-interactive mode + return validate_date(None) # None will default to today + + print("\nOptional: Specify a release date (format: YYYY-MM-DD)") # noqa: T201 + print("- Press Enter to use today's date (default)") # noqa: T201 + date_str = input("Release date (default: today): ").strip() + + return validate_date(date_str) + + def run_git_command(command): """Run git command and handle errors""" try: @@ -224,18 +310,47 @@ def update_test_files(major, minor, patch): def main(): - print("Snowpark Python Release Preparation Script") # noqa: T201 - print("==========================================") # noqa: T201 + # Parse command line arguments + parser = setup_argument_parser() + args = parser.parse_args() + + # Determine if we're in interactive or non-interactive mode + # Non-interactive mode: any command-line arguments provided + # Interactive mode: no command-line arguments (just script name) + is_interactive = len(sys.argv) == 1 + + # In non-interactive mode, --version is required + if not is_interactive and not args.version: + print( + "Error: --version is required when using non-interactive mode" + ) # noqa: T201 + sys.exit(1) + + if is_interactive: + print("Snowpark Python Release Preparation Script") # noqa: T201 + print("==========================================") # noqa: T201 + else: + print( + "Snowpark Python Release Preparation Script (Non-Interactive Mode)" + ) # noqa: T201 + print( + "==================================================================" + ) # noqa: T201 # Check if we're in the right directory check_snowpark_directory() # Get version input - version_str, major, minor, patch = get_version_input() + version_result = get_version_input(args, is_interactive) + if not version_result: + print("Error: Version is required") # noqa: T201 + sys.exit(1) + version_str, major, minor, patch = version_result # Get release date - release_date = get_release_date_input() - print(f"\nRelease date: {release_date}") # noqa: T201 + release_date = get_release_date_input(args, is_interactive) + if is_interactive or args.release_date: + print(f"\nRelease date: {release_date}") # noqa: T201 print(f"\nPreparing release for version {version_str}") # noqa: T201 print(f"Major: {major}, Minor: {minor}, Patch: {patch}") # noqa: T201 @@ -246,7 +361,7 @@ def main(): ) # Get base reference for release branch - base_ref, base_description = get_base_reference() + base_ref, base_description = get_base_reference(args, is_interactive) # Checkout release branch branch_name = f"release-v{version_str}" diff --git a/scripts/release/readme.md b/scripts/release/readme.md index 81f0736598..d48be115c7 100644 --- a/scripts/release/readme.md +++ b/scripts/release/readme.md @@ -1,18 +1,19 @@ # Snowpark Python Release Preparation Script -This script automates the process of preparing a release branch for Snowpark Python. It handles version updates, changelog management, and test file modifications in a single automated workflow. +This script automates the process of preparing a release branch for Snowpark Python. It supports both **interactive** and **non-interactive** modes, making it perfect for both manual releases and CI/CD automation. The script handles version updates, changelog management, and test file modifications in a single automated workflow. ## What It Does The `prepare_release_branch.py` script performs the following tasks: 1. **Validates Environment**: Ensures you're running from within a snowpark-python directory -2. **Creates Release Branch**: Creates a new git branch from a specified base (commit, branch, or origin/main) -3. **Updates Version Files**: +2. **Mode Detection**: Automatically detects interactive vs non-interactive mode based on command-line arguments +3. **Creates Release Branch**: Creates a new git branch from a specified base (commit, branch, or origin/main) +4. **Updates Version Files**: - Updates `src/snowflake/snowpark/version.py` with the new VERSION tuple - Updates `recipe/meta.yaml` with the new version string -4. **Updates Changelog**: Replaces the first version entry in `CHANGELOG.md` with the new version and today's date -5. **Updates Test Files**: Updates all `.test` and `.test.DISABLED` files in `tests/ast/data/` with the correct `client_version` format +5. **Updates Changelog**: Replaces the first version entry in `CHANGELOG.md` with the new version and specified/today's date +6. **Updates Test Files**: Updates all `.test` and `.test.DISABLED` files in `tests/ast/data/` with the correct `client_version` format ## Prerequisites @@ -22,13 +23,46 @@ The `prepare_release_branch.py` script performs the following tasks: ## Usage -### Basic Usage +The script supports both **interactive** and **non-interactive** modes with automatic detection: + +- **Interactive Mode**: Run without arguments - prompts for all inputs +- **Non-Interactive Mode**: Provide any command-line arguments - uses CLI values and smart defaults + +### Interactive Mode ```bash python ./scripts/release/prepare_release_branch.py ``` -### What You'll Be Prompted For +### Non-Interactive Mode + +```bash +# View help and examples +python ./scripts/release/prepare_release_branch.py --help + +# Minimal usage (uses today's date and origin/main as defaults) +python ./scripts/release/prepare_release_branch.py --version 1.40.0 + +# Full customization +python ./scripts/release/prepare_release_branch.py --version 1.40.0 --release-date 2025-01-15 --base-ref origin/main + +# Use specific commit as base +python ./scripts/release/prepare_release_branch.py --version 1.40.0 --base-ref abc1234 + +# Custom date only (still requires --version in non-interactive mode) +python ./scripts/release/prepare_release_branch.py --version 1.40.0 --release-date 2025-12-31 +``` + +### Command-Line Arguments + +- `--version VERSION`: Release version in format x.y.z (e.g., 1.39.0). **Required in non-interactive mode**. +- `--release-date RELEASE_DATE`: Release date in format YYYY-MM-DD (default: today) +- `--base-ref BASE_REF`: Base reference for release branch - commit ID, branch name, or origin/main (default: origin/main) +- `--help`: Show help message with examples + +### Interactive Mode Prompts + +When running in interactive mode (no arguments), you'll be prompted for: #### 1. Version Input ``` @@ -38,7 +72,14 @@ Version: _ - Enter the version in `x.y.z` format (e.g., `1.40.0`, `2.1.3`) - The script validates the format and determines if it's a patch release (patch > 0) or major/minor release (patch = 0) -#### 2. Base Reference (Optional) +#### 2. Release Date (Optional) +``` +Optional: Specify a release date (format: YYYY-MM-DD) +- Press Enter to use today's date (default) +Release date (default: today): _ +``` + +#### 3. Base Reference (Optional) ``` Optional: Specify a base for the release branch - Enter a commit ID (e.g., abc1234) @@ -54,37 +95,74 @@ Base reference (default: origin/main): _ ## Examples -### Example 1: Standard Release from origin/main +### Interactive Mode Examples + +#### Example 1: Standard Release from origin/main ```bash ./scripts/release/prepare_release_branch.py # Prompts: Version: 1.40.0 +Release date (default: today): [Press Enter] Base reference (default: origin/main): [Press Enter] -# Creates: release-v1.40.0 branch from latest origin/main +# Creates: release-v1.40.0 branch from latest origin/main with today's date ``` -### Example 2: Patch Release from Specific Commit +#### Example 2: Patch Release from Specific Commit ```bash ./scripts/release/prepare_release_branch.py # Prompts: Version: 1.39.1 +Release date (default: today): [Press Enter] Base reference (default: origin/main): abc123def -# Creates: release-v1.39.1 branch from commit abc123def +# Creates: release-v1.39.1 branch from commit abc123def with today's date ``` -### Example 3: Release from Feature Branch +#### Example 3: Release from Feature Branch with Custom Date ```bash ./scripts/release/prepare_release_branch.py # Prompts: Version: 2.0.0 +Release date (default: today): 2025-12-31 Base reference (default: origin/main): feature-new-api -# Creates: release-v2.0.0 branch from feature-new-api branch +# Creates: release-v2.0.0 branch from feature-new-api branch with specified date +``` + +### Non-Interactive Mode Examples + +#### Example 4: Quick Release (Minimal Arguments) +```bash +./scripts/release/prepare_release_branch.py --version 1.40.0 + +# Uses defaults: +# - Release date: today's date +# - Base reference: origin/main +# Creates: release-v1.40.0 branch from latest origin/main with today's date +``` + +#### Example 5: Full Automation (All Arguments) +```bash +./scripts/release/prepare_release_branch.py \ + --version 1.40.0 \ + --release-date 2025-01-15 \ + --base-ref origin/main + +# Creates: release-v1.40.0 branch from origin/main with specified date +``` + +#### Example 6: CI/CD Pipeline Usage +```bash +# Perfect for automation and CI/CD +./scripts/release/prepare_release_branch.py \ + --version 1.39.1 \ + --base-ref abc123def + +# Uses today's date, creates release-v1.39.1 from specific commit ``` ## What Gets Updated @@ -119,6 +197,7 @@ client_version { ## Sample Output +### Interactive Mode Output ``` Snowpark Python Release Preparation Script ========================================== @@ -126,6 +205,12 @@ Snowpark Python Release Preparation Script Enter the next release version (format: x.y.z, e.g., 1.39.0) Version: 1.40.0 +Optional: Specify a release date (format: YYYY-MM-DD) +- Press Enter to use today's date (default) +Release date (default: today): + +Release date: 2025-09-12 + Preparing release for version 1.40.0 Major: 1, Minor: 40, Patch: 0 Release type: Major/Minor @@ -154,14 +239,80 @@ Next steps: 4. Push the branch with: git push origin release-v1.40.0 ``` +### Non-Interactive Mode Output +``` +Snowpark Python Release Preparation Script (Non-Interactive Mode) +================================================================== + +Preparing release for version 1.40.0 +Major: 1, Minor: 40, Patch: 0 +Release type: Major/Minor + +šŸ”€ Creating release branch: release-v1.40.0 from origin/main +Fetching latest origin/main... + +šŸ“ Updating version files... +āœ“ Updated src/snowflake/snowpark/version.py +āœ“ Updated recipe/meta.yaml +āœ“ Updated CHANGELOG.md +āœ“ Updated 124 .test and .test.DISABLED files in tests/ast/data + +šŸŽ‰ Release preparation completed successfully! + +Next steps: +1. Sync the dependency updates in version.py and meta.yaml if they are inconsistent +2. Review the changes with: git diff +3. Commit the changes with: git commit -am 'Prepare release v1.40.0' +4. Push the branch with: git push origin release-v1.40.0 +``` + +### Help Output +```bash +$ python ./scripts/release/prepare_release_branch.py --help + +usage: prepare_release_branch.py [-h] [--version VERSION] + [--release-date RELEASE_DATE] + [--base-ref BASE_REF] + +Snowpark Python Release Preparation Script + +options: + -h, --help show this help message and exit + --version VERSION Release version in format x.y.z (e.g., 1.39.0). + Required in non-interactive mode. + --release-date RELEASE_DATE + Release date in format YYYY-MM-DD (default: today) + --base-ref BASE_REF Base reference for release branch - commit ID, branch + name, or origin/main (default: origin/main) + +Examples: + # Interactive mode (no arguments - prompts for input) + python prepare_release_branch.py + + # Non-interactive mode (any arguments provided - requires --version) + python prepare_release_branch.py --version 1.39.0 --release-date 2024-12-31 --base-ref origin/main + + # Non-interactive mode with minimal options (uses defaults for date and base-ref) + python prepare_release_branch.py --version 1.39.0 + + # Use a specific commit as base + python prepare_release_branch.py --version 1.39.0 --base-ref abc1234 + + # Use custom release date with version + python prepare_release_branch.py --version 1.39.0 --release-date 2024-12-31 +``` + ## Error Handling The script will exit with an error if: - Not run from a snowpark-python directory -- Invalid version format provided +- Invalid version format provided (must be x.y.z format) +- Invalid date format provided (must be YYYY-MM-DD format) +- Invalid date value provided (e.g., 2025-13-45) +- `--version` is missing when using non-interactive mode (any CLI arguments provided) - Required files are missing (`version.py`, `meta.yaml`, `CHANGELOG.md`) -- Git commands fail -- Test directory doesn't exist +- Git commands fail (e.g., branch already exists, uncommitted changes, invalid base reference) +- Test directory doesn't exist (`tests/ast/data/`) ## Next Steps After Running @@ -175,13 +326,33 @@ After the script completes successfully: ## Notes +### Mode Detection +- **Interactive mode**: Triggered when no command-line arguments are provided +- **Non-interactive mode**: Automatically triggered when any CLI arguments are provided +- No need to specify `--non-interactive` flag - the script detects mode automatically + +### Smart Defaults (Non-Interactive Mode) +- **Release date**: Uses today's date if not specified +- **Base reference**: Uses `origin/main` if not specified +- **Version**: Always required in non-interactive mode + +### Technical Details - The script uses only Python standard library modules (no external dependencies) - All print statement linter warnings are suppressed with `# noqa: T201` - The script creates a new branch and doesn't modify your current working branch until checkout - If a release branch with the same name already exists, git will show an error +- Perfect for CI/CD automation with the non-interactive mode +### Automation-Friendly +The non-interactive mode makes this script perfect for: +- **CI/CD Pipelines**: Automated release preparation +- **Batch Processing**: Multiple releases with different parameters +- **Shell Scripts**: Integration with larger automation workflows +- **Scheduled Releases**: Cron jobs or scheduled tasks ## Future Improvements - Auto sync dependency updates - Use Python based unparser to update ast code +- Support for pre-release versions (e.g., 1.40.0-rc1) +- Integration with GitHub Actions for automated PRs From 22569b10442bade5958e05590740231ae5e615e3 Mon Sep 17 00:00:00 2001 From: Adam Ling Date: Fri, 12 Sep 2025 14:08:46 -0700 Subject: [PATCH 7/8] update --- scripts/release/prepare_release_branch.py | 96 +++++++++-------------- 1 file changed, 38 insertions(+), 58 deletions(-) diff --git a/scripts/release/prepare_release_branch.py b/scripts/release/prepare_release_branch.py index 55edaea33c..b835b19999 100755 --- a/scripts/release/prepare_release_branch.py +++ b/scripts/release/prepare_release_branch.py @@ -61,9 +61,7 @@ def check_snowpark_directory(): """Check if we're in a snowpark-python directory""" current_dir = os.getcwd() if "snowpark-python" not in current_dir: - print( # noqa: T201 - "Error: This script must be run from within a snowpark-python directory" - ) + print("Error: This script must be run from within a snowpark-python directory") sys.exit(1) @@ -74,7 +72,7 @@ def validate_version(version_str): # Validate version format (x.y.z) if not re.match(r"^\d+\.\d+\.\d+$", version_str): - print("Error: Version must be in format x.y.z (e.g., 1.39.0)") # noqa: T201 + print("Error: Version must be in format x.y.z (e.g., 1.39.0)") sys.exit(1) parts = version_str.split(".") @@ -90,18 +88,14 @@ def validate_date(date_str): # Validate date format (YYYY-MM-DD) if not re.match(r"^\d{4}-\d{2}-\d{2}$", date_str): - print( # noqa: T201 - "Error: Date must be in format YYYY-MM-DD (e.g., 2024-12-31)" - ) + print("Error: Date must be in format YYYY-MM-DD (e.g., 2024-12-31)") sys.exit(1) # Validate that it's a valid date try: datetime.strptime(date_str, "%Y-%m-%d") except ValueError: - print( # noqa: T201 - "Error: Invalid date. Please enter a valid date in format YYYY-MM-DD" - ) + print("Error: Invalid date. Please enter a valid date in format YYYY-MM-DD") sys.exit(1) return date_str @@ -124,10 +118,10 @@ def get_version_input(args=None, is_interactive=True): if not is_interactive: # This shouldn't happen as we validate --version is required in non-interactive mode - print("Error: Version is required in non-interactive mode") # noqa: T201 + print("Error: Version is required in non-interactive mode") sys.exit(1) - print("Enter the next release version (format: x.y.z, e.g., 1.39.0)") # noqa: T201 + print("Enter the next release version (format: x.y.z, e.g., 1.39.0)") version_str = input("Version: ").strip() return validate_version(version_str) @@ -141,10 +135,10 @@ def get_base_reference(args=None, is_interactive=True): # Use default value in non-interactive mode return validate_base_ref("origin/main") - print("\nOptional: Specify a base for the release branch") # noqa: T201 - print("- Enter a commit ID (e.g., abc1234)") # noqa: T201 - print("- Enter a local branch name (e.g., feature-branch)") # noqa: T201 - print("- Press Enter to use origin/main (default)") # noqa: T201 + print("\nOptional: Specify a base for the release branch") + print("- Enter a commit ID (e.g., abc1234)") + print("- Enter a local branch name (e.g., feature-branch)") + print("- Press Enter to use origin/main (default)") base_ref = input("Base reference (default: origin/main): ").strip() return validate_base_ref(base_ref) @@ -159,8 +153,8 @@ def get_release_date_input(args=None, is_interactive=True): # Use today's date as default in non-interactive mode return validate_date(None) # None will default to today - print("\nOptional: Specify a release date (format: YYYY-MM-DD)") # noqa: T201 - print("- Press Enter to use today's date (default)") # noqa: T201 + print("\nOptional: Specify a release date (format: YYYY-MM-DD)") + print("- Press Enter to use today's date (default)") date_str = input("Release date (default: today): ").strip() return validate_date(date_str) @@ -174,8 +168,8 @@ def run_git_command(command): ) return result.stdout except subprocess.CalledProcessError as e: - print(f"Git command failed: {command}") # noqa: T201 - print(f"Error: {e.stderr}") # noqa: T201 + print(f"Git command failed: {command}") + print(f"Error: {e.stderr}") sys.exit(1) @@ -184,7 +178,7 @@ def update_version_py(version_str, major, minor, patch): version_file = "src/snowflake/snowpark/version.py" if not os.path.exists(version_file): - print(f"Error: {version_file} not found") # noqa: T201 + print(f"Error: {version_file} not found") sys.exit(1) with open(version_file) as f: @@ -200,7 +194,7 @@ def update_version_py(version_str, major, minor, patch): with open(version_file, "w") as f: f.write(new_content) - print(f"āœ“ Updated {version_file}") # noqa: T201 + print(f"āœ“ Updated {version_file}") def update_meta_yaml(version_str): @@ -208,7 +202,7 @@ def update_meta_yaml(version_str): meta_file = "recipe/meta.yaml" if not os.path.exists(meta_file): - print(f"Error: {meta_file} not found") # noqa: T201 + print(f"Error: {meta_file} not found") sys.exit(1) with open(meta_file) as f: @@ -224,7 +218,7 @@ def update_meta_yaml(version_str): with open(meta_file, "w") as f: f.write(new_content) - print(f"āœ“ Updated {meta_file}") # noqa: T201 + print(f"āœ“ Updated {meta_file}") def update_changelog(version_str, release_date): @@ -232,7 +226,7 @@ def update_changelog(version_str, release_date): changelog_file = "CHANGELOG.md" if not os.path.exists(changelog_file): - print(f"Error: {changelog_file} not found") # noqa: T201 + print(f"Error: {changelog_file} not found") sys.exit(1) with open(changelog_file) as f: @@ -246,11 +240,11 @@ def update_changelog(version_str, release_date): new_content = re.sub(first_version_pattern, replacement, content, count=1) if new_content == content: - print(f"Warning: No version entry found in {changelog_file}") # noqa: T201 + print(f"Warning: No version entry found in {changelog_file}") else: with open(changelog_file, "w") as f: f.write(new_content) - print(f"āœ“ Updated {changelog_file}") # noqa: T201 + print(f"āœ“ Updated {changelog_file}") def update_test_files(major, minor, patch): @@ -258,7 +252,7 @@ def update_test_files(major, minor, patch): test_dir = "tests/ast/data" if not os.path.exists(test_dir): - print(f"Error: {test_dir} not found") # noqa: T201 + print(f"Error: {test_dir} not found") sys.exit(1) # Get both .test and .test.DISABLED files @@ -266,9 +260,7 @@ def update_test_files(major, minor, patch): test_files.extend(glob.glob(os.path.join(test_dir, "*.test.DISABLED"))) if not test_files: - print( # noqa: T201 - f"Warning: No .test or .test.DISABLED files found in {test_dir}" - ) + print(f"Warning: No .test or .test.DISABLED files found in {test_dir}") return # Determine client_version format based on patch version @@ -304,9 +296,7 @@ def update_test_files(major, minor, patch): f.write(new_content) updated_count += 1 - print( # noqa: T201 - f"āœ“ Updated {updated_count} .test and .test.DISABLED files in {test_dir}" - ) + print(f"āœ“ Updated {updated_count} .test and .test.DISABLED files in {test_dir}") def main(): @@ -321,21 +311,15 @@ def main(): # In non-interactive mode, --version is required if not is_interactive and not args.version: - print( - "Error: --version is required when using non-interactive mode" - ) # noqa: T201 + print("Error: --version is required when using non-interactive mode") sys.exit(1) if is_interactive: - print("Snowpark Python Release Preparation Script") # noqa: T201 - print("==========================================") # noqa: T201 + print("Snowpark Python Release Preparation Script") + print("==========================================") else: - print( - "Snowpark Python Release Preparation Script (Non-Interactive Mode)" - ) # noqa: T201 - print( - "==================================================================" - ) # noqa: T201 + print("Snowpark Python Release Preparation Script (Non-Interactive Mode)") + print("==================================================================") # Check if we're in the right directory check_snowpark_directory() @@ -343,42 +327,38 @@ def main(): # Get version input version_result = get_version_input(args, is_interactive) if not version_result: - print("Error: Version is required") # noqa: T201 + print("Error: Version is required") sys.exit(1) version_str, major, minor, patch = version_result # Get release date release_date = get_release_date_input(args, is_interactive) if is_interactive or args.release_date: - print(f"\nRelease date: {release_date}") # noqa: T201 + print(f"\nRelease date: {release_date}") - print(f"\nPreparing release for version {version_str}") # noqa: T201 - print(f"Major: {major}, Minor: {minor}, Patch: {patch}") # noqa: T201 + print(f"\nPreparing release for version {version_str}") + print(f"Major: {major}, Minor: {minor}, Patch: {patch}") is_patch_release = patch != 0 - print( # noqa: T201 - f"Release type: {'Patch' if is_patch_release else 'Major/Minor'}" - ) + print(f"Release type: {'Patch' if is_patch_release else 'Major/Minor'}") # Get base reference for release branch base_ref, base_description = get_base_reference(args, is_interactive) # Checkout release branch branch_name = f"release-v{version_str}" - print( # noqa: T201 - f"\nšŸ”€ Creating release branch: {branch_name} from {base_description}" - ) + print(f"\nšŸ”€ Creating release branch: {branch_name} from {base_description}") if base_ref == "origin/main": # Fetch origin/main and create branch from it - print("Fetching latest origin/main...") # noqa: T201 + print("Fetching latest origin/main...") run_git_command("git fetch origin main") run_git_command(f"git checkout -b {branch_name} origin/main") else: # Create branch from specified commit or branch run_git_command(f"git checkout -b {branch_name} {base_ref}") - print("\nšŸ“ Updating version files...") # noqa: T201 + print("\nšŸ“ Updating version files...") # Update files update_version_py(version_str, major, minor, patch) @@ -386,7 +366,7 @@ def main(): update_changelog(version_str, release_date) update_test_files(major, minor, patch) - print( # noqa: T201 + print( f"\nšŸŽ‰ Release preparation completed successfully!" f"\n\nNext steps:" f"\n1. Sync the dependency updates in version.py and meta.yaml if they are inconsistent" From e2165c0671ee77d9189210d011c7d79a6419b1b7 Mon Sep 17 00:00:00 2001 From: Adam Ling Date: Fri, 12 Sep 2025 14:13:22 -0700 Subject: [PATCH 8/8] Apply suggestions from code review Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> --- scripts/release/prepare_release_branch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/release/prepare_release_branch.py b/scripts/release/prepare_release_branch.py index b835b19999..2d78221900 100755 --- a/scripts/release/prepare_release_branch.py +++ b/scripts/release/prepare_release_branch.py @@ -105,7 +105,7 @@ def validate_base_ref(base_ref): """Validate and format base reference""" if not base_ref or base_ref == "origin/main": return "origin/main", "origin/main" - elif re.match(r"^[a-f0-9]{7,40}$", base_ref): + elif re.match(r"^[a-fA-F0-9]{7,40}$", base_ref): return base_ref, f"commit {base_ref}" else: return base_ref, f"branch {base_ref}"