Skip to content
This repository has been archived by the owner on Jun 23, 2020. It is now read-only.

Commit

Permalink
Support uploading to PyPI (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
brettcannon authored Mar 31, 2020
1 parent 418e3db commit 75204b3
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 45 deletions.
6 changes: 1 addition & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,13 @@ on:
pull_request:
types: [opened, synchronize, reopened, closed]

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
test:
if: github.event.action != 'closed' || github.event.pull_request.merged
# The type of runner that the job will run on

runs-on: ubuntu-latest

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2

- uses: actions/setup-python@v1
Expand Down
95 changes: 58 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,73 @@
# release-often
A GitHub Action for releasing a Python project to PyPI after every relevant, merged PR.

The purpose of this action is to make project maintenance as easy as possible for PyPI-hosted projects by removing the need to decide when to release. By releasing after every relevant PR is merged, not only is the question of whether to release gone, but the overhead of remembering how to even do a release and then preparing one is also gone. It also allows for releases to occur in situations where you may not have easy access to a checkout or machine setup to make a release (e.g. merging a PR from your phone). As well, contributors will be able to benefit from their hard work much faster than having to wait for the gathering of multiple changes together into a single release.
The purpose of this action is to make project maintenance as easy as possible for PyPI-hosted projects by removing the need to decide when to release. By releasing after every relevant PR is merged, not only is the question of whether to release gone, but the overhead of remembering how to even do a release and then preparing one is also gone. It also allows for releases to occur in situations where you may not have easy access to a checkout or machine set up to make a release (e.g. merging a PR from your phone). As well, contributors will be able to benefit from their hard work much faster than having to wait for the gathering of multiple changes together into a single release.

Do note that this action is not designed to work for all projects. There are legitimate reasons to bundle multiple changes in a release. This action is specifically tailored towards smaller -- typically single-maintainer -- projects where PR merges are infrequent and the release process alone makes up a sizable amount of the cost of maintenance.
Do note that this action is not designed to work for all projects. This action is very opinionated and is not expected to be a fit for all Python projects.

## Outline
1. Update the version number according to a label on the merged PR
2. Update the changelog based on the commit message
3. Commit the above updates
4. Build sdist and wheels
4. Build the sdist and wheel
5. Upload to PyPI
6. Create a release on GitHub

## Caveats
Due to the fact that the action commits back to the repository you cannot have required status checks on PRs as that prevents direct commits from non-admins.

## Action instructions
### Suggested configuration
### Configuration example
```YAML
release:
needs: [test, lint]
if: github.event_name == 'pull_request' && github.ref == 'refs/heads/master' && github.event.action == 'closed' && github.event.pull_request.merged

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: brettcannon/release-often@v1
with:
changelog-path: doc/CHANGELOG.rst
pypi-token: ${{ secrets.PYPI_TOKEN }}
github-token: ${{ secret.GITHUB_TOKEN }}
on:
push:
branches: [ master ]
pull_request:
types: [opened, synchronize, reopened, closed]

jobs:
test:
if: github.event.action != 'closed' || github.event.pull_request.merged

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- uses: actions/setup-python@v1
with:
python-version: '3.8'

# ... more steps for testing.

lint:
if: github.event.action != 'closed' || github.event.pull_request.merged

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- uses: actions/setup-python@v1
with:
python-version: '3.8'

# ... more steps for linting.

release:
needs: [test, lint]
if: github.event_name == 'pull_request' && github.ref == 'master' && github.event.action == 'closed' && github.event.pull_request.merged

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- uses: brettcannon/release-often@v1
with:
changelog-path: doc/CHANGELOG.rst
pypi-token: ${{ secrets.PYPI_TOKEN }}
github-token: ${{ secret.GITHUB_TOKEN }}
```
### Inputs
Expand All @@ -51,8 +87,6 @@ Leaving this input out will disable uploading to PyPI.
#### `github-token`
**Required**: The GitHub access token (i.e. `${{ secrets.GITHUB_TOKEN }}`). This allows for committing changes back to the repository.

Leaving this input out will disable committing any changes made and creating a release.


## Details
### Update version
Expand All @@ -64,12 +98,9 @@ Based on which of the following labels are applied to a PR, update the version n
- `impact:post-release` to bump the `post` version
- `impact:project` to make no change

Input:
- build tool

Supported build tools:
- poetry
- flit
- [Poetry](https://pypi.org/project/poetry/)
- [flit](https://pypi.org/project/flit/)

### Update the changelog
The first line of the commit message is used as the entry in the changelog for the change. The PR and the author of the change are mentioned as part of the changelog entry.
Expand All @@ -82,27 +113,17 @@ Supported changelog formats are:
- `.rst`


### Build project

Build the project's release artifacts.

#### TODO
- Build sdist and wheel via [pep517](https://pypi.org/project/pep517/)
### Build sdist and wheel
Build the project's sdist and wheel using [PEP 517](https://www.python.org/dev/peps/pep-0517/).

### Commit the changes
Once the above changes are made and the build artifacts can be successfully built, commit the changes that were made.

#### TODO
- Allow for specifying the author details?

### Upload to PyPI
With the checkout and repository in the appropriate state for release, the code can now be built and pushed to PyPI.

Input:
- PyPI token

#### TODO
- Upload via twine
- PyPI API token

### Create a release on GitHub
Finally, when everything is live, create a release on GitHub to both tag the release in the repository and store the artifacts uploaded to PyPI. The name of the release is the version prepended by `v` and the body of the release is the changelog entry.
Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ inputs:
runs:
using: 'docker'
image: 'Dockerfile'
args: ['--changelog-path', '${{ inputs.changelog-path }}']
args: ['--changelog-path', '${{ inputs.changelog-path }}', '--pypi-token', '${{ inputs.pypi-token }}']
17 changes: 15 additions & 2 deletions release_often/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def error(message):
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("--changelog-path", dest="changelog_path")
parser.add_argument("--pypi-token", dest="pypi_token")
return parser.parse_args()


Expand Down Expand Up @@ -81,15 +82,27 @@ def commit(new_version):
subprocess.run([GIT, "push"], check=True)


def upload(output_dir, pypi_token):
env = {"TWINE_USERNAME": "__token__", "TWINE_PASSWORD": pypi_token}
subprocess.run(
["twine", "upload", "--non-interactive", f"{output_dir}/*"], check=True, env=env
)


if __name__ == "__main__":
args = parse_args()
new_version = update_version()
if new_version is None:
actions.command("debug", "No version update requested")
sys.exit()
update_changelog(pathlib.Path(args.changelog_path), new_version)
changelog_entry = update_changelog(pathlib.Path(args.changelog_path), new_version)
output_dir = build()
commit(new_version)
# XXX Upload to PyPI
if args.pypi_token != "-":
upload(output_dir, args.pypi_token)
else:
actions.command("debug", "PyPI uploading skipped; no API token provided")
# XXX Create a release on GitHub
# https://developer.github.com/v3/repos/releases/
# release(new_version, changelog_entry, output_dir)
pass

0 comments on commit 75204b3

Please sign in to comment.