diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5f2a6299..1a3f8495 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -44,11 +44,11 @@ jobs: - name: "Find the names of the Python files that will be linted" run: echo "PYTHON_FILES=$(uv run ruff check --show-files | grep '\.py$' | tr -s '\n' ' ')" >> $GITHUB_ENV - # Lint with ruff and cpplint + # Fast static checks - name: "Lint with ruff . . ." run: uv run ruff check - - name: "Check ruff formatting . . ." - run: uv run ruff format --diff + - name: "Check Ruff formatting . . ." + run: uv run ruff format --check --diff - name: "Lint with cpplint . . ." run: uv run cpplint src/*.hpp src/*.cpp @@ -72,7 +72,7 @@ jobs: - name: "Install libsemigroups_pybind11 . . ." run: uv sync --verbose --locked --all-extras --no-dev --group lint - # Lint with ruff and cpplint + # Pylint requires the built extension - name: "Lint with pylint . . ." run: uv run pylint $PYTHON_FILES clang-format: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..eb8db28a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,36 @@ +minimum_pre_commit_version: "3.2.0" +default_install_hook_types: [pre-commit, pre-push] + +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.14.2 + hooks: + - id: ruff-check + stages: [pre-commit] + - id: ruff-format + stages: [pre-commit] + + - repo: local + hooks: + - id: pylint + name: pylint + entry: pylint + language: system + # Keep the local hook static; CI checks the built native extension. + args: + - --jobs=1 + - --extension-pkg-allow-list= + - --ignored-modules=_libsemigroups_pybind11 + - --disable=missing-class-docstring + types: [python] + exclude: ^(?:build_tools|etc)/ + require_serial: true + stages: [pre-push] + + - id: cpplint + name: cpplint + entry: cpplint + language: system + files: ^src/ + types_or: [c, c++] + stages: [pre-push] diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index a8362aa8..118be3a3 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -30,7 +30,27 @@ where [package_manager] is your favourite conda-like package manager, such as conda or mamba. The default value is mamba. Note that this DOES NOT *yet* work with micromamba. -To build libsemigroups (with the above environment active): +Git hooks +--------- + +Install the repository's commit and push hooks after creating the development +environment: + +.. code-block:: console + + pre-commit install + +Ruff linting and formatting then run when committing, while Pylint and cpplint +run before a push. To check every tracked file without making a commit or push, +run: + +.. code-block:: console + + pre-commit run --all-files + pre-commit run --all-files --hook-stage pre-push + +Building libsemigroups +---------------------- First, clone and prepare the libsemigroups repository: diff --git a/dev-environment.yml b/dev-environment.yml index 90f1f016..a7d15a4c 100644 --- a/dev-environment.yml +++ b/dev-environment.yml @@ -15,6 +15,7 @@ dependencies: - pip - pip-tools - pkgconfig + - pre-commit - pybind11 - pylint - pytest diff --git a/etc/make-lint.sh b/etc/make-lint.sh index 1d5b4b9e..577bb9c0 100755 --- a/etc/make-lint.sh +++ b/etc/make-lint.sh @@ -10,6 +10,9 @@ exit_code=0 echo "Linting with ruff . . ." ruff check || ((exit_code = 1)) +echo "Checking formatting with ruff . . ." +ruff format --check || ((exit_code = 1)) + echo "Linting with pylint . . ." pylint $(ruff check --show-files | grep "\.py$") || ((exit_code = 1)) diff --git a/pyproject.toml b/pyproject.toml index f25aba8f..76b8ea77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,5 @@ # TODO: -# * Use ruff # * Decide on good configurations for tools -# * Use pre-commit # * Use MyPy [build-system] build-backend = "setuptools.build_meta" @@ -80,8 +78,14 @@ test = ["pytest>=8.4.2"] [dependency-groups] # This list should contain any package used in the development of # libsemigroups_pybind11. -dev = ["ipython>=9.6.0", { include-group = "lint" }] -lint = ["codespell>=2.4.1", "cpplint>=2.0.2", "pylint>=4.0.2", "ruff>=0.14.2"] +dev = ["ipython>=9.6.0", { include-group = "lint" }] +lint = [ + "codespell>=2.4.1", + "cpplint>=2.0.2", + "pre-commit>=4.3.0", + "pylint>=4.0.2", + "ruff>=0.14.2", +] ################################################################### # Tools diff --git a/src/libsemigroups_pybind11/action.py b/src/libsemigroups_pybind11/action.py index 238d5359..ece1b8cf 100644 --- a/src/libsemigroups_pybind11/action.py +++ b/src/libsemigroups_pybind11/action.py @@ -184,9 +184,7 @@ def generators(self: _Self) -> Iterator[Element]: _copy_cxx_mem_fns(_RightActionPPerm1PPerm1, Action) -for _type in ( - Action._py_template_params_to_cxx_type.values() # pylint: disable=protected-access -): +for _type in Action._py_template_params_to_cxx_type.values(): # pylint: disable=protected-access _register_cxx_wrapped_type(_type, Action) ######################################################################## diff --git a/src/libsemigroups_pybind11/adapters.py b/src/libsemigroups_pybind11/adapters.py index 203b8d24..4c2798dd 100644 --- a/src/libsemigroups_pybind11/adapters.py +++ b/src/libsemigroups_pybind11/adapters.py @@ -106,9 +106,7 @@ def __call__(self: _Self, pt: Point, x: Element) -> Point: _copy_cxx_mem_fns(_ImageRightActionPPerm1PPerm1, ImageRightAction) -for _type in ( - ImageRightAction._py_template_params_to_cxx_type.values() # pylint:disable=protected-access -): +for _type in ImageRightAction._py_template_params_to_cxx_type.values(): # pylint:disable=protected-access _register_cxx_wrapped_type(_type, ImageRightAction) diff --git a/src/libsemigroups_pybind11/froidure_pin.py b/src/libsemigroups_pybind11/froidure_pin.py index baa206e5..dabfabc2 100644 --- a/src/libsemigroups_pybind11/froidure_pin.py +++ b/src/libsemigroups_pybind11/froidure_pin.py @@ -259,9 +259,7 @@ def sorted_elements( # pylint: disable=missing-function-docstring _copy_cxx_mem_fns(_FroidurePinBMat, FroidurePin) -for _fp_type in ( - FroidurePin._py_template_params_to_cxx_type.values() # pylint:disable=protected-access -): +for _fp_type in FroidurePin._py_template_params_to_cxx_type.values(): # pylint:disable=protected-access _register_cxx_wrapped_type(_fp_type, FroidurePin) diff --git a/src/libsemigroups_pybind11/konieczny.py b/src/libsemigroups_pybind11/konieczny.py index 1c7005dc..62aebd55 100644 --- a/src/libsemigroups_pybind11/konieczny.py +++ b/src/libsemigroups_pybind11/konieczny.py @@ -208,14 +208,10 @@ def D_classes(self: _Self) -> _Iterator[DClass]: _copy_cxx_mem_fns(_KoniecznyBMat8, Konieczny) _copy_cxx_mem_fns(_KoniecznyBMat8DClass, Konieczny.DClass) -for _type in ( - Konieczny._py_template_params_to_cxx_type.values() # pylint: disable=protected-access -): +for _type in Konieczny._py_template_params_to_cxx_type.values(): # pylint: disable=protected-access _register_cxx_wrapped_type(_type, Konieczny) -for _type in ( - Konieczny.DClass._py_template_params_to_cxx_type.values() # pylint: disable=protected-access -): +for _type in Konieczny.DClass._py_template_params_to_cxx_type.values(): # pylint: disable=protected-access _register_cxx_wrapped_type(_type, Konieczny.DClass) __all__ = ["Konieczny"] diff --git a/tests/test_konieczny.py b/tests/test_konieczny.py index c90c0885..2b412318 100644 --- a/tests/test_konieczny.py +++ b/tests/test_konieczny.py @@ -6,7 +6,6 @@ """This module contains some tests for the Konieczny class.""" - # pylint: disable=missing-function-docstring, invalid-name from datetime import timedelta diff --git a/uv.lock b/uv.lock index 12e062e6..e555c983 100644 --- a/uv.lock +++ b/uv.lock @@ -75,6 +75,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, ] +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + [[package]] name = "charset-normalizer" version = "3.4.4" @@ -185,6 +194,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, ] +[[package]] +name = "distlib" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, +] + [[package]] name = "docutils" version = "0.21.2" @@ -215,6 +233,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, ] +[[package]] +name = "filelock" +version = "3.32.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/64/a02e6765de08964ed371eca577870593245afc9dfac16d037de7c10d18e6/filelock-3.32.3.tar.gz", hash = "sha256:0ffa185a3540854c95caa7fa76b76cb219d907415e2c5dc9af25fd970563487f", size = 218135, upload-time = "2026-08-13T16:00:05.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/8e/50f46a9c0ce8d2861a394c1347caae037ea0431d2f67d7feb151cbc4649a/filelock-3.32.3-py3-none-any.whl", hash = "sha256:7f0ca4bcc0e181c60dbbd8aa9ab5b120ebb99e4e064e83636340056f833a1f09", size = 98901, upload-time = "2026-08-13T16:00:03.974Z" }, +] + [[package]] name = "graphviz" version = "0.21" @@ -224,6 +251,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" }, ] +[[package]] +name = "identify" +version = "2.6.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -353,12 +389,14 @@ dev = [ { name = "codespell", marker = "(python_full_version >= '3.12' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux')" }, { name = "cpplint", marker = "(python_full_version >= '3.12' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux')" }, { name = "ipython", marker = "(python_full_version >= '3.12' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux')" }, + { name = "pre-commit", marker = "(python_full_version >= '3.12' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux')" }, { name = "pylint", marker = "(python_full_version >= '3.12' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux')" }, { name = "ruff", marker = "(python_full_version >= '3.12' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux')" }, ] lint = [ { name = "codespell", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "cpplint", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pre-commit", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "pylint", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "ruff", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, ] @@ -383,12 +421,14 @@ dev = [ { name = "codespell", marker = "python_full_version >= '3.12'", specifier = ">=2.4.1" }, { name = "cpplint", marker = "python_full_version >= '3.12'", specifier = ">=2.0.2" }, { name = "ipython", marker = "python_full_version >= '3.12'", specifier = ">=9.6.0" }, + { name = "pre-commit", marker = "python_full_version >= '3.12'", specifier = ">=4.3.0" }, { name = "pylint", marker = "python_full_version >= '3.12'", specifier = ">=4.0.2" }, { name = "ruff", marker = "python_full_version >= '3.12'", specifier = ">=0.14.2" }, ] lint = [ { name = "codespell", specifier = ">=2.4.1" }, { name = "cpplint", specifier = ">=2.0.2" }, + { name = "pre-commit", specifier = ">=4.3.0" }, { name = "pylint", specifier = ">=4.0.2" }, { name = "ruff", specifier = ">=0.14.2" }, ] @@ -478,6 +518,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, ] +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + [[package]] name = "numpy" version = "2.2.6" @@ -649,6 +698,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pre-commit" +version = "4.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "identify", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nodeenv", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "virtualenv", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/89/1f3e8e1fc3e97de0fa963495832f581f025f29471602a309e48808244292/pre_commit-4.6.2.tar.gz", hash = "sha256:8f5d7bfb021ecdbcd9d49d89847082dd24172ccde534390081a679ad046e2441", size = 198670, upload-time = "2026-08-10T22:07:18.421Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/e2/bbb7129c9e7999a6b8ee9cca3b66486c25c423ab5a75f34071798b74ce94/pre_commit-4.6.2-py2.py3-none-any.whl", hash = "sha256:e2dde9a75d3bce11bd3831c26d134df00a2803c1d818be6a0383c3dcda25dc4e", size = 226202, upload-time = "2026-08-10T22:07:16.942Z" }, +] + [[package]] name = "prompt-toolkit" version = "3.0.52" @@ -749,6 +814,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, ] +[[package]] +name = "python-discovery" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/b7/ac44da2cf0e53ada0e419033c2d058219c95dc1403126f163304c9e814b1/python_discovery-1.5.2.tar.gz", hash = "sha256:45fd4f20a4e3f9b7bf2e0817870bc8e3b320a19658da177af800768c82dbf354", size = 82350, upload-time = "2026-08-12T14:05:26.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/45/689603d04b3bb8d7faa00f25c24acef993aab7813b3dbbfc472a459ab0b5/python_discovery-1.5.2-py3-none-any.whl", hash = "sha256:3e338c2d0f15dfaeea57493f4c2c6caebe0e998ea815c30ae8bf8ee21f1112d3", size = 38350, upload-time = "2026-08-12T14:05:25.113Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -1128,6 +1205,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] +[[package]] +name = "virtualenv" +version = "21.7.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "filelock", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "platformdirs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "python-discovery", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/dc/a6eb1ddfa7f1e390fa599b078453c97edb3f6f846b34fb4eac3e8ea16401/virtualenv-21.7.4.tar.gz", hash = "sha256:c9d960c95fa458171e58222a5ccab7465298e4b6559977865e627c4719f1e825", size = 5345511, upload-time = "2026-08-10T22:54:33.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/4c/eb2f52aeeaf30dbd073d315a251a63ae2b8263171ec4428c135140cb0802/virtualenv-21.7.4-py3-none-any.whl", hash = "sha256:376ec93cd6aab3044fa395d7db226db38043b7b5748948044b2a87168525e843", size = 5324444, upload-time = "2026-08-10T22:54:31.515Z" }, +] + [[package]] name = "wcwidth" version = "0.2.14"