Skip to content

Commit

Permalink
GH Action: Set new version and publish on push (BeanieODM#961)
Browse files Browse the repository at this point in the history
* Implemented

* remove extra newline

* remove testing part from gh action script
  • Loading branch information
roman-right authored Jul 13, 2024
1 parent ff709bf commit 6c940bd
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 5 deletions.
Empty file.
91 changes: 91 additions & 0 deletions .github/scripts/handlers/gh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import subprocess
from dataclasses import dataclass
from datetime import datetime
from typing import List

import requests # type: ignore


@dataclass
class PullRequest:
number: int
title: str
user: str
user_url: str
url: str


class GitHubHandler:
def __init__(
self,
username: str,
repository: str,
current_version: str,
new_version: str,
):
self.username = username
self.repository = repository
self.base_url = f"https://api.github.com/repos/{username}/{repository}"
self.current_version = current_version
self.new_version = new_version
self.commits = self.get_commits_after_tag(current_version)
self.prs = [self.get_pr_for_commit(commit) for commit in self.commits]

def get_commits_after_tag(self, tag: str) -> List[str]:
result = subprocess.run(
["git", "log", f"{tag}..HEAD", "--pretty=format:%H"],
stdout=subprocess.PIPE,
text=True,
)
return result.stdout.split()

def get_pr_for_commit(self, commit_sha: str) -> PullRequest:
url = f"{self.base_url}/commits/{commit_sha}/pulls"
response = requests.get(url)
response.raise_for_status()
pr_data = response.json()[0]
return PullRequest(
number=pr_data["number"],
title=pr_data["title"],
user=pr_data["user"]["login"],
user_url=pr_data["user"]["html_url"],
url=pr_data["html_url"],
)

def build_markdown_for_many_prs(self) -> str:
markdown = f"\n## [{self.new_version}] - {datetime.now().strftime('%Y-%m-%d')}\n"
for pr in self.prs:
markdown += (
f"### {pr.title.capitalize()}\n"
f"- Author - [{pr.user}]({pr.user_url})\n"
f"- PR <{pr.url}>\n"
)
markdown += f"\n[{self.new_version}]: https://pypi.org/project/{self.repository}/{self.new_version}\n"
return markdown

def commit_changes(self):
self.run_git_command(
["git", "config", "--global", "user.name", "github-actions[bot]"]
)
self.run_git_command(
[
"git",
"config",
"--global",
"user.email",
"github-actions[bot]@users.noreply.github.com",
]
)
self.run_git_command(["git", "add", "."])
self.run_git_command(
["git", "commit", "-m", f"Bump version to {self.new_version}"]
)
self.run_git_command(["git", "tag", self.new_version])
self.git_push()

def git_push(self):
self.run_git_command(["git", "push", "origin", "main", "--tags"])

@staticmethod
def run_git_command(command: List[str]):
subprocess.run(command, check=True)
116 changes: 116 additions & 0 deletions .github/scripts/handlers/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import subprocess
from pathlib import Path

import requests # type: ignore
import toml
from gh import GitHubHandler


class SemVer:
def __init__(self, version: str):
self.version = version
self.major, self.minor, self.patch = map(int, self.version.split("."))

def increment_minor(self):
return SemVer(f"{self.major}.{self.minor + 1}.0")

def __str__(self):
return self.version

def __eq__(self, other):
return self.version == other.version

def __gt__(self, other):
return (
(self.major > other.major)
or (self.major == other.major and self.minor > other.minor)
or (
self.major == other.major
and self.minor == other.minor
and self.patch > other.patch
)
)


class VersionHandler:
PACKAGE_NAME = "beanie"
ROOT_PATH = Path(__file__).parent.parent.parent.parent

def __init__(self):
self.pyproject = self.ROOT_PATH / "pyproject.toml"
self.init_py = self.ROOT_PATH / "beanie" / "__init__.py"
self.changelog = self.ROOT_PATH / "docs" / "changelog.md"

self.current_version = self.parse_version_from_pyproject(
self.pyproject
)
self.pypi_version = self.get_version_from_pypi()

if self.current_version < self.pypi_version:
raise ValueError("Current version is less than pypi version")

if self.current_version == self.pypi_version:
self.current_version = self.current_version.increment_minor()
self.update_files()
else:
self.flit_publish()

@staticmethod
def parse_version_from_pyproject(pyproject: Path) -> SemVer:
toml_data = toml.loads(pyproject.read_text())
return SemVer(toml_data["project"]["version"])

def get_version_from_pypi(self) -> SemVer:
response = requests.get(
f"https://pypi.org/pypi/{self.PACKAGE_NAME}/json"
)
if response.status_code == 200:
return SemVer(response.json()["info"]["version"])
raise ValueError("Can't get version from pypi")

def update_files(self):
self.update_pyproject_version()
self.update_file_versions([self.init_py])
self.update_changelog()

def update_pyproject_version(self):
pyproject = toml.loads(self.pyproject.read_text())
pyproject["project"]["version"] = str(self.current_version)
self.pyproject.write_text(toml.dumps(pyproject))

def update_file_versions(self, files_to_update):
for file_path in files_to_update:
content = file_path.read_text()
content = content.replace(
str(self.pypi_version), str(self.current_version)
)
file_path.write_text(content)

def update_changelog(self):
handler = GitHubHandler(
"BeanieODM",
"beanie",
str(self.pypi_version),
str(self.current_version),
)
changelog_content = handler.build_markdown_for_many_prs()

changelog_lines = self.changelog.read_text().splitlines()
new_changelog_lines = []
inserted = False

for line in changelog_lines:
new_changelog_lines.append(line)
if line.strip() == "# Changelog" and not inserted:
new_changelog_lines.append(changelog_content)
inserted = True

self.changelog.write_text("\n".join(new_changelog_lines))
handler.commit_changes()

def flit_publish(self):
subprocess.run(["flit", "publish"], check=True)


if __name__ == "__main__":
VersionHandler()
11 changes: 8 additions & 3 deletions .github/workflows/github-actions-publish-project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.11
- name: install dependencies
run: pip install .[ci]
- name: install flit
run: pip3 install flit
- name: publish project
run: pip install flit
- name: update version or publish
env:
FLIT_USERNAME: __token__
FLIT_PASSWORD: ${{ secrets.FLIT_PASSWORD }}
run: flit publish
run: python .github/scripts/handlers/version.py
2 changes: 1 addition & 1 deletion .github/workflows/github-actions-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
mongodb-version: ${{ matrix.mongodb-version }}
mongodb-replica-set: test-rs
- name: install dependencies
run: pip install .[test]
run: pip install .[test,ci]
- name: install pydantic
run: pip install pydantic==${{ matrix.pydantic-version }}
- name: run tests
Expand Down
12 changes: 11 additions & 1 deletion tests/test_beanie.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
from pathlib import Path

import toml

from beanie import __version__


def parse_version_from_pyproject():
pyproject = Path(__file__).parent.parent / "pyproject.toml"
toml_data = toml.loads(pyproject.read_text())
return toml_data["project"]["version"]


def test_version():
assert __version__ == "1.26.0"
assert __version__ == parse_version_from_pyproject()

0 comments on commit 6c940bd

Please sign in to comment.