Skip to content

Commit

Permalink
hato-botのCIを反映するよ!
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] committed Dec 15, 2024
1 parent 43e0bbd commit a34c090
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 28 deletions.
17 changes: 9 additions & 8 deletions .github/workflows/pr-format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,19 @@ jobs:
# submodule: 'recursive'
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Set up Python
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
id: setup_python
- name: Get uv version
id: get_uv_version
if: github.event_name != 'pull_request' || github.event.action != 'closed'
run: echo "uv_version=$(sed -e 's/uv==//g' requirements.txt)" >> "$GITHUB_OUTPUT"
- name: Set up uv
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4.2.0
if: github.event_name != 'pull_request' || github.event.action != 'closed'
with:
python-version-file: .python-version
cache: pipenv
- if: github.event_name != 'pull_request' || github.event.action != 'closed'
run: sed -i -e "s/python_version = \".*\"/python_version = \"$(echo ${{ steps.setup_python.outputs.python-version }} | sed -e 's/\([0-9]*\.[0-9]*\).*/\1/g')\"/g" Pipfile
version: ${{steps.get_uv_version.outputs.uv_version}}
enable-cache: true
- name: Install dependencies
if: github.event_name != 'pull_request' || github.event.action != 'closed'
run: bash "${GITHUB_WORKSPACE}/scripts/pipenv_install.sh"
run: bash "${GITHUB_WORKSPACE}/scripts/uv_install.sh"
# formatする
# --exit-codeをつけることで、autopep8内でエラーが起きれば1、差分があれば2のエラーステータスコードが返ってくる。正常時は0が返る
- name: Format files
Expand Down
15 changes: 9 additions & 6 deletions .github/workflows/pr-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ jobs:
with:
submodules: "recursive"
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
- name: Get uv version
id: get_uv_version
run: echo "uv_version=$(sed -e 's/uv==//g' requirements.txt)" >> "$GITHUB_OUTPUT"
- name: Set up uv
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4.2.0
with:
python-version-file: .python-version
cache: pipenv
- name: Install pipenv
run: bash "${GITHUB_WORKSPACE}/scripts/pipenv_install.sh"
version: ${{steps.get_uv_version.outputs.uv_version}}
enable-cache: true
- name: Install uv
run: bash "${GITHUB_WORKSPACE}/scripts/uv_install.sh"
- name: Set venv path
env:
DEST_PATH: "/home/runner/work/_temp/_github_workflow/.venv"
Expand Down
1 change: 0 additions & 1 deletion .prettierignore

This file was deleted.

1 change: 0 additions & 1 deletion .python-version

This file was deleted.

2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ setuptools = "==75.6.0"

[packages]
pyperclip = ">=1.5.27"
click = "==8.1.7"


[requires]
python_version = "3.13"
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pipenv==2024.4.0
uv==0.5.9
267 changes: 267 additions & 0 deletions scripts/pr_format/pr_format/fix_pyproject.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
"""
pyproject.tomlに対して以下の修正を行う。
* pyproject.tomlでのバージョン指定がないパッケージについて、バージョン指定を実際にインストールされるものに修正する
* プロジェクト内のPythonファイルでimportされているがpyproject.toml内には存在しないパッケージをpyproject.tomlの「project.dependencies」セクションに追加する
"""

import importlib.util
import re
import sys
from pathlib import Path
from typing import NoReturn, TypeGuard

import importlib_metadata
import toml

# pyproject.tomlのproject.dependenciesやdependency-groups.devのデータ型
PyProjectDependencies = list[str]

# pyproject.tomlのデータ型
PyProject = dict[str, dict[str, PyProjectDependencies]]

version_operator_pattern = re.compile(r"[\\[<=>@]")


def is_pyproject_dependencies(
pyproject_dependencies,
) -> TypeGuard[PyProjectDependencies]:
"""
データがPyProjectDependencies型であるかを判定する
:param pyproject_dependencies: 判定対象のデータ
:return: PyProjectDependencies型であるか
"""
if not isinstance(pyproject_dependencies, list):
return False

for v in pyproject_dependencies:
if not isinstance(v, str):
return False

return True


def get_package_version(package_name: str) -> str:
"""
メタデータからパッケージのバージョンを取得する
:param package_name: 取得対象のパッケージ名
:return: パッケージのバージョン (「package_name==X.Y.Z」形式)。バージョンを取得できなかった場合はpackage_nameを返す。
"""
try:
package_data = version_operator_pattern.split(package_name)
dist = importlib_metadata.distribution(package_data[0])
except importlib_metadata.PackageNotFoundError:
return package_name

return f"{package_name}=={dist.version}"


def fix_package_version(packages: PyProjectDependencies) -> PyProjectDependencies:
"""
project.tomlでのバージョン指定がないパッケージについて、バージョン指定を実際にインストールされるものに修正する
:param packages: パッケージ一覧
:return: パッケージ一覧
"""
for i in range(len(packages)):
if not re.findall(r"[<=>@]", packages[i]):
packages[i] = get_package_version(packages[i])

return packages


def is_std_or_local_lib(project_root: Path, package_name: str) -> bool:
"""
与えられたパッケージが標準パッケージ or 独自に定義したものであるかを判定する
:param project_root: プロジェクトのルートディレクトリのパス
:param package_name: 判定対象のパッケージ名
:return: 与えられたパッケージが標準パッケージ or 独自に定義したものであるか
"""
# 与えられたパッケージがビルドインのモジュールならば標準パッケージと判定する
if package_name in sys.builtin_module_names:
return True

package_spec = None

# Finderを使ってパッケージ情報 (Spec) を取得する
for finder in sys.meta_path:
try:
package_spec = finder.find_spec(package_name, ".")
except (AttributeError, ValueError, ModuleNotFoundError):
pass

if package_spec:
break

if package_spec is None:
try:
package_spec = importlib.util.find_spec(package_name)
except (AttributeError, ValueError, ModuleNotFoundError):
pass

# パッケージ情報がないならばpyproject.tomlによってインストールされたものと判定する
if package_spec is None:
return False

# パッケージのファイルパス
package_origin = package_spec.origin

# 次のいずれかならばpyproject.tomlによってインストールされたものと判定する
# * パッケージのファイルパスが取得できない
# * パッケージのファイルパス内に「.venv」を含む
if not package_origin or ".venv" in package_origin:
return False

# パッケージのファイルパスがプロジェクト内のものであれば独自に定義したものと判定する
if project_root.resolve() in Path(package_origin).resolve().parents:
return True

# パッケージのファイルパスがPythonのシステムのパスと一致するならば標準パッケージと判定する
if package_origin.startswith(sys.base_prefix):
return True

return False


def get_imported_packages(project_root: Path) -> set[str]:
"""
プロジェクト内のPythonファイルからimportされているパッケージの一覧 (標準パッケージや独自に定義したものを除く) を取得する
:param project_root: プロジェクトのルートディレクトリのパス
:return: プロジェクト内のPythonファイル内でimportされているパッケージの一覧 (標準パッケージや独自に定義したものを除く)
"""
imported_packages: set[str] = set()

for file in project_root.glob("**/*.py"):
if (
str(file).endswith("setup.py")
or "node_modules" in str(file)
or ".venv" in str(file)
):
continue

with open(str(file), "r") as python_file:
for imported_package in re.findall(
r"^(?:import|from)\s+(\w+)", python_file.read(), re.MULTILINE
):
if imported_package != "sudden_death" and not is_std_or_local_lib(
project_root, imported_package
):
imported_packages.add(imported_package)

return imported_packages


def get_pyproject_packages(pyproject: PyProject) -> set[str] | NoReturn:
"""
pyproject.tomlからパッケージ一覧を取得する
:param pyproject: pyproject.tomlの中身
:return: pyproject.toml内のパッケージ一覧
"""
pyproject_packages: set[str] = set()

for pyproject_dependencies in [
pyproject["project"]["dependencies"],
pyproject["dependency-groups"]["dev"],
]:
if not is_pyproject_dependencies(pyproject_dependencies):
raise TypeError(
"Failed to cast to PyProjectDependencies: "
+ str(pyproject_dependencies)
)

for pyproject_dependency in pyproject_dependencies:
package_data = version_operator_pattern.split(pyproject_dependency)
pyproject_packages.add(package_data[0].strip().lower().replace("_", "-"))

return pyproject_packages


def exist_package_in_pyproject(
packages: list[str], pyproject_packages: set[str]
) -> bool:
"""
与えられたパッケージ群のいずれかがpyproject.toml内に存在するかを判定する
:param packages: パッケージ群
:param pyproject_packages: pyproject.toml内のパッケージ一覧
:return: 与えられたパッケージ群のいずれかがpyproject.toml内に存在するか
"""
for package_name in packages:
if package_name.lower().replace("_", "-") in pyproject_packages:
return True

return False


def get_missing_packages(
imported_packages: set[str], pyproject_packages: set[str]
) -> PyProjectDependencies | NoReturn:
"""
プロジェクト内のPythonファイルでimportされているがpyproject.toml内には存在しないパッケージ一覧を取得する
:param imported_packages: プロジェクト内のPythonファイルからimportされているパッケージ一覧
:param pyproject_packages: pyproject.toml内のパッケージ一覧
:return: プロジェクト内のPythonファイルでimportされているがpyproject.toml内には存在しないパッケージ一覧。pyproject.toml内でのパッケージ名がkey、バージョンがvalueになっている。
"""
# import時のパッケージ名とpyproject.toml内でのパッケージ名の対応表
distributions = importlib_metadata.packages_distributions()

missing_packages: PyProjectDependencies = list()

for imported_package in imported_packages:
if imported_package not in distributions:
raise ModuleNotFoundError(
f"Package {imported_package} is not found. It maybe not installed."
)

packages = distributions[imported_package]

if len(packages) == 0:
raise ModuleNotFoundError(
f"Package {imported_package} is not found. It maybe not installed."
)

if not exist_package_in_pyproject(packages, pyproject_packages):
package_name: str = packages[0]
missing_packages.append(get_package_version(package_name))

return missing_packages


def main():
project_root = Path.cwd()
pyproject_path = project_root / "pyproject.toml"

if not pyproject_path.exists():
raise FileNotFoundError("pyproject.toml not found.")

pyproject = toml.load(pyproject_path)

if not is_pyproject_dependencies(pyproject["project"]["dependencies"]):
raise TypeError(
"Failed to cast to PyProjectDependencies: "
+ str(pyproject["project"]["dependencies"])
)

pyproject["project"]["dependencies"] = fix_package_version(
pyproject["project"]["dependencies"]
)

# プロジェクト内のPythonファイルでimportされているがpyproject.toml内には存在しないパッケージをpyproject.tomlの「project.dependencies」セクションに追加する
pyproject["project"]["dependencies"] += get_missing_packages(
get_imported_packages(project_root), get_pyproject_packages(pyproject)
)

if not is_pyproject_dependencies(pyproject["dependency-groups"]["dev"]):
raise TypeError(
"Failed to cast to PyProjectDependencies: "
+ str(pyproject["dependency-groups"]["dev"])
)

pyproject["dependency-groups"]["dev"] = fix_package_version(
pyproject["dependency-groups"]["dev"]
)

with open(pyproject_path, "w") as f:
toml.dump(pyproject, f)


if __name__ == "__main__":
main()
12 changes: 6 additions & 6 deletions scripts/pr_format/pr_format/format.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#!/usr/bin/env bash

pipenv run python "${GITHUB_WORKSPACE}/scripts/pr_format/pr_format/fix_pipfile.py"
pipenv run python "${GITHUB_WORKSPACE}/scripts/pr_format/pr_format/fix_pyproject.py"
tag_name="$(yq '.jobs.pr-super-lint.steps[-1].uses' .github/workflows/pr-test.yml | sed -e 's;/slim@.*;:slim;g')"
tag_version="$(yq '.jobs.pr-super-lint.steps[-1].uses | line_comment' .github/workflows/pr-test.yml)"
pyink_version="$(docker run --rm --entrypoint '' "ghcr.io/${tag_name}-${tag_version}" /bin/sh -c 'pyink --version' | grep pyink | awk '{ print $2 }')"
sed -i -e "s/pyink = .*/pyink = \"==${pyink_version}\"/g" Pipfile
pipenv install --dev
pipenv run autopep8 --exit-code --in-place --recursive .
pipenv run pyink --config .python-black .
pipenv run isort --sp .isort.cfg .
sed -i -e "s/pyink==.*\"/pyink==${pyink_version}\"/g" pyproject.toml
uv sync --dev
uv tool run autopep8 --exit-code --in-place --recursive .
uv tool run pyink --config .python-black .
uv tool run isort --sp .isort.cfg .
3 changes: 3 additions & 0 deletions scripts/pr_test/pr_super_lint/get_python_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import sys

print(".".join(map(str, sys.version_info[0:2])))
2 changes: 1 addition & 1 deletion scripts/pr_test/pr_super_lint/npm_ci.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bash

bash "${GITHUB_WORKSPACE}/scripts/npm_ci.sh"
echo "PYTHONPATH=/github/workspace/:/github/workflow/.venv/lib/python$(echo 'import sys; print(".".join(map(str, sys.version_info[0:2])))' | python)/site-packages" >>"${GITHUB_ENV}"
echo "PYTHONPATH=/github/workspace/:/github/workflow/.venv/lib/python$(uv run "${GITHUB_WORKSPACE}/scripts/pr_test/pr_super_lint/get_python_version.py")/site-packages" >>"${GITHUB_ENV}"
action="$(yq '.jobs.pr-super-lint.steps[-1].uses | line_comment' .github/workflows/pr-test.yml)"
PATH="$(docker run --rm --entrypoint '' "ghcr.io/super-linter/super-linter:slim-${action}" /bin/sh -c 'echo $PATH')"
echo "PATH=/github/workspace/node_modules/.bin:${PATH}" >>"$GITHUB_ENV"
4 changes: 1 addition & 3 deletions scripts/pr_test/pr_super_lint/set_venv_path.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

# 環境ファイルを使ってenvにsetしている
# 参考URL: https://bit.ly/2KJhjqk
venv_path=$(pipenv --venv)
echo "${venv_path}"
VENV_PATH="${venv_path}"
VENV_PATH=".venv"

# https://github.com/github/super-linter/issues/157#issuecomment-648850330
# -v "/home/runner/work/_temp/_github_workflow":"/github/workflow"
Expand Down
5 changes: 5 additions & 0 deletions scripts/uv_install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash

uv --version
uv python install
uv sync --dev

0 comments on commit a34c090

Please sign in to comment.