Skip to content

Commit

Permalink
chore: Dropped support for Python 3.8 and use Ruff to lint (#98)
Browse files Browse the repository at this point in the history
  • Loading branch information
edgarrmondragon authored Nov 19, 2024
1 parent 1726891 commit 96997aa
Show file tree
Hide file tree
Showing 8 changed files with 361 additions and 279 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches: [ main ]
pull_request:
workflow_dispatch:

jobs:
pytest:
Expand All @@ -12,7 +13,6 @@ jobs:
strategy:
matrix:
python-version:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
Expand Down
19 changes: 5 additions & 14 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,9 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace

- repo: https://github.com/psf/black
rev: 24.10.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.7.4
hooks:
- id: black

- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort

- repo: https://github.com/asottile/pyupgrade
rev: v3.19.0
hooks:
- id: pyupgrade
args: [--py38-plus]
- id: ruff
args: [ --fix ]
- id: ruff-format
51 changes: 28 additions & 23 deletions cz_version_bump/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import os
import re
import typing as t
from collections import OrderedDict
from textwrap import dedent

Expand All @@ -12,19 +13,19 @@

from commitizen import defaults, git
from commitizen.cz.base import BaseCommitizen
from commitizen.defaults import Questions

from cz_version_bump.git import repo_name_from_git_remote
from cz_version_bump.thanks import Thanker

if t.TYPE_CHECKING:
from commitizen.defaults import Questions

issue_id_pattern = re.compile(r"\s+\(#(\d+)\)$")


class MeltanoCommitizen(BaseCommitizen):
bump_pattern = defaults.bump_pattern
bump_map = defaults.bump_map
bump_pattern = r"^(feat|fix|refactor|perf|break|docs|ci|chore|style|revert|test|build|packaging)(\(.+\))?(!)?"
bump_map = OrderedDict(
bump_pattern = r"^(feat|fix|refactor|perf|break|docs|ci|chore|style|revert|test|build|packaging)(\(.+\))?(!)?" # noqa: E501
bump_map: t.ClassVar = OrderedDict(
(
(
r"^break",
Expand All @@ -44,8 +45,8 @@ class MeltanoCommitizen(BaseCommitizen):
(r"^packaging", defaults.PATCH),
)
)
commit_parser = r"^(?P<change_type>feat|fix|refactor|perf|break|docs|packaging)(?:\((?P<scope>[^()\r\n]*)\)|\()?(?P<breaking>!)?:\s(?P<message>.*)?"
schema_pattern = r"(feat|fix|refactor|perf|break|docs|ci|chore|style|revert|test|build|packaging)(?:\((?P<scope>[^()\r\n]*)\)|\()?(?P<breaking>!)?:(\s.*)"
commit_parser = r"^(?P<change_type>feat|fix|refactor|perf|break|docs|packaging)(?:\((?P<scope>[^()\r\n]*)\)|\()?(?P<breaking>!)?:\s(?P<message>.*)?" # noqa: E501
schema_pattern = r"(feat|fix|refactor|perf|break|docs|ci|chore|style|revert|test|build|packaging)(?:\((?P<scope>[^()\r\n]*)\)|\()?(?P<breaking>!)?:(\s.*)" # noqa: E501
schema = dedent(
"""
<type>(<scope>): <subject>
Expand All @@ -55,7 +56,7 @@ class MeltanoCommitizen(BaseCommitizen):
(BREAKING CHANGE: )<footer>
"""
).strip("\n")
change_type_order = [
change_type_order = [ # noqa: RUF012
"BREAKING CHANGES",
"✨ New",
"🐛 Fixes",
Expand All @@ -64,7 +65,7 @@ class MeltanoCommitizen(BaseCommitizen):
"📚 Documentation Improvements",
"📦 Packaging changes",
]
change_type_map = {
change_type_map = { # noqa: RUF012
"break": "BREAKING CHANGES",
"feat": "✨ New",
"fix": "🐛 Fixes",
Expand All @@ -74,14 +75,14 @@ class MeltanoCommitizen(BaseCommitizen):
"packaging": "📦 Packaging changes",
}

def __init__(self, *args, **kwargs):
def __init__(self: MeltanoCommitizen, *args: t.Any, **kwargs: t.Any) -> None:
super().__init__(*args, **kwargs)
self.repo_name = os.environ.get(
"GITHUB_REPOSITORY", repo_name_from_git_remote()
)
self.thanker = Thanker(self.repo_name)

def questions(self) -> Questions:
def questions(self: MeltanoCommitizen) -> Questions:
"""Questions regarding the commit message."""
return [
{
Expand All @@ -92,7 +93,7 @@ def questions(self) -> Questions:
{"value": "fix", "name": "fix: A bug fix."},
{
"value": "refactor",
"name": "refactor: A code change that neither fixes a bug nor adds a feature.",
"name": "refactor: A code change that neither fixes a bug nor adds a feature.", # noqa: E501
},
{
"value": "perf",
Expand All @@ -102,7 +103,7 @@ def questions(self) -> Questions:
{"value": "break", "name": "break: A breaking change."},
{
"value": "chore",
"name": "chore: A change that doesn't affect the meaning of the codebase.",
"name": "chore: A change that doesn't affect the meaning of the codebase.", # noqa: E501
},
{"value": "style", "name": "style: A code style change."},
{"value": "revert", "name": "revert: Revert to a commit."},
Expand All @@ -111,7 +112,7 @@ def questions(self) -> Questions:
{"value": "ci", "name": "ci: A change to CI/CD."},
{
"value": "packaging",
"name": "packaging: A change to how the project is packaged or distributed.",
"name": "packaging: A change to how the project is packaged or distributed.", # noqa: E501
},
],
"message": "Select the type of change you are committing",
Expand All @@ -123,15 +124,15 @@ def questions(self) -> Questions:
},
]

def message(self, answers: dict) -> str:
def message(self: MeltanoCommitizen, answers: dict) -> str:
"""Format the git message."""
message_template = Template("{{change_type}}: {{message}}")
if getattr(Template, "substitute", None):
return message_template.substitute(**answers)
return message_template.render(**answers)

def changelog_message_builder_hook(
self,
self: MeltanoCommitizen,
parsed_message: dict[str, str],
commit: git.GitCommit,
) -> dict:
Expand All @@ -154,13 +155,13 @@ def changelog_message_builder_hook(
# Convert to int then back to str to validate that it is an integer:
issue_id = str(int(issue_id_pattern.findall(message)[0]))
message = issue_id_pattern.sub("", message)
except Exception:
except Exception: # noqa: BLE001, S110
pass
else:
# NOTE: The "issue ID" will usually be for a pull request. GitHub considers PRs to be
# issues in their APIs, but not vice versa.
# NOTE: The "issue ID" will usually be for a pull request. GitHub considers
# PRs to be issues in their APIs, but not vice versa.
parsed_message["message"] = (
f"[#{issue_id}](https://github.com/{self.repo_name}/issues/{issue_id}) {message}"
f"[#{issue_id}](https://github.com/{self.repo_name}/issues/{issue_id}) {message}" # noqa: E501
)

# Remove the scope because we are too inconsistent with them.
Expand All @@ -169,13 +170,17 @@ def changelog_message_builder_hook(
# Thank third-party contributors:
parsed_message["message"] += self.thanker.thanks_message(commit)

# Remove the commit message body because is isn't needed for the changelog, and can cause
# formatting issues if present.
# Remove the commit message body because is isn't needed for the changelog, and
# can cause formatting issues if present.
commit.body = ""

return parsed_message

def changelog_hook(self, full_changelog: str, partial_changelog: str | None) -> str:
def changelog_hook(
self: MeltanoCommitizen,
full_changelog: str,
partial_changelog: str | None,
) -> str:
"""Perform custom action at the end of changelog generation.
full_changelog: The full changelog about to being written into the file.
Expand Down
3 changes: 2 additions & 1 deletion cz_version_bump/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@


def repo_name_from_git_remote() -> str:
git_remote_output = subprocess.run(
git_remote_output = subprocess.run( # noqa: S603
("git", "remote", "-v"),
stdout=subprocess.PIPE,
text=True,
check=False,
).stdout
github_remote = next(x for x in git_remote_output.split() if "github.com" in x)

Expand Down
45 changes: 29 additions & 16 deletions cz_version_bump/thanks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,43 @@

import os
import re
from typing import Iterable
import typing as t
from warnings import warn

from commitizen import git
from github import Auth, Consts, Github

if t.TYPE_CHECKING:
from collections.abc import Iterable

from commitizen import git


class Thanker:
co_author_pattern = re.compile(r"<(.*)>$")

def __init__(self, repo_name: str, base_url: str = Consts.DEFAULT_BASE_URL) -> None:
def __init__(
self: Thanker,
repo_name: str,
base_url: str = Consts.DEFAULT_BASE_URL,
) -> None:
github_token = os.environ.get(
"INPUT_GITHUB_TOKEN", os.environ.get("GITHUB_TOKEN", None)
)
if github_token is None:
warn(
"No GitHub token provided - changelog may include "
"thanks for first-party contributors"
"thanks for first-party contributors",
stacklevel=2,
)
self.agent = Github(auth=Auth.Token(github_token), base_url=base_url)
self.repo = self.agent.get_repo(repo_name)
# NOTE: The org object obtained from `self.repo.organization` has the wrong URL, so we
# retrieve it using `get_organization` instead to get one that isn't broken.
# NOTE: The org object obtained from `self.repo.organization` has the wrong URL,
# so we retrieve it using `get_organization` instead to get one that isn't
# broken.
self.org = self.agent.get_organization(self.repo.organization.login)
self.org_members = {member.login for member in self.org.get_members()}

def thanks_message(self, commit: git.GitCommit) -> str:
def thanks_message(self, commit: git.GitCommit) -> str: # noqa: ANN101
usernames = [
f"@{username}" for username in self.third_party_contributors(commit)
]
Expand All @@ -41,18 +51,19 @@ def thanks_message(self, commit: git.GitCommit) -> str:
return template.format(usernames[0])
return template.format(f"{', '.join(usernames[:-1])}, and {usernames[-1]}")

def third_party_contributors(self, commit: git.GitCommit) -> Iterable[str]:
def third_party_contributors(self: Thanker, commit: git.GitCommit) -> Iterable[str]:
for contributor in self.contributors(commit):
if contributor not in self.org_members:
yield contributor

def contributors(self, commit: git.GitCommit) -> Iterable[str]:
def contributors(self: Thanker, commit: git.GitCommit) -> Iterable[str]:
github_commit = self.repo.get_commit(commit.rev)
yield github_commit.author.login
# FIXME: Cannot thank co-authors automatically until `email_to_github_username` is implemented.
# yield from self.co_authors(github_commit.commit.message)
# FIXME: Cannot thank co-authors automatically until `email_to_github_username`
# is implemented.
# yield from self.co_authors(github_commit.commit.message) # noqa: ERA001

def co_authors(self, commit_message: str) -> Iterable[str]:
def co_authors(self: Thanker, commit_message: str) -> Iterable[str]:
co_author_lines = {
line
for line in commit_message.splitlines()
Expand All @@ -64,8 +75,10 @@ def co_authors(self, commit_message: str) -> Iterable[str]:
)

# TODO: This method should use memoization - https://pypi.org/project/methodtools/
def email_to_github_username(self, email: str) -> str:
# TODO: Find a reliable way to get the GitHub username linked to a given email address.
# The user search API was tried, but it cannot find many users given their public primary
# GitHub emails. I'm not sure why it is not reliable, or what conditions make it work.
def email_to_github_username(self: Thanker, email: str) -> str:
# TODO: Find a reliable way to get the GitHub username linked to a given email
# address.
# The user search API was tried, but it cannot find many users given their
# public primary GitHub emails. I'm not sure why it is not reliable, or what
# conditions make it work.
raise NotImplementedError
Loading

0 comments on commit 96997aa

Please sign in to comment.