Introduction
Effective versioning, release management, and changelog generation are crucial aspects of maintaining a Python package. These tasks can be time-consuming and error-prone if done manually. In this article, we'll explore how to streamline the release process using Semantic Release and GitHub Actions, ensuring automated versioning, changelog generation, and PyPI release.
What is Semantic Release?
Semantic Release is a tool that automates versioning, generates changelogs, and releases packages based on the Semantic Versioning specification. It analyzes commit messages using conventional commit to determine the type of version bump (major, minor, or patch) and generates a new version number, release the version, and create changelogs accordingly.
Understanding Semantic Versioning
Semantic Versioning, or SemVer, is a versioning system that brings clarity and consistency to the way software versions are assigned and incremented. It follows a three-part version number format: MAJOR.MINOR.PATCH. Each component has a specific meaning:
- MAJOR version: Increased for each incompatible API changes introduced.
- MINOR version: Added for each backward-compatible functionality enhancements.
- PATCH version: Incremented for backward-compatible bug fixes without additional functionality added.
Understanding Conventional Commit
Conventional Commits, as the name suggests, adhere to a convention when structuring commit messages. The specification follows a simple yet powerful pattern:
<type>([optional scope]): <description>
[optional body]
[optional footer]
- Type: Describes the purpose of the commit (e.g.,
feat
for a new feature,fix
for a bug fix). - Scope: Indicates the module or area of the project impacted by the commit.
- Description: A concise and clear statement of what the commit accomplishes.
- Body: Additional details providing context and reasoning behind the change (optional).
- Footer: Used to reference issues, breaking changes, or other metadata (optional).
Example 1: New Feature with Scope and Breaking Change
feat(api): add support for GraphQL queries
BREAKING CHANGE: The existing REST endpoints are deprecated and
will be removed in the next release.
Update your clients to use the new GraphQL queries.
Example 2: Bug Fix with Detailed Explanation
fix(database): resolve null pointer exception in query parsing
The previous implementation did not handle null values in query parameters,
leading to a null pointer exception during query parsing.
This fix addresses the issue #5 and ensures proper handling of null values.
Example Workflow:
Let's consider a practical example of applying Conventional Commits in a Python project:
1. Initialize Project Folder
We will use poetry to initialize our Python package using poetry new <package_name>
command with simple greet
function
2. Initialize Github Repository
# Change to <package_name> directory
cd <package_name>
git init
Create a Python file named <package_name>/greet.py
with the following content:
def greet(name):
return f"Hello, {name}!"
3. Add Semantic Release Github Action
Create a github workflow file and save it as .github/workflows/ci.yml
on your repository
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
Release:
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, 'chore(release):')
runs-on: ubuntu-latest
steps:
- uses: actions/setup-python@v3
with:
python-version: 3.8.17
- name: Checkout Code
uses: actions/checkout@v3
- name: Install Python Poetry
uses: abatilo/actions-poetry@v2.1.0
with:
poetry-version: 1.5.1
- name: Semantic Release
uses: bjoluc/semantic-release-config-poetry@v2
with:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
RELEASE_BRANCH: main
This GitHub Actions workflow triggers on every push and pull request to the main
branch. It sets up Python, installs dependencies (if any), and runs Semantic Release.
Your directory should look like this now
.
├── .github
│ └── workflows
│ └── ci.yml
├── README.md
├── pyproject.toml
├── semver_example
│ ├── __init__.py
│ └── greet.py
└── tests
└── __init__.py
4 directories, 6 files
4. Configure GitHub Secrets and Workflow Permission
Create an API token for PyPI and GitHub personal API token with sufficient privilege
To securely store your GitHub and PyPI tokens, go to your GitHub repository, navigate to Settings
> Secrets
, and add GH_TOKEN
and PYPI_TOKEN
secret using token value created from previous step.
Make sure workflow permissions (Settings
> Code and automation
> Action
> General
> Workflow permissions
) has been set to read and write permissions
5. Push your package to Github
git init
git add *
git commit -m "feat: add greet function"
git branch -M main
git remote add origin https://github.com/<YOUR_REPOSITORY>
git push -u origin main
Soon you will see a release with changelogs generated from our commit message. Pretty cool right?
6. Add Changes to Your Repository
Let's create another function and save it under <package_name>/bye.py
def bye(name):
return f"Goodbye, {name}!"
# pull auto generated files from github actions first
git pull
git add *
git commit -m "feat: add bye function"
git push -u origin main
You will then notice we have another release with minor version number increased as we create new functionality without breaking any backward compatibility
Moving forward, we can continue to use conventional commits in our repository to automate versioning, changelog generation, and PyPI releases. Check out this sample repository to see them in action
Conclusion
By integrating Semantic Release and GitHub Actions into your Python package development workflow, you can automate versioning, changelog generation, and PyPI releases. This ensures a consistent and error-free release process, allowing you to focus on coding rather than managing releases. Embrace automation and enhance the reliability of your package releases today!