Skip to content

Conversation

@jonathan343
Copy link
Contributor

@jonathan343 jonathan343 commented Jan 4, 2026

Note

These scripts are adapted from smithy-lang/smithy-python#552, with the addition of support for release summary entries (SUMMARY.json).

Summary

  • Add Python scripts for managing per-package changelogs in a structured, automation-friendly format.
  • Backfill changelog entries for existing client packages (aws-sdk-bedrock-runtime, aws-sdk-sagemaker-runtime-http2, aws-sdk-transcribe-streaming).

Changelog Tooling

This PR introduces three scripts under scripts/changelog/:

Script Purpose
new-entry.py Create individual changelog entries stored in .changes/next-release/
new-release.py Consolidate pending entries into a versioned JSON file (e.g., 0.3.0.json)
render.py Generate CHANGELOG.md from version JSON files using a Jinja2 template

Workflow

  1. Contributors add entries via new-entry.py --package <name> --type <type> --description "..."
  2. At release time, new-release.py consolidates entries into <version>.json
  3. render.py regenerates CHANGELOG.md from all version files

Test plan

  • Verify new-entry.py creates entries in the correct location
  • Verify new-release.py consolidates entries and cleans up next-release/
  • Verify render.py generates accurate CHANGELOG.md output
  • Confirm rendered changelogs match the JSON source data

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

- new-entry.py allows for creating entried for a specific package, type, and a description.
- new-release.py consolidates new entries into a versioned json file for preservation.
- render.py will generate a CHANGELOG.md file using the versioned json files and a jinja2 template.
@jonathan343 jonathan343 marked this pull request as ready for review January 4, 2026 02:58
@jonathan343 jonathan343 requested a review from a team as a code owner January 4, 2026 02:58
Copy link

@ubaskota ubaskota left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good. I've added some comments. Please, let me know if you have any questions.

Comment on lines +28 to +34
# Get package .changes directory and ensure it exists
changes_dir = get_package_changes_dir(package_name)
changes_dir.mkdir(exist_ok=True)

# Create next-release directory for pending changes
next_release_dir = changes_dir / "next-release"
next_release_dir.mkdir(exist_ok=True)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both create_change_entry and create_summary_entry functions repeat the same logic for directory setup. Do you think they can be consolidated into a single function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1; I'm not sure I understand the reason for duplicating this logic. It's minor, but this could easily be one function:

def ensure_changes_dir(package_name):
    changes_dir = PROJECT_ROOT_DIR / "clients" / package_name / ".changes"
    changes_dir.mkdir(parents=True, exist_ok=True)
    return changes_dir

) -> str:
"""Create or update the release summary for the next release."""
# Get package .changes directory and ensure it exists
changes_dir = get_package_changes_dir(package_name)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will there be a case where the package_name is invalid? Do we need to add a validation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it doesn't exist, we'll get the following error:

FileNotFoundError: [Errno 2] No such file or directory: '/Users/gytndd/dev/GitHub/aws-sdk-python/clients/aws-sdk-bedrock-runtimes/.changes'

I can add a better error message

versions.append(file.stem)

# Sort by semantic version (oldest first)
versions.sort(key=lambda v: [int(x) for x in v.split(".")])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Would using versions.sort(key=.., reverse=True) be better than reversing the sorted list inside render_changes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either one works tbh. I'll add a reverse parameter to get_sorted_versions and provide True when we call it

# Generate unique filename
timestamp = int(time.time() * 1_000_000)
unique_id = uuid.uuid4().hex
filename = f"{package_name}-{change_type}-{timestamp}-{unique_id}.json"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For filename, timestamp is already in microseconds. Wouldn't that be enough? What is the benefit of adding uuid to it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relying on timestamps alone will never really guarantee uniqueness since our infra will be creating changelog entries in rapid succession (and in parallel in the future). See the example below:

>>> timestamps = []
>>> for i in range(1000):
...     timestamp = int(time.time() * 1_000_000)
...     if timestamp in timestamps:
...         print("Found duplicate")
...     else:
...         timestamps.append(timestamp)
...         
Found duplicate
Found duplicate
Found duplicate

We could increase the precision, however, using uuid is more robust. I decided to include the timestamp in attempt to achieve natural ordering of changelog entries when rendering them out. However, that isn't really working right now and I'll handle it in a separate PR. It's more of a nice to have.

I'm gonna remove timestamp for now and only use uuid.

},
{
"type": "dependency",
"description": "**Updated**: `smithy_aws_core[eventstream, json]` from `~=0.2.0` to `~=0.3.0`."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like an odd thing to bold to begin with, and it is less readable (for me) in both JSON and markdown.

Not a huge thing, and we shouldn't change them retroactively, but if we are going to start using markdown syntax in changelog entries, we should agree on guidelines for when and how to apply them and record them in our contributing.md file.

def get_package_changes_dir(package_name: str) -> Path:
package_path = PROJECT_ROOT_DIR / "clients" / package_name
changes_dir = package_path / ".changes"
return changes_dir
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:
Personally I don't find this any more readable than a single line function of return PROJECT_ROOT_DIR / "clients" / package_name / ".changes".

If you do or prefer this for any reason, keep it as is.

Comment on lines +28 to +34
# Get package .changes directory and ensure it exists
changes_dir = get_package_changes_dir(package_name)
changes_dir.mkdir(exist_ok=True)

# Create next-release directory for pending changes
next_release_dir = changes_dir / "next-release"
next_release_dir.mkdir(exist_ok=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1; I'm not sure I understand the reason for duplicating this logic. It's minor, but this could easily be one function:

def ensure_changes_dir(package_name):
    changes_dir = PROJECT_ROOT_DIR / "clients" / package_name / ".changes"
    changes_dir.mkdir(parents=True, exist_ok=True)
    return changes_dir

parser.add_argument(
"-t",
"--type",
# TODO: Remove the 'breaking' option once this project is stable.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought- maybe we leave this in permanently? For minor "breaks" that we will continue to do that offer more benefit than pain to customers.

One example that may be upcoming in boto is switching to using standard instead of legacy retry mode.

version_data["summary"] = summary

with open(version_file, "w") as f:
json.dump(version_data, f, indent=2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
json.dump(version_data, f, indent=2)
json.dump(version_data, f, indent=2)
f.write("\n")

Adding a newline at the end of the file will prevent git from showing the warning and help with tools that prefer files end in newlines.

entry_file.unlink()
removed_count += 1
except OSError as e:
print(f"Warning: Could not remove {entry_file}: {e}", file=sys.stderr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention here is to ignore the error quietly and continue? Do you think it's possible that this error will get ignored and leave behind the entry file and duplicate the entry for the next release?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants