-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from ukaea/github-migration
Migrate hook to GitHub
- Loading branch information
Showing
9 changed files
with
916 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
repos: | ||
- repo: https://github.com/charliermarsh/ruff-pre-commit | ||
rev: v0.0.292 | ||
hooks: | ||
- id: ruff | ||
args: [--fix, --exit-non-zero-on-fix] | ||
|
||
- repo: https://github.com/psf/black | ||
rev: 23.9.1 | ||
hooks: | ||
- id: black | ||
exclude: 'tests/data/.*' | ||
|
||
- repo: https://github.com/pre-commit/pre-commit-hooks | ||
rev: v4.4.0 | ||
hooks: | ||
- id: check-toml | ||
- id: check-yaml | ||
- id: check-merge-conflict | ||
- id: end-of-file-fixer | ||
exclude: 'tests/data/.*' | ||
- id: mixed-line-ending | ||
- id: check-added-large-files | ||
|
||
- repo: https://github.com/pre-commit/mirrors-mypy | ||
rev: v1.5.1 | ||
hooks: | ||
- id: mypy | ||
args: ['--ignore-missing', '--python-version', '3.10'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
- id: check-ui-files | ||
name: check-ui-files | ||
description: check that generated PySide ui files are up-to-date | ||
entry: pyuic-pre-commit | ||
language: python | ||
files: .*\.ui$ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,49 @@ | ||
# pyuic-pre-commit | ||
A pre-commit hook to run PyQt/PySide's uic tool and ensure generated Python files are up-to-date with their corresponding .ui files. | ||
|
||
A [pre-commit](https://pre-commit.com/) hook to run PyQt/PySide's `pyuic` tool | ||
and ensure generated Python files are up-to-date with their `.ui` files. | ||
|
||
## Usage | ||
|
||
Add the following to your `.pre-commit-config.yaml`: | ||
|
||
```yaml | ||
- repo: https://github.com/ukaea/pyuic-pre-commit.git | ||
rev: v0.1.0 # change to your desired version | ||
hooks: | ||
- id: check-ui-files | ||
args: ['--exe-name', 'pyside6-uic'] # optional | ||
``` | ||
You **must** have a `pyuic` tool available and on your path. | ||
The default `pyuic` tool is `pyside6-uic`, | ||
but this can be set using the `--exe-name` argument shown above. | ||
For example, if you're using PyQt5: | ||
|
||
```yaml | ||
args: ['--exe-name', 'pyuic5'] | ||
``` | ||
|
||
## Assumptions | ||
|
||
This hook assumes that each `.ui` and generated Python file pair are | ||
located in the same directory and have a consistent naming pattern. | ||
The generated Python file must have the same name as the `.ui` file | ||
with a `ui_` prefix. | ||
|
||
For example, the following is OK: | ||
|
||
```text | ||
module/widget.ui -> module/ui_widget.py | ||
``` | ||
|
||
But these examples are not: | ||
|
||
```text | ||
module/ui/widget.ui -> module/ui_widget.py | ||
module/widget.ui -> module/widget.py | ||
``` | ||
|
||
> *Note: | ||
> If you have requirements that are not supported by these assumptions, | ||
> please let the hook authors know and they can try to help.* |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
[tool.poetry] | ||
name = "pyuicprecommit" | ||
version = "0.1.0" | ||
description = "Pre-commit hook to validate Qt UI files have been generated using pyuic" | ||
authors = ["hsaunders1904"] | ||
readme = "README.md" | ||
|
||
[tool.poetry.scripts] | ||
pyuic-pre-commit = 'pyuicprecommit:main_with_exit' | ||
|
||
[tool.poetry.dependencies] | ||
python = "^3.10,<3.12" | ||
|
||
[tool.poetry.group.dev.dependencies] | ||
pre-commit = "^3.4.0" | ||
black = "^23.9.1" | ||
ruff = "^0.0.292" | ||
mypy = "^1.5.1" | ||
|
||
[tool.poetry.group.test.dependencies] | ||
pre-commit = "^3.4.0" | ||
pytest = "^7.4.2" | ||
gitpython = "^3.1.37" | ||
pyside6 = "6.5.3" | ||
|
||
[build-system] | ||
requires = ["poetry-core"] | ||
build-backend = "poetry.core.masonry.api" | ||
|
||
[tool.black] | ||
extend-exclude = "tests/data/*" | ||
|
||
[tool.ruff] | ||
line-length = 89 | ||
select = [ | ||
"C90", # mccabe | ||
"B", # flake8-bugbear | ||
"D", # pydocstyle | ||
"E", # pycodestyle-error | ||
"F", # pyflakes | ||
"I", # isort | ||
"N", # pep8-naming | ||
"PTH", # flake8-use-pathlib | ||
"S", # flake8-bandit | ||
"UP", # pyupgrade | ||
"W", # pydocstyle-warning | ||
] | ||
|
||
[tool.ruff.per-file-ignores] | ||
"tests/data/*" = ["ALL"] | ||
"tests/**/test_*.py" = [ | ||
# Allow asserts in tests | ||
"S101", | ||
# Allow undocumented code in tests | ||
"D", | ||
] | ||
|
||
[tool.ruff.pydocstyle] | ||
convention = "numpy" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
"""The entry point for the pyuic-pre-commit pre-commit hook.""" | ||
|
||
from __future__ import annotations | ||
|
||
import argparse | ||
import shutil | ||
import subprocess | ||
import sys | ||
from dataclasses import dataclass | ||
from pathlib import Path | ||
|
||
|
||
@dataclass | ||
class Args: | ||
"""Command line options for the hook.""" | ||
|
||
files: list[Path] | ||
exe_name: str | ||
|
||
|
||
def main_with_exit(): | ||
"""Run the pre-commit checks and exit on the returned error code.""" | ||
sys.exit(main(sys.argv[1:])) | ||
|
||
|
||
def main(argv: list[str]) -> int: | ||
"""Run the pre-commit checks.""" | ||
args = parse_args(argv) | ||
if not (uic_exe := find_uic_exe(args.exe_name)): | ||
print("cannot find uic executable, ensure it's on your path", file=sys.stderr) | ||
return 1 | ||
|
||
exit_code = 0 | ||
for ui_file in args.files: | ||
target_file = Path(ui_file.parent / f"ui_{ui_file.stem}.py") | ||
if not target_file.is_file(): | ||
print(f"no Python file found for ui file '{ui_file}'", file=sys.stderr) | ||
exit_code = 1 | ||
continue | ||
out = run_uic(uic_exe, ui_file) | ||
if out != target_file.read_text(): | ||
print( | ||
f"Python file '{target_file}' out of date with '{ui_file}'", | ||
file=sys.stderr, | ||
) | ||
exit_code = 1 | ||
return exit_code | ||
|
||
|
||
def parse_args(argv: list[str]) -> Args: | ||
"""Parse command line arguments.""" | ||
parser = argparse.ArgumentParser(description=__doc__) | ||
parser.add_argument( | ||
"files", | ||
nargs="+", | ||
type=Path, | ||
help="the files to apply the check to", | ||
) | ||
parser.add_argument( | ||
"--exe-name", | ||
default="pyside6-uic", | ||
type=str, | ||
help="the name of the uic executable to use", | ||
) | ||
return Args(**vars(parser.parse_args(argv))) | ||
|
||
|
||
def find_uic_exe(cli_exe: str) -> Path | None: | ||
"""Find the uic executable on the system path.""" | ||
if exe := shutil.which(cli_exe): | ||
return Path(exe) | ||
return None | ||
|
||
|
||
def run_uic(uic_exe: Path, ui_file: Path) -> str: | ||
"""Run uic on the given ui file, returning the output.""" | ||
cmd = [str(uic_exe), str(ui_file)] | ||
return subprocess.check_output(cmd).decode() # noqa: S603 | ||
|
||
|
||
if __name__ == "__main__": | ||
main_with_exit() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
################################################################################ | ||
## Form generated from reading UI file 'window.ui' | ||
## | ||
## Created by: Qt User Interface Compiler version 6.5.3 | ||
## | ||
## WARNING! All changes made in this file will be lost when recompiling UI file! | ||
################################################################################ | ||
|
||
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, | ||
QMetaObject, QObject, QPoint, QRect, | ||
QSize, QTime, QUrl, Qt) | ||
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, | ||
QFont, QFontDatabase, QGradient, QIcon, | ||
QImage, QKeySequence, QLinearGradient, QPainter, | ||
QPalette, QPixmap, QRadialGradient, QTransform) | ||
from PySide6.QtWidgets import (QApplication, QSizePolicy, QWidget) | ||
|
||
class Ui_Form(object): | ||
def setupUi(self, Form): | ||
if not Form.objectName(): | ||
Form.setObjectName(u"Form") | ||
Form.resize(400, 300) | ||
|
||
self.retranslateUi(Form) | ||
|
||
QMetaObject.connectSlotsByName(Form) | ||
# setupUi | ||
|
||
def retranslateUi(self, Form): | ||
Form.setWindowTitle(QCoreApplication.translate("Form", u"Form", None)) | ||
# retranslateUi | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<ui version="4.0"> | ||
<class>Form</class> | ||
<widget class="QWidget" name="Form"> | ||
<property name="geometry"> | ||
<rect> | ||
<x>0</x> | ||
<y>0</y> | ||
<width>400</width> | ||
<height>300</height> | ||
</rect> | ||
</property> | ||
<property name="windowTitle"> | ||
<string>Form</string> | ||
</property> | ||
</widget> | ||
<resources/> | ||
<connections/> | ||
</ui> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import os | ||
import shutil | ||
import tempfile | ||
from contextlib import contextmanager | ||
from pathlib import Path | ||
|
||
import git | ||
import pytest | ||
from pre_commit.main import main as pre_commit_main | ||
|
||
DATA_DIR = Path(__file__).parent / "data" | ||
ROOT_DIR = Path(__file__).parent.parent | ||
HOOK_NAME = "check-ui-files" | ||
|
||
|
||
@contextmanager | ||
def working_directory(working_dir: Path): | ||
current_dir = Path.cwd() | ||
try: | ||
os.chdir(working_dir) | ||
yield | ||
finally: | ||
os.chdir(current_dir) | ||
|
||
|
||
@pytest.fixture | ||
def temp_repo(): | ||
with tempfile.TemporaryDirectory() as tmp_dir: | ||
yield git.Repo.init(tmp_dir) | ||
|
||
|
||
def test_install_succeeds(temp_repo: git.Repo): | ||
repo_dir = Path(temp_repo.git_dir).parent | ||
|
||
with working_directory(repo_dir): | ||
result = pre_commit_main(["try-repo", str(ROOT_DIR), HOOK_NAME]) | ||
|
||
assert result == 0 | ||
|
||
|
||
def test_hook_errors_given_missing_py_file(temp_repo: git.Repo): | ||
repo_dir = Path(temp_repo.git_dir).parent | ||
shutil.copyfile(DATA_DIR / "window.ui", repo_dir / "window.ui") | ||
temp_repo.git.add(str(repo_dir / "window.ui")) | ||
|
||
with working_directory(repo_dir): | ||
result = pre_commit_main(["try-repo", str(ROOT_DIR), HOOK_NAME]) | ||
|
||
assert result == 1 | ||
|
||
|
||
def test_hook_errors_given_py_file_out_of_date(temp_repo: git.Repo): | ||
repo_dir = Path(temp_repo.git_dir).parent | ||
shutil.copyfile(DATA_DIR / "window.ui", repo_dir / "window.ui") | ||
(repo_dir / "ui_window.py").write_text("Not right content") | ||
temp_repo.git.add(str(repo_dir / "window.ui")) | ||
|
||
with working_directory(repo_dir): | ||
result = pre_commit_main(["try-repo", str(ROOT_DIR), HOOK_NAME]) | ||
|
||
assert result == 1 | ||
|
||
|
||
def test_hook_successful_given_py_file_present(temp_repo: git.Repo): | ||
repo_dir = Path(temp_repo.git_dir).parent | ||
shutil.copyfile(DATA_DIR / "window.ui", repo_dir / "window.ui") | ||
shutil.copyfile(DATA_DIR / "ui_window.py", repo_dir / "ui_window.py") | ||
temp_repo.git.add(str(repo_dir / "window.ui")) | ||
|
||
with working_directory(repo_dir): | ||
result = pre_commit_main(["try-repo", str(ROOT_DIR), HOOK_NAME]) | ||
|
||
assert result == 0 |