diff --git a/.github/scripts/get_latest_changelog.py b/.github/scripts/get_latest_changelog.py
old mode 100644
new mode 100755
index de82b07..dd1727a
--- a/.github/scripts/get_latest_changelog.py
+++ b/.github/scripts/get_latest_changelog.py
@@ -26,14 +26,14 @@ def main() -> None:
if re.match(rf"{version}", line) or re.match(r"=", line):
correct_version = True
continue
- elif re.match(r"v\d+\.\d+\.\d+", line):
+ if re.match(r"v\d+\.\d+\.\d+", line):
# encountered next version
break
if correct_version:
changes += line
- print(convert_rst_to_md(changes).strip())
+ print(convert_rst_to_md(changes).strip()) # noqa: T201
def convert_rst_to_md(text: str) -> str:
diff --git a/.github/scripts/prep_release.py b/.github/scripts/prep_release.py
old mode 100644
new mode 100755
index 0d4ee11..4c55b98
--- a/.github/scripts/prep_release.py
+++ b/.github/scripts/prep_release.py
@@ -9,17 +9,23 @@
(pytest's script I stole a lot from)
"""
+from __future__ import annotations
+
import argparse
-import datetime
+import datetime as dt
import pathlib
import re
+import shutil
import subprocess
from textwrap import indent
+from typing import TYPE_CHECKING
import github3
-from github3.repos import Repository
-SLUG = "MoeMusic/moe_{plugin name}"
+if TYPE_CHECKING:
+ from github3.repos import Repository
+
+SLUG = "MoeMusic/Moe"
PR_BODY = """
This pull request was created automatically by a manual trigger.
@@ -67,7 +73,7 @@ class Commit:
title=(?P
.+);\s
body=(?P.*);
""",
- re.VERBOSE | re.S,
+ re.VERBOSE | re.DOTALL,
) # based on "git_log_format defined below"
def __init__(self, commit_log: str) -> None:
@@ -77,7 +83,10 @@ def __init__(self, commit_log: str) -> None:
self.breaking = False
match = re.match(self.COMMIT_RE, commit_log)
- assert match
+ if not match:
+ err_msg = f"Could not parse commit log: '{commit_log}'"
+ raise ValueError(err_msg)
+
self.commit_hash = match["hash"]
title = match["title"]
self.body = match["body"].strip()
@@ -110,37 +119,52 @@ def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("token")
+ git_path = shutil.which("git")
+ if not git_path:
+ err_msg = "Could not find 'git' executable in PATH."
+ raise RuntimeError(err_msg)
+
args = parser.parse_args()
- prepare_release_pr(args.token)
+ prepare_release_pr(args.token, git_path)
-def prepare_release_pr(token: str) -> None:
+def prepare_release_pr(token: str, git_path: str) -> None:
"""Prepares the release pull request."""
- subprocess.run(["git", "config", "user.name", "Moe bot"], check=True)
- subprocess.run(["git", "config", "user.email", "<>"], check=True)
+ subprocess.run([git_path, "config", "user.name", "Moe bot"], check=True) # noqa: S603 git is a trusted executable
+ subprocess.run([git_path, "config", "user.email", "<>"], check=True) # noqa: S603 git is a trusted executable
- old_version = subprocess.run(
- ["cz", "version", "--project"], text=True, capture_output=True, check=True
+ cz_path = shutil.which("cz")
+ if not cz_path:
+ err_msg = "Could not find 'cz' executable in PATH."
+ raise RuntimeError(err_msg)
+
+ old_version = subprocess.run( # noqa: S603 trusted inputs
+ [cz_path, "version", "--project"], text=True, capture_output=True, check=True
).stdout
old_version = f"v{old_version.strip()}"
- subprocess.run(["cz", "bump", "--files-only"], check=True)
- new_version = subprocess.run(
- ["cz", "version", "--project"], text=True, capture_output=True, check=True
+ subprocess.run([cz_path, "bump", "--files-only"], check=True) # noqa: S603 trusted inputs
+ new_version = subprocess.run( # noqa: S603 trusted inputs
+ [cz_path, "version", "--project"], text=True, capture_output=True, check=True
).stdout
new_version = f"v{new_version.strip()}"
- generate_changelog(old_version, new_version)
+ generate_changelog(old_version, new_version, git_path)
release_branch = f"release_{new_version}"
- subprocess.run(["git", "checkout", "-b", f"{release_branch}"], check=True)
+ subprocess.run([git_path, "checkout", "-b", f"{release_branch}"], check=True) # noqa: S603 git is a trusted executable
- subprocess.run(["git", "commit", "-a", "-m", f"release: {new_version}"], check=True)
+ subprocess.run( # noqa: S603 git is a trusted executable
+ [git_path, "commit", "-a", "-m", f"release: {new_version}"], check=True
+ )
oauth_url = f"https://{token}:x-oauth-basic@github.com/{SLUG}.git"
- subprocess.run(
- ["git", "push", oauth_url, f"HEAD:{release_branch}", "--force"], check=True
+ subprocess.run( # noqa: S603 git is a trusted executable
+ [git_path, "push", oauth_url, f"HEAD:{release_branch}", "--force"], check=True
)
repo = login(token)
- assert repo
+ if not repo:
+ err_msg = f"Failed to get repository '{SLUG}'."
+ raise RuntimeError(err_msg)
+
repo.create_pull(
f"Prepare release {new_version}",
base="main",
@@ -149,24 +173,23 @@ def prepare_release_pr(token: str) -> None:
)
-def generate_changelog(old_version: str, new_version: str) -> None:
+def generate_changelog(old_version: str, new_version: str, git_path: str) -> None:
"""Generates a changelog for a new release."""
changelog_path = pathlib.Path("CHANGELOG.rst")
- release_title = f"{new_version} ({datetime.date.today()})"
+ release_title = f"{new_version} ({dt.datetime.now(dt.timezone.utc).date()})"
changelog_title = f"\n{release_title}\n"
changelog_title += "=" * len(release_title) + "\n"
field_delim = ";"
commit_delim = ";|;"
git_log_format = (
- f"hash=%H{field_delim} title=%s{field_delim} "
- f"body=%b{field_delim}{commit_delim}"
+ f"hash=%H{field_delim} title=%s{field_delim} body=%b{field_delim}{commit_delim}"
)
git_log = str(
- subprocess.run(
+ subprocess.run( # noqa: S603 git is a trusted executable
[
- "git",
+ git_path,
"log",
f"--pretty=format:{git_log_format}",
f"{old_version}..HEAD",
@@ -222,7 +245,10 @@ def generate_changelog(old_version: str, new_version: str) -> None:
def login(token: str) -> Repository | None:
"""Logins to github and returns the working repository."""
github = github3.login(token=token)
- assert github
+ if not github:
+ err_msg = "Failed to login to GitHub. Check the provided token."
+ raise RuntimeError(err_msg)
+
owner, repo = SLUG.split("/")
return github.repository(owner, repo)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a19b56a..911033d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,21 +1,11 @@
default_stages: [pre-commit]
repos:
- - repo: local
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: v0.12.1
hooks:
- - id: black
- name: black
- entry: black
- language: system
- types: [python]
-
- - repo: local
- hooks:
- - id: flake8
- name: flake8
- entry: flake8
- language: system
- types: [python]
+ - id: ruff-check
+ - id: ruff-format
- repo: local
hooks:
@@ -25,14 +15,6 @@ repos:
language: system
stages: [commit-msg]
- - repo: local
- hooks:
- - id: isort
- name: isort
- entry: isort
- language: system
- types: [python]
-
- repo: local
hooks:
- id: pyright
diff --git a/pyproject.toml b/pyproject.toml
index 9ef07bc..8cb24bd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -18,21 +18,11 @@ pytest-cov = "^6.0.0"
tox = "^4.0.0"
[tool.poetry.group.lint.dependencies]
-black = "^24.0.0"
+ruff = "==0.12.1"
commitizen = "^3.0.0"
-darglint = "^1.8.1"
-flake8 = "^7.0.0"
-flake8-alphabetize = "^0.0.19"
-flake8-bugbear = "^24.0.0"
-flake8-comprehensions = "^3.10.0"
-flake8-docstrings = "^1.5.0"
-flake8-pytest-style = "^2.0.0"
-flake8-use-fstring = "^1.1"
"github3.py" = "^4.0.0"
-isort = "^5.10.1"
-mccabe = "^0.7.0"
pre-commit = "^4.0.0"
-pyright = "^1.1.267"
+pyright = "==1.1.402"
[tool.poetry.group.docs.dependencies]
furo = "*"
@@ -55,9 +45,6 @@ bump_pattern = '^(build|deprecate|feat|fix|perf)'
bump_map = { feat = "MINOR", build = "PATCH", deprecate = "PATCH", fix = "PATCH", perf = "PATCH" }
schema_pattern = '(build|ci|deprecate|docs|feat|fix|perf|refactor|release|style|test)(\(\w+\))?!?:\s\S.*'
-[tool.isort]
-profile = 'black'
-
[tool.pytest.ini_options]
log_cli_level = 10
addopts = "--color=yes"
@@ -74,6 +61,34 @@ exclude = [
]
pythonPlatform = "All"
+[tool.ruff.lint]
+select = ["ALL"]
+ignore = [
+ "COM", # the formatter handles trailing commas
+ "G004", # use f strings in logging for a consistent string format
+ "PYI056", # use extend on __all__
+ "RET501", # don't care about return None semantics
+ "RET502", # don't care about return None semantics
+ "RET503", # don't care about return None semantics
+]
+
+[tool.ruff.lint.per-file-ignores]
+"docs/conf.py" = [
+ "ERA001", # conf.py includes commented config examples
+ "INP001", # docs directory isn't a python package
+]
+"tests/*" = [
+ "ANN", # tests don't need type annotations
+ "ARG", # arguments in pytest are fixtures
+ "C90", # tests are as complex as they need to be
+ "INP001", # tests don't need __init__
+ "RET", # tests don't need returns
+ "S", # asserts used in tests
+]
+
+[tool.ruff.lint.pydocstyle]
+convention = "google"
+
[build-system]
requires = ["poetry-core>=1.0"]
build-backend = "poetry.core.masonry.api"
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 4298a81..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,49 +0,0 @@
-[flake8]
-max-line-length = 88
-docstring-convention = google
-docstring_style = google
-strictness = long
-max-complexity = 10
-use_class_attributes_order_strict_mode = True
-select =
- AZ400, # alphabetize __all__ list
- B, # bugbear
- B9, # bugbear opinionated errors; disabled by default
- C4 # comprehensions
- C90, # mccabe
- CCE, # class attributes order
- D, # docstrings
- DAR, # darglint (another docstring linter)
- E, # flake8 and pycodestyle error
- F, # pyflakes
- FS, # use-fstring
- N8, # pep8-naming
- PT, # pytest style
- W6, # pycodestyle deprecation warnings
-ignore =
- # allow `@pytest.fixture` (no parens)
- PT001,
- # allow `@pytest.mark.foo` (no parens)
- PT023,
- # bug-bear line length; just use flake8 line length limit
- B950,
- # whitespace before ':' (black conflict)
- E203,
- # Excess exceptions in "Raises" (I like to document sub-private-methods)
- DAR402,
- # too many leading '#' for block comment
- E266,
-per-file-ignores =
- # E800: config file has commented out code as examples
- alembic/env.py:E800
- # D1: database migrations don't need docstrings
- # I: isort errors tend to misinterpret alembic as a local package
- alembic/versions/*.py:D1,I
- # B907: manually specified quotes are usually intentional for testing purposes
- # C901: don't check complexity for tests
- # DAR101: pytest uses fixtures as arguments, documenting each use is unnecessary
- # DAR102: factory arguments with fixtures can be weird
- tests/*:B907,C901,DAR101,DAR102
- # F401: import unused
- # F403: allow * imports (used for packaging in some cases)
- */__init__.py:F401,F403