Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
name: Release to PyPI

on:
workflow_dispatch:
inputs:
version_override:
description: "Manual version override (leave empty to use semantic-release)"
required: false
type: string
dry_run:
description: "Dry run (determine version only, do not publish)"
required: false
type: boolean
default: false

jobs:
determine-version:
name: Determine Version
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: true
- uses: actions/setup-node@v4
with:
node-version: "26"
- name: Install semantic-release
run: npm install -g semantic-release @semantic-release/commit-analyzer @semantic-release/release-notes-generator @semantic-release/changelog @semantic-release/exec @semantic-release/git @semantic-release/github conventional-changelog-conventionalcommits
- name: Determine next version
id: version
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ -n "${{ inputs.version_override }}" ]; then
echo "version=${{ inputs.version_override }}" >> "$GITHUB_OUTPUT"
echo "Using manual override: ${{ inputs.version_override }}"
else
# Run semantic-release in dry-run to get the next version
VERSION=$(npx semantic-release --dry-run 2>&1 | grep -oP 'The next release version is \K[0-9]+\.[0-9]+\.[0-9]+' || true)
if [ -z "$VERSION" ]; then
echo "No release needed based on commits"
echo "version=" >> "$GITHUB_OUTPUT"
else
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Semantic release determined version: $VERSION"
fi
fi

test:
name: Run Tests
needs: determine-version
Comment on lines +46 to +56
if: needs.determine-version.outputs.version != ''
uses: ./.github/workflows/python-integ.yml
permissions:
id-token: write
contents: read
secrets: inherit

build:
name: Build Package
needs: [determine-version, test]
if: needs.determine-version.outputs.version != ''
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: "3.10"
- run: pip install build
- name: Set version in pyproject.toml
run: sed -i "s/^version = .*/version = \"${{ needs.determine-version.outputs.version }}\"/" pyproject.toml
- name: Verify version
run: |
grep "version = \"${{ needs.determine-version.outputs.version }}\"" pyproject.toml
- run: python -m build
- uses: actions/upload-artifact@v7
with:
name: dist
path: dist/

publish-testpypi:
name: Publish to TestPyPI
if: ${{ !inputs.dry_run && needs.determine-version.outputs.version != '' }}
needs: [determine-version, build]
runs-on: ubuntu-latest
environment: testpypi
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v7
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
skip-existing: true

validate-testpypi:
name: Validate TestPyPI Package
needs: [determine-version, publish-testpypi]
if: needs.determine-version.outputs.version != ''
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v6
with:
sparse-checkout: release-validation
- uses: actions/setup-python@v6
with:
python-version: "3.10"
- uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::370957321024:role/S3EC-Python-Github-test-role
aws-region: us-west-2
- name: Wait for TestPyPI availability
run: |
for i in $(seq 1 30); do
if pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ "amazon-s3-encryption-client-python==${{ needs.determine-version.outputs.version }}" 2>/dev/null; then
echo "Package available on TestPyPI"
exit 0
fi
echo "Waiting for package to appear on TestPyPI ($i/30)..."
sleep 10
done
echo "Package not found on TestPyPI after 5 minutes"
exit 1
- name: Run validation
run: python release-validation/validate.py
env:
CI_S3_BUCKET: ${{ vars.CI_S3_BUCKET }}
CI_KMS_KEY_ALIAS: ${{ vars.CI_KMS_KEY_ALIAS }}

publish-pypi:
name: Publish to PyPI
needs: [determine-version, validate-testpypi]
if: needs.determine-version.outputs.version != ''
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v7
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1

validate-pypi:
name: Validate PyPI Package
needs: [determine-version, publish-pypi]
if: needs.determine-version.outputs.version != ''
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v6
with:
sparse-checkout: release-validation
- uses: actions/setup-python@v6
with:
python-version: "3.10"
- uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::370957321024:role/S3EC-Python-Github-test-role
aws-region: us-west-2
- name: Wait for PyPI availability
run: |
for i in $(seq 1 30); do
if pip install "amazon-s3-encryption-client-python==${{ needs.determine-version.outputs.version }}" 2>/dev/null; then
echo "Package available on PyPI"
exit 0
fi
echo "Waiting for package to appear on PyPI ($i/30)..."
sleep 10
done
echo "Package not found on PyPI after 5 minutes"
exit 1
- name: Run validation
run: python release-validation/validate.py
env:
CI_S3_BUCKET: ${{ vars.CI_S3_BUCKET }}
CI_KMS_KEY_ALIAS: ${{ vars.CI_KMS_KEY_ALIAS }}

create-release:
name: Create GitHub Release
if: ${{ !inputs.dry_run && needs.determine-version.outputs.version != '' }}
needs: [determine-version, validate-pypi]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: true
- uses: actions/setup-node@v4
with:
node-version: "26"
- name: Install semantic-release
run: npm install -g semantic-release @semantic-release/commit-analyzer @semantic-release/release-notes-generator @semantic-release/changelog @semantic-release/exec @semantic-release/git @semantic-release/github conventional-changelog-conventionalcommits
- name: Create release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ needs.determine-version.outputs.version }}"
if [ -n "${{ inputs.version_override }}" ]; then
# Manual override: commit the version bump and create a GitHub release
sed -i "s/^version = .*/version = \"${VERSION}\"/" pyproject.toml
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add pyproject.toml
git commit -m "chore(release): ${VERSION} [skip ci]" || true
git tag "v${VERSION}"
git push --follow-tags
gh release create "v${VERSION}" \
--title "v${VERSION}" \
--generate-notes \
--draft
else
npx semantic-release
fi
Comment on lines +215 to +230

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I don't fully understand this in either path --

  • If we specify a version then the script will automatically publish a release right? Do we do this anywhere else? I thought we prefer to have the bot make a draft then humans review and click publish
  • What does running npx semantic-release do here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The Github releases can be a draft, I'll fix that in the next revision.

npx semantic-release does this. In short it commits the version change and creates a Github release.

The branch exists because if we give a manual override, we can't use semantic-release so we have to do the various steps manually. Notably, there is no CHANGELOG update when this happens, I'll make a note to mention this in the release process.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,6 @@ smithy-java-core/out
.coverage
coverage-report/
perf-results/

# Sphinx docs build output
docs/_build/
16 changes: 16 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: 2

build:
os: ubuntu-22.04
tools:
python: "3.11"

sphinx:
configuration: docs/conf.py

python:
install:
- method: pip
path: .
extra_requirements:
- docs
70 changes: 70 additions & 0 deletions .releaserc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Semantic Release configuration for Amazon S3 Encryption Client for Python.
*
* Determines the next version from conventional commits, updates pyproject.toml,
* generates release notes, and creates a GitHub release.
*/
module.exports = {
branches: ["main"],
plugins: [
[
"@semantic-release/commit-analyzer",
{
preset: "conventionalcommits",
releaseRules: [
{ type: "feat", release: "minor" },
{ type: "fix", release: "patch" },
{ type: "perf", release: "patch" },
{ type: "revert", release: "patch" },
{ breaking: true, release: "major" },
],
},
],
[
"@semantic-release/release-notes-generator",
{
preset: "conventionalcommits",
presetConfig: {
types: [
{ type: "feat", section: "Features" },
{ type: "fix", section: "Bug Fixes" },
{ type: "perf", section: "Performance" },
{ type: "revert", section: "Reverts" },
{ type: "docs", section: "Documentation", hidden: false },
{ type: "chore", section: "Maintenance", hidden: false },
{ type: "refactor", section: "Refactoring", hidden: false },
{ type: "test", section: "Tests", hidden: true },
{ type: "ci", section: "CI", hidden: true },
],
},
},
],
[
"@semantic-release/exec",
{
prepareCmd:
'sed -i "s/^version = .*/version = \\"${nextRelease.version}\\"/" pyproject.toml',
},
],
[
"@semantic-release/changelog",
{
changelogFile: "CHANGELOG.md",
},
],
[
"@semantic-release/git",
{
assets: ["pyproject.toml", "CHANGELOG.md"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Will this include the source code by default?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If you mean the zip of source code in Github releases, that is done automatically by Github whenever a git tag exists.

message:
"chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}",
},
],
[
"@semantic-release/github",
{
draftRelease: true,
},
],
],
};
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: lint format test test-unit test-integration test-perf install
.PHONY: lint format format-check test test-unit test-integration test-perf install docs

# Default target
all: lint test duvet
Expand Down Expand Up @@ -56,3 +56,12 @@ duvet-report:
duvet-view-report-mac:
open .duvet/reports/report.html


# Build docs locally
docs:
uv pip install -e ".[docs]"
uv run sphinx-build -b html docs/ docs/_build/html
@echo "Docs built at docs/_build/html/index.html"

docs-open: docs
open docs/_build/html/index.html
35 changes: 35 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
API Reference
=============

Client
------

.. automodule:: s3_encryption
:members: S3EncryptionClient, S3EncryptionClientConfig

Materials
---------

KMS Keyring
~~~~~~~~~~~

.. automodule:: s3_encryption.materials.kms_keyring
:members:

Keyring Interface
~~~~~~~~~~~~~~~~~

.. automodule:: s3_encryption.materials.keyring
:members:

Materials
~~~~~~~~~

.. automodule:: s3_encryption.materials.materials
:members:

Exceptions
----------

.. automodule:: s3_encryption.exceptions
:members:
Loading
Loading