diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..cfa624f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,85 @@ +# Inspired by https://github.com/python-poetry/poetry-plugin-export/blob/main/.github/workflows/main.yml + +name: Tests + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +# Allow only one concurrent run per branch: +# Runs currently in progress will be cancelled if a new run is triggered and if the event is a pull request +concurrency: + group: tests-${{ github.head_ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + tests: + name: ${{ matrix.os }} / ${{ matrix.python-version }} ${{ matrix.suffix }} + runs-on: ${{ matrix.image }} + defaults: + run: + shell: bash + strategy: + fail-fast: false + matrix: + os: [Ubuntu, macOS, Windows] + python-version: ["3.9", "3.10", "3.11"] + include: + - os: Ubuntu + image: ubuntu-latest + - os: Windows + image: windows-latest + - os: macOS + image: macos-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + # Store full python version in a variable 'version' + - name: Get full Python version + id: full-python-version + run: echo "version=$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")" >> $GITHUB_OUTPUT + + - name: Bootstrap poetry + run: | + curl -sL https://install.python-poetry.org | python - -y ${{ matrix.bootstrap-args }} + + - name: Update PATH + if: ${{ matrix.os != 'Windows' }} + run: echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Update Path for Windows + if: ${{ matrix.os == 'Windows' }} + run: echo "$APPDATA\Python\Scripts" >> $GITHUB_PATH + + - name: Configure poetry + run: poetry config virtualenvs.in-project true + + - name: Set up cache + uses: actions/cache@v4 + id: cache + with: + path: .venv + key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} + + - name: Ensure cache is healthy + if: steps.cache.outputs.cache-hit == 'true' + run: timeout 10s poetry run pip --version || rm -rf .venv + + - name: Install dependencies + run: poetry install --with ci + + - name: Run pre-commit tests + run: | + poetry run pre-commit run --all-files + + - name: Test with pytest + run: | + poetry run pytest -v diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml deleted file mode 100644 index 0a95d62..0000000 --- a/.github/workflows/python-tests.yml +++ /dev/null @@ -1,38 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python - -name: Python package - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - build: - name: python-cicd - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.9", "3.10", "3.11"] - - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install poetry - poetry update - poetry install - - name: Run pre-commit tests - run: | - poetry run pre-commit run --all-files - - name: Test with pytest - run: | - poetry run pytest diff --git a/poetry.lock b/poetry.lock index ca57b43..811a16c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -977,6 +977,20 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-github-actions-annotate-failures" +version = "0.2.0" +description = "pytest plugin to annotate failed tests with a workflow command for GitHub Actions" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-github-actions-annotate-failures-0.2.0.tar.gz", hash = "sha256:844ab626d389496e44f960b42f0a72cce29ae06d363426d17ea9ae1b4bef2288"}, + {file = "pytest_github_actions_annotate_failures-0.2.0-py3-none-any.whl", hash = "sha256:8bcef65fed503faaa0524b59cfeccc8995130972dd7b008d64193cc41b9cde85"}, +] + +[package.dependencies] +pytest = ">=4.0.0" + [[package]] name = "pywin32-ctypes" version = "0.2.3" @@ -1477,4 +1491,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "61fe6085de2a190800907167ca76bd2abfbf9709218824447eabebe2c3d4f593" +content-hash = "b6d94aef77fa97985235e9358868600438a2f1e484e7de9b4f5e99ccd102d29f" diff --git a/poetry_monoranger_plugin/monorepo_adder.py b/poetry_monoranger_plugin/monorepo_adder.py index 7204e10..2639e67 100644 --- a/poetry_monoranger_plugin/monorepo_adder.py +++ b/poetry_monoranger_plugin/monorepo_adder.py @@ -8,16 +8,16 @@ import copy from typing import TYPE_CHECKING -from cleo.events.console_terminate_event import ConsoleTerminateEvent from poetry.console.commands.add import AddCommand from poetry.console.commands.remove import RemoveCommand from poetry.factory import Factory from poetry.installation.installer import Installer from poetry.poetry import Poetry +from tomlkit.toml_document import TOMLDocument if TYPE_CHECKING: from cleo.events.console_command_event import ConsoleCommandEvent - from tomlkit.toml_document import TOMLDocument + from cleo.events.console_terminate_event import ConsoleTerminateEvent from poetry_monoranger_plugin.config import MonorangerConfig diff --git a/poetry_monoranger_plugin/path_rewriter.py b/poetry_monoranger_plugin/path_rewriter.py index 7d31e4a..d41b4a7 100644 --- a/poetry_monoranger_plugin/path_rewriter.py +++ b/poetry_monoranger_plugin/path_rewriter.py @@ -49,7 +49,7 @@ def execute(self, event: ConsoleCommandEvent): try: pinned = self._pin_dependency(poetry, dependency) except (RuntimeError, ValueError) as e: - io.write_line(f"Could not pin dependency {dependency.name}: {str(e)}") + io.write_line(f"Could not pin dependency {dependency.name}: {e!s}") continue main_deps_group.remove_dependency(dependency.name) diff --git a/poetry_monoranger_plugin/plugin.py b/poetry_monoranger_plugin/plugin.py index baaf60f..d304682 100644 --- a/poetry_monoranger_plugin/plugin.py +++ b/poetry_monoranger_plugin/plugin.py @@ -7,7 +7,7 @@ from __future__ import annotations import copy -from typing import TYPE_CHECKING, Any, Mapping +from typing import TYPE_CHECKING, Any import cleo.events.console_events from cleo.events.console_command_event import ConsoleCommandEvent @@ -25,6 +25,8 @@ from poetry_monoranger_plugin.config import MonorangerConfig if TYPE_CHECKING: + from collections.abc import Mapping + from cleo.events.event import Event from cleo.events.event_dispatcher import EventDispatcher from poetry.console.application import Application @@ -52,8 +54,8 @@ class Monoranger(ApplicationPlugin): def __init__(self): super().__init__() - self.poetry: Poetry = None # type: ignore - self.plugin_conf: MonorangerConfig = None # type: ignore + self.poetry: Poetry = None # type: ignore[assignment] + self.plugin_conf: MonorangerConfig = None # type: ignore[assignment] self.ctx: dict[type[PoetryCommand], Any] = {} def activate(self, application: Application): @@ -105,9 +107,8 @@ def console_command_event_listener(self, event: Event, event_name: str, dispatch from poetry_monoranger_plugin.lock_modifier import LockModifier # NOTE: consider moving this to a separate UpdateModifier class - if isinstance(command, UpdateCommand): - if not event.io.input._arguments.get("packages", None): - event.io.input._arguments["packages"] = [command.poetry.package.name] + if isinstance(command, UpdateCommand) and not event.io.input._arguments.get("packages", None): + event.io.input._arguments["packages"] = [command.poetry.package.name] LockModifier(self.plugin_conf).execute(event) if isinstance(command, (AddCommand, RemoveCommand)): diff --git a/pyproject.toml b/pyproject.toml index b0fb7de..e794f01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,12 @@ pre-commit = "^3.8.0" pre-commit-hooks = "^4.6.0" pytest = "^8.3.3" +[tool.poetry.group.ci] +optional = true + +[tool.poetry.group.ci.dependencies] +pytest-github-actions-annotate-failures = "^0.2.0" + [tool.mypy] disable_error_code = "import-untyped" check_untyped_defs = true @@ -25,7 +31,19 @@ line-length = 120 target-version = "py39" [tool.ruff.lint] -extend-select = ["I", "D"] +extend-select = [ + "B", # flake8-bugbear + "C4", # fkale8-comprehensions + "D", # pydocstyle + "I", # isort + "N", # pep8-naming + "PIE", # fkale8-pie + "PGH", # pygrep-hooks + "RUF", # ruff checks + "SIM", # flake8-simplify + "TCH", # flake8-type-checking + "UP", # pyupgrade +] # Disabled rules: # `D104`: requires documentation in __init__.py of public packages # `D105`: requires documentation for magic methods diff --git a/tests/conftest.py b/tests/conftest.py index fc07718..777b0ce 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,4 @@ from pathlib import Path -from typing import Type from unittest.mock import Mock import pytest @@ -13,7 +12,7 @@ def mock_event_gen(): from poetry.console.commands.command import Command - def _factory(command_cls: Type[Command], disable_cache: bool): + def _factory(command_cls: type[Command], disable_cache: bool): from cleo.events.console_command_event import ConsoleCommandEvent main_grp = DependencyGroup("main") @@ -48,7 +47,7 @@ def _factory(command_cls: Type[Command], disable_cache: bool): def mock_terminate_event_gen(mock_event_gen): from poetry.console.commands.command import Command - def _factory(command_cls: Type[Command], disable_cache: bool): + def _factory(command_cls: type[Command], disable_cache: bool): from cleo.events.console_terminate_event import ConsoleTerminateEvent mock_event = mock_event_gen(command_cls, disable_cache) diff --git a/tests/test_import.py b/tests/test_import.py index d119c43..8d06f4a 100644 --- a/tests/test_import.py +++ b/tests/test_import.py @@ -3,5 +3,3 @@ def test_import(): import poetry_monoranger_plugin # noqa: F401 - - pass diff --git a/tests/test_venv_modifier.py b/tests/test_venv_modifier.py index deaf003..7452bbb 100644 --- a/tests/test_venv_modifier.py +++ b/tests/test_venv_modifier.py @@ -33,7 +33,7 @@ def test_executes_modifications_for_env_command(mock_event_gen, disable_cache: b # create_poetry is called with the correct args mock_create_poetry.assert_called_once() - assert mock_create_poetry.call_args[1]["cwd"] == Path("/monorepo_root") + assert mock_create_poetry.call_args[1]["cwd"] == Path("/monorepo_root").resolve() assert mock_create_poetry.call_args[1]["io"] == mock_event.io assert mock_create_poetry.call_args[1]["disable_cache"] == disable_cache