diff --git a/.github/instructions/pact-cli.instructions.md b/.github/instructions/pact-cli.instructions.md deleted file mode 100644 index e1b08ca03..000000000 --- a/.github/instructions/pact-cli.instructions.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -description: "Pact CLI" -applyTo: "/pact-python-cli/**" ---- - -# Pact CLI - -- This directory contains the source for the `pact-python-cli` package, which provides a thin wrapper around the Pact CLI binaries. -- It allows users to install the Pact CLI tools via PyPI and use them in Python projects without requiring separate installation steps. -- The package includes executable wrappers for all major Pact CLI tools (`pact`, `pact-broker`, `pact-message`, `pact-mock-service`, `pact-provider-verifier`, etc.). -- By default, it uses bundled binaries, but can fall back to system-installed Pact CLI tools when `PACT_USE_SYSTEM_BINS` environment variable is set to `TRUE` or `YES`. diff --git a/.github/labels.yml b/.github/labels.yml index d5b84ff59..e7c9d1f68 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -1,8 +1,4 @@ --- -- name: area:cli - description: Relating to the CLI - color: C2E0C6 - - name: area:core description: Relating to the core Pact Python library color: C2E0C6 diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml deleted file mode 100644 index 91c3f6dfd..000000000 --- a/.github/workflows/release-cli.yml +++ /dev/null @@ -1,311 +0,0 @@ ---- -# Release workflow for pact-python-cli (CLI wrapper package). -# -# This package tracks the upstream pact-foundation/pact-standalone project and -# versions itself as `{upstream_version}.{N}` (e.g. `2.4.0.0`). The release -# lifecycle runs in three stages: -# -# Stage 1: Prepare (trigger: push to main) -# The `prepare` job runs `scripts/release.py prepare cli`, which fetches the -# latest `v*` release from pact-foundation/pact-standalone, computes the next -# wrapper version, updates pyproject.toml and CHANGELOG.md, and force-pushes -# those changes to the fixed branch `release/pact-python-cli`. It then -# creates (or updates the title and body of) the release PR targeting main. -# -# Stage 2: Tag (trigger: release PR merged โ†’ `closed` event, merged == true) -# When the release PR on `release/pact-python-cli` is merged, the `tag` job -# runs `scripts/release.py tag cli`, which reads the version from -# pyproject.toml and pushes a git tag of the form `pact-python-cli/X.Y.Z.N`. -# -# Stage 3: Publish (trigger: tag push matching `pact-python-cli/*`) -# The `build-sdist`, `build-wheels`, and `publish` jobs build the full wheel -# matrix (macOS/Linux/Windows, multiple architectures), create a GitHub -# release with the changelog, and publish all artifacts to PyPI. -# -# Additional: `build-sdist` and `build-wheels` also run on open/updated release -# PRs (not closed) to verify builds before merging. -# -# The `closed` PR type is listed in the trigger so that Stage 2 fires on merge. -# All jobs except `tag` guard against closed-but-not-merged events with -# explicit `if` conditions. -name: release cli - -on: - push: - branches: - - main - tags: - - pact-python-cli/* - pull_request: - branches: - - main - types: - - opened - - synchronize - - reopened - - closed - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref || github.run_id }} - cancel-in-progress: ${{ github.event_name == 'pull_request' && github.event.action != 'closed' }} - -env: - STABLE_PYTHON_VERSION: '311' - HATCH_VERBOSE: '1' - FORCE_COLOR: '1' - CIBW_BUILD_FRONTEND: build - -jobs: - complete: - name: Release CLI completion check - if: always() - runs-on: ubuntu-latest - needs: - - prepare - - tag - - build-sdist - - build-wheels - - publish - steps: - - name: Failed - run: exit 1 - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') - - prepare: - name: Update CLI release PR - if: >- - github.event_name == 'push' && github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - environment: - name: release-pr - steps: - - name: Generate GitHub App token - id: app-token - uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0 - with: - app-id: ${{ vars.PACT_FOUNDATION_BOT_APP_ID }} - private-key: ${{ secrets.PACT_FOUNDATION_BOT_PRIVATE_KEY }} - - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - # Need full history for git-cliff to determine the next version and - # generate the changelog - fetch-depth: 0 - token: ${{ steps.app-token.outputs.token }} - - - name: Configure git identity - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - run: | - git config user.name "${{ steps.app-token.outputs.app-slug }}[bot]" - git config user.email "$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq '.id')+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com" - - - name: Install git-cliff and typos - uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2.87.28 - with: - tool: git-cliff,typos - - - name: Set up uv - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 - - - name: Install Python - # Bump to 3.11 to access tomllib - run: uv python install 311 - - - name: Update release PR - run: uv run python scripts/release.py prepare cli - env: - # Use the GitHub App token instead of GITHUB_TOKEN so that the PR can - # itself trigger workflows - GH_TOKEN: ${{ steps.app-token.outputs.token }} - - tag: - name: Create CLI release tag - if: >- - github.event_name == 'pull_request' && github.event.pull_request.merged == true && github.event.pull_request.head.ref - == 'release/pact-python-cli' - runs-on: ubuntu-latest - environment: - name: release-pr - steps: - - name: Generate GitHub App token - id: app-token - uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0 - with: - app-id: ${{ vars.PACT_FOUNDATION_BOT_APP_ID }} - private-key: ${{ secrets.PACT_FOUNDATION_BOT_PRIVATE_KEY }} - - - name: Checkout main - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: main - token: ${{ steps.app-token.outputs.token }} - - - name: Configure git identity - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - run: | - git config user.name "${{ steps.app-token.outputs.app-slug }}[bot]" - git config user.email "$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq '.id')+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com" - - - name: Set up uv - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 - - - name: Install Python - # Bump to 3.11 to access tomllib - run: uv python install 311 - - - name: Create and push release tag - run: uv run python scripts/release.py tag cli - env: - # Use the GitHub App token instead of GITHUB_TOKEN so that the tag can - # itself trigger workflows - GH_TOKEN: ${{ steps.app-token.outputs.token }} - - build-sdist: - name: Build CLI source distribution - # Prepare the wheels for upload, and also verify that the build works on - # release PRs before merging. - if: >- - ( - github.event_name == 'push' && - startsWith(github.ref, 'refs/tags/pact-python-cli/') - ) || ( - github.event_name == 'pull_request' && - github.event.action != 'closed' && - github.event.pull_request.head.ref == 'release/pact-python-cli' - ) - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Set up uv - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0 - - - name: Install Python - run: uv python install ${{ env.STABLE_PYTHON_VERSION }} - - - name: Install hatch - run: uv tool install hatch - - - name: Build source distribution - working-directory: pact-python-cli - run: hatch build --target sdist - - - name: Upload sdist - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 - with: - name: wheels-sdist - path: pact-python-cli/dist/*.tar* - if-no-files-found: error - compression-level: 0 - - build-wheels: - name: Build CLI wheel (release) on ${{ matrix.os }} - # Prepare the wheels for upload, and also verify that the build works on - # release PRs before merging. - if: >- - ( - github.event_name == 'push' && - startsWith(github.ref, 'refs/tags/pact-python-cli/') - ) || ( - github.event_name == 'pull_request' && - github.event.action != 'closed' && - github.event.pull_request.head.ref == 'release/pact-python-cli' - ) - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - macos-15-intel - - macos-latest - - ubuntu-24.04-arm - - ubuntu-latest - - windows-11-arm - - windows-latest - steps: - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Build wheels - uses: pypa/cibuildwheel@b3395e271a35f73dad77f21802abe9b986dfe299 # v4.3.1 - with: - package-dir: pact-python-cli - env: - # Only target the oldest supported Python version, as the CLI wrapper - # is thin and should be compatible with future Python versions. - CIBW_BUILD: cp${{ env.STABLE_PYTHON_VERSION }}-* - - - name: Upload wheels - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 - with: - name: wheels-${{ matrix.os }} - path: wheelhouse/*.whl - if-no-files-found: error - compression-level: 0 - - publish: - name: Publish CLI wheels and sdist - if: >- - github.event_name == 'push' && startsWith(github.ref, 'refs/tags/pact-python-cli/') - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/p/pact-python-cli - needs: - - build-sdist - - build-wheels - permissions: - contents: write - id-token: write - steps: - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Extract release changelog - run: | - version="${{ github.ref_name }}" - version="${version#*/}" - out="${{ runner.temp }}/release-changelog.md" - awk -v ver="$version" ' - /^## / { if (found) exit; if (index($0, ver)) found=1; next } - found { print } - ' pact-python-cli/CHANGELOG.md > "$out" - if [ ! -s "$out" ]; then - printf '> [!WARNING]\n>\n> No changelog entry found for %s.\n' "$version" > "$out" - fi - - - name: Find previous release tag - id: previous-tag - run: | - git fetch --tags --quiet - previous=$(git tag --list 'pact-python-cli/*' --sort=-version:refname \ - | grep -v "^${{ github.ref_name }}$" | head -1) - echo "tag=${previous}" >> "$GITHUB_OUTPUT" - - - name: Download wheels and sdist - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 - with: - path: wheelhouse - merge-multiple: true - - - name: Create GitHub release - uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 - with: - files: wheelhouse/* - body_path: ${{ runner.temp }}/release-changelog.md - previous_tag: ${{ steps.previous-tag.outputs.tag }} - draft: false - prerelease: false - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 - with: - skip-existing: true - packages-dir: wheelhouse diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d94e5a0e2..2b02a812f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -85,6 +85,9 @@ jobs: - name: Install Python run: uv python install ${{ matrix.python-version }} + - name: Install Pact CLI + uses: jdx/mise-action@08ae2a091c038e1e05ec6f5115dc5c69d5a2a6b8 # v5.1.2 + - name: Set PATH on Windows if: startsWith(matrix.os, 'windows-') shell: pwsh @@ -100,10 +103,6 @@ jobs: - name: Run tests run: hatch run test.py${{ matrix.python-version }}:test --junit-xml=junit.xml --cov-report=xml:coverage-tests.xml - - name: Run tests (CLI) - working-directory: pact-python-cli - run: hatch run test.py${{ matrix.python-version }}:test --junit-xml=junit.xml --cov-report=xml:coverage-cli.xml - - name: Run tests (FFI) working-directory: pact-python-ffi run: hatch run test.py${{ matrix.python-version }}:test --junit-xml=junit.xml --cov-report=xml:coverage-ffi.xml @@ -114,7 +113,7 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} flags: tests - files: coverage-tests.xml,pact-python-cli/coverage-cli.xml,pact-python-ffi/coverage-ffi.xml + files: coverage-tests.xml,pact-python-ffi/coverage-ffi.xml - name: Upload test results if: ${{ !cancelled() }} @@ -210,10 +209,6 @@ jobs: - name: Format run: hatch run format --check --output-format github - - name: Format (CLI) - working-directory: pact-python-cli - run: hatch run format --check --output-format github - - name: Format (FFI) working-directory: pact-python-ffi run: hatch run format --check --output-format github @@ -242,10 +237,6 @@ jobs: - name: Lint run: hatch run lint - - name: Lint (CLI) - working-directory: pact-python-cli - run: hatch run lint - - name: Lint (FFI) working-directory: pact-python-ffi run: hatch run lint @@ -275,10 +266,6 @@ jobs: - name: Typecheck run: hatch run typecheck - - name: Typecheck (CLI) - working-directory: pact-python-cli - run: hatch run typecheck - prek: name: Prek if: github.event_name != 'pull_request' || !github.event.pull_request.draft diff --git a/.vscode/settings.json b/.vscode/settings.json index e4b488462..e6415bfbd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,6 @@ "--reruns=0", "--no-cov", "examples/", - "pact-python-cli/tests/", "pact-python-ffi/tests/", "tests/" ], diff --git a/AGENTS.md b/AGENTS.md index 78aafcbcf..c5adfa820 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,4 +29,3 @@ - `src/pact/`: Main V3+ codebase (active development) - `pact-python-ffi/`: Low-level FFI bindings (memory management, type conversion only) -- `pact-python-cli/`: CLI wrapper (bundled binaries with system fallback) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8fdddca94..95af1146d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -99,7 +99,7 @@ You can also try using the new [github.dev](https://github.dev/pact-foundation/p pipx install hatch ``` -3. After cloning the repository, run `hatch shell` in the root of the repository. This will install all dependencies in a Python virtual environment and then ensure that the virtual environment is activated. You will also need to run `git submodule init` if you want to run tests, as Pact Python makes use of the Pact Compatibility Suite. +3. After cloning the repository, run `hatch shell` in the root of the repository. This will install all dependencies in a Python virtual environment and then ensure that the virtual environment is activated. You will also need to run `git submodule init` if you want to run tests, as Pact Python makes use of the Pact Compatibility Suite. The compatibility suite also needs the [Pact CLI](https://github.com/pact-foundation/pact-cli), which [`mise install`](https://mise.jdx.dev/) provides from `mise.toml`. 4. To run tests, run `hatch run test` to make sure the test suite is working. You should also make sure the example works by running `hatch run example`. For the examples, you will have to make sure that you have Docker (or a suitable alternative) installed and running. diff --git a/cliff.toml b/cliff.toml index e89e3846f..6f5f7f9d5 100644 --- a/cliff.toml +++ b/cliff.toml @@ -102,7 +102,7 @@ topo_order = false # sort the commits inside sections by oldest/newest order sort_commits = "oldest" # Ignore any changes from the CLI and FFI sub-projects -exclude_paths = ["pact-python-cli/", "pact-python-ffi/"] +exclude_paths = ["pact-python-ffi/"] [remote.github] owner = "pact-foundation" diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 61202609a..ef3bd19dc 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -11,5 +11,4 @@ - [Examples](examples/) - [API Documentation](api/) - [`pact-ffi`](pact-python-ffi/) -- [`pact-cli`](pact-python-cli/) - [Blog](blog/index.md) diff --git a/docs/consumer.md b/docs/consumer.md index 59b13f7b5..7c69684a9 100644 --- a/docs/consumer.md +++ b/docs/consumer.md @@ -235,12 +235,12 @@ The above example showed how to test a single consumer; however, without also te Once the tests are complete (and successful), the contracts can be uploaded to the Pact Broker. The provider can then download the contracts and run its own tests to ensure it meets the consumer's expectations. -The Broker CLI is a command-line tool that can be installed through the `pact-python-cli` package, or directly from the [Pact Standalone](https://github.com/pact-foundation/pact-standalone) releases page. It bundles several standalone CLI tools, including the `pact-broker` CLI client. +The [Pact CLI](https://github.com/pact-foundation/pact-cli) is a single `pact` executable whose `broker` subcommand publishes contracts. See its [installation instructions](https://github.com/pact-foundation/pact-cli#installation). The general syntax for the CLI is: ```console -pact-broker publish \ +pact broker publish \ /path/to/pacts/consumer-provider.json \ --consumer-app-version 1.0.0 \ --auto-detect-version-properties diff --git a/docs/releases.md b/docs/releases.md index 0160be4bf..769529ea6 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -2,15 +2,16 @@ Pact Python is made available through both GitHub releases and PyPI. The GitHub releases also come with a summary of changes and contributions since the last release. -The entire process is automated through three GitHub Actions workflows (one per package), each running a three-stage pipeline. A description of the process is provided [below](#release-pipeline). +The entire process is automated through two GitHub Actions workflows (one per package), each running a three-stage pipeline. A description of the process is provided [below](#release-pipeline). ## Packages -Pact Python is split into three independently versioned packages: +Pact Python is split into two independently versioned packages: - **[`pact-python`](https://pypi.org/p/pact-python)**: the core library. Versioned with [semantic versioning](https://semver.org/), derived from conventional commits via [git-cliff](https://git-cliff.org/). - **[`pact-python-ffi`](https://pypi.org/p/pact-python-ffi)**: Python bindings for the [pact-reference](https://github.com/pact-foundation/pact-reference) Rust library. Versioned as `{upstream}.{N}` (e.g. `0.4.28.0`), tracking upstream `libpact_ffi` releases. -- **[`pact-python-cli`](https://pypi.org/p/pact-python-cli)**: Python wrapper for the [pact-standalone](https://github.com/pact-foundation/pact-standalone) CLI tools. Versioned as `{upstream}.{N}` (e.g. `2.4.0.0`), tracking upstream releases. + +The [`pact-python-cli`](https://pypi.org/p/pact-python-cli) package, which distributes the [Pact CLI](https://github.com/pact-foundation/pact-cli) through PyPI, is released from the [`pact-cli`](https://github.com/pact-foundation/pact-cli) repository. ## Versioning @@ -29,9 +30,9 @@ Any deviation from the standard semantic versioning rules will be clearly docume The next version is computed automatically by [git-cliff](https://git-cliff.org/) from the [conventional commit](https://www.conventionalcommits.org/) history since the last release tag. -### pact-python-ffi and pact-python-cli +### pact-python-ffi -These packages follow their upstream projects' versioning, extended with a fourth component `N` that starts at `0` for each new upstream version and increments with each packaging-only release. When a new upstream version is released, `N` resets to `0`. +This package follows its upstream project's versioning, extended with a fourth component `N` that starts at `0` for each new upstream version and increments with each packaging-only release. When a new upstream version is released, `N` resets to `0`. ### Version storage @@ -39,7 +40,7 @@ Each package stores its version as a static string in its `pyproject.toml`. The ## Release Pipeline -Each package has its own release workflow (`.github/workflows/release-{core,ffi,cli}.yml`). All three follow the same three-stage process: +Each package has its own release workflow (`.github/workflows/release-{core,ffi}.yml`). Both follow the same three-stage process: ### Stage 1: Prepare @@ -47,7 +48,7 @@ Triggered by every push to `main`. The `prepare` job runs `scripts/release.py prepare `, which: -1. Computes the next version (via git-cliff for core; by fetching the latest upstream release for ffi/cli). +1. Computes the next version (via git-cliff for core; by fetching the latest upstream release for ffi). 2. Updates `pyproject.toml` with the new version. 3. Prepends the new release entry to `CHANGELOG.md` using git-cliff, preserving all previous entries (including any manual edits). 4. Force-pushes those changes to a fixed release branch (e.g. `release/pact-python`). diff --git a/docs/scripts/markdown.py b/docs/scripts/markdown.py index a8af79d03..822d00f1e 100644 --- a/docs/scripts/markdown.py +++ b/docs/scripts/markdown.py @@ -126,7 +126,6 @@ def process_markdown( ".", ignore=[ "docs", - "pact-python-cli", "pact-python-ffi", ".github", ], @@ -138,10 +137,3 @@ def process_markdown( ("pact-python-ffi", "pact-python-ffi"), ], ) - process_markdown( - "pact-python-cli", - mapping=[ - ("pact-python-cli/docs", "pact-python-cli"), - ("pact-python-cli", "pact-python-cli"), - ], - ) diff --git a/docs/scripts/python.py b/docs/scripts/python.py index b037af9d0..3a84a2df8 100644 --- a/docs/scripts/python.py +++ b/docs/scripts/python.py @@ -212,13 +212,6 @@ def process_python( ], python_mapping=[("src.pact", "pact")], ) - process_python( - "pact-python-cli/src/pact_cli", - python_mapping=[("pact-python-cli.src.pact_cli", "pact_cli")], - destination_mapping=[ - ("pact-python-cli/src/pact_cli", "pact-python-cli/api"), - ], - ) process_python( "pact-python-ffi/src/pact_ffi", python_mapping=[("pact-python-ffi.src.pact_ffi", "pact_ffi")], diff --git a/mise.toml b/mise.toml new file mode 100644 index 000000000..92a9990ec --- /dev/null +++ b/mise.toml @@ -0,0 +1,2 @@ +[tools] +"github:pact-foundation/pact-cli" = "0.10.9" diff --git a/pact-python-cli/.gitignore b/pact-python-cli/.gitignore deleted file mode 100644 index ff74a364b..000000000 --- a/pact-python-cli/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -src/pact_cli/bin -src/pact_cli/data -src/pact_cli/lib diff --git a/pact-python-cli/CHANGELOG.md b/pact-python-cli/CHANGELOG.md deleted file mode 100644 index 09aee24d1..000000000 --- a/pact-python-cli/CHANGELOG.md +++ /dev/null @@ -1,106 +0,0 @@ -# Pact Python CLI Changelog - -All notable changes to this project will be documented in this file. - -Note that this _only_ includes changes to the Python re-packaging of the Pact CLI. For changes to the Pact CLI itself, see the [Pact CLI changelog](https://github.com/pact-foundation/pact-standalone/blob/master/CHANGELOG.md). - - - - - -## [pact-python-cli/2.6.4.0] _2026-10-14_ - -### ๐Ÿ› Bug Fixes - -- _(cli)_ Improve bin dirs search -- _(cli)_ Use renamed macOS standalone assets - -### โš™๏ธ Miscellaneous Tasks - -- _(ci)_ Skip generic linux_ tags -- Require python 3.11 or newer - -### Contributors - -- @JP-Ellis - -## [pact-python-cli/2.6.0.1] _2026-06-02_ - -### ๐Ÿš€ Features - -- Bump Rust CLI to 0.10.2. See [Pact CLI releases](https://github.com/pact-foundation/pact-cli/releases) for details. - -### Contributors - -- @JP-Ellis - -## [pact-python-cli/2.6.0.0] _2026-04-16_ - -### ๐Ÿ› Bug Fixes - -- Bundle both ruby and rust CLIs - -### ๐Ÿ“š Documentation - -- Update changelog for pact-python-cli/2.5.7.0 - -### โš™๏ธ Miscellaneous Tasks - -- Remove versioningit, switch to static version in pyproject.toml -- Minor update to cliff config -- Replace taplo with tombi -- Allow windows arm cli builds to proceed - -### Contributors - -- @JP-Ellis - -## [pact-python-cli/2.5.7.0] _2025-12-10_ - -### ๐Ÿš€ Features - -- _(ffi)_ Add standalone ffi package -- _(v3)_ [**breaking**] Remove pact.v3.ffi module - > `pact.v3.ffi` is removed, and to be replaced by `pact_ffi`. That is, `pact.v3.ffi.$fn` should be replaced by `pact_ffi.$fn`. -- _(ffi)_ Upgrade lib to 0.4.28 - -### ๐Ÿ› Bug Fixes - -- Allow none in with_metadata -- _(ffi)_ Make version dynamic - -### ๐Ÿ“š Documentation - -- Update changelog for pact-python-ffi/0.4.22.0 -- _(ffi)_ Fix old references to pact.v3.ffi -- V3 review -- Update git cliff configuration -- Update changelog for pact-python-ffi/0.4.28.0 -- Update changelog for pact-python-ffi/0.4.28.1 -- Fix CI badge links -- Update changelogs - -### โš™๏ธ Miscellaneous Tasks - -- Create cli and ffi packages -- _(ffi)_ Cleanup build script -- Ignore extensions -- Split out dependencies and tests -- Support pre and post release tags -- Remove reference count checks -- Store hatch venv in .venv -- Fix sub-project git cliff config -- _(ffi)_ Clean up data directory -- [**breaking**] Drop python 3.9 add 3.14 - > Python 3.9 is no longer supported. -- Update non-compliant docstrings and types -- Upgrade pymdownx extensions -- Fix json schema url -- Remove ruff sub-configs -- Switch to versioningit - -### Contributors - -- @JP-Ellis - - diff --git a/pact-python-cli/LICENSE b/pact-python-cli/LICENSE deleted file mode 100644 index 032bed571..000000000 --- a/pact-python-cli/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Pact Foundation - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/pact-python-cli/README.md b/pact-python-cli/README.md deleted file mode 100644 index 4cf5e00e1..000000000 --- a/pact-python-cli/README.md +++ /dev/null @@ -1,119 +0,0 @@ - -# Pact Python CLI - -> [!NOTE] -> -> This package is used to package and bundle the Pact CLI _only_. It does not provide any Python functionality or API. - - -
- - - - - - - - - - - - - - - - -
Package - Version - Python Versions - Downloads -
CI/CD - Test Status - Build Status - Build Status -
Meta - Hatch project - linting - Ruff - style - Ruff - types - Mypy - License -
Community - Issues - Discussions - GitHub Stars -
- Slack - Stack Overflow - Twitter -
- - ---- - -This sub-package is part of the [Pact Python](https://github.com/pact-foundation/pact-python) project and exists solely to distribute the [Pact CLI](https://github.com/pact-foundation/pact-standalone) as a Python package. If you are looking for the main Pact Python library for contract testing, please see the [root package](https://github.com/pact-foundation/pact-python#pact-python). - -It is used by version 2 of Pact Python, and can be used to install the Pact CLI in Python environments. - -The versionining of `pact-python-cli` is aligned with the Pact CLI versioning. For example, version `2.4.26.2` corresponds to Pact CLI version `2.4.26`, with the `.2` indicating that this is the third release of that Pact CLI version in the Python package (with the first release being `.0`). - -## Installation - -You can install this package via pip: - -```console -pip install pact-python-cli -``` - -## Contributing - -Contributions to this package are generally not required as it contains minimal Python functionality and generally only requires updating the version number. This is done by pushing a tag of the form `pact-python-cli/` which will automatically trigger a release build in the CI pipeline. - -To contribute to the Pact CLI itself, please refer to the [Pact Ruby Standalone repository](https://github.com/pact-foundation/pact-standalone). - -For contributing to Pact Python, see the [main contributing guide](https://github.com/pact-foundation/pact-python/blob/main/CONTRIBUTING.md). - ---- - -For questions or support, please visit the [Pact Foundation Slack](https://slack.pact.io) or [GitHub Discussions](https://github.com/pact-foundation/pact-python/discussions). - ---- diff --git a/pact-python-cli/cliff.toml b/pact-python-cli/cliff.toml deleted file mode 100644 index 0d5368605..000000000 --- a/pact-python-cli/cliff.toml +++ /dev/null @@ -1,112 +0,0 @@ -# git-cliff configuration file -# https://git-cliff.org/docs/configuration - -[changelog] -# template for the changelog header -header = """ -# Pact Python CLI Changelog - -All notable changes to this project will be documented in this file. - -Note that this _only_ includes changes to the Python re-packaging of the Pact CLI. For changes to the Pact CLI itself, see the [Pact CLI changelog](https://github.com/pact-foundation/pact-standalone/blob/master/CHANGELOG.md). - - - - - -""" - -# template for the changelog body -# https://keats.github.io/tera/docs/#introduction -body = """ -{% if version %}\ - ## [{{ version | trim_start_matches(pat="v") }}] _{{ timestamp | date(format="%Y-%m-%d") }}_ -{% else %}\ - ## [unreleased] -{% endif %}\ -{% for group, commits in commits | group_by(attribute="group") %} - ### {{ group | striptags | trim | upper_first }} - {% for commit in commits %} - - {% if commit.scope %}_({{ commit.scope }})_ {% endif %}\ - {% if commit.breaking %}[**breaking**] {% endif %}\ - {{ commit.message | upper_first }}\ - {% if commit.breaking and commit.breaking_description %} - {{ " " }}\ - > {{ - commit.breaking_description - | split(pat="\n") - | join(sep=" ") - | replace(from=" ", to=" ") - | replace(from=" ", to=" ") - | replace(from=" ", to=" ") - | upper_first - }}\ - {% endif %}\ - {% endfor %} -{% endfor %} -{% if github.contributors %}\ - ### Contributors - {% for contributor in github.contributors %}\ - {% if contributor.username and contributor.username is ending_with("[bot]") %}{% continue %}{% endif %} - - @{{ contributor.username }}\ - {% endfor %} -{% endif %} - -""" - -# template for the changelog footer -footer = """\ - -""" - -# remove the leading and trailing s -trim = true -# postprocessors -postprocessors = [] -# render body even when there are no releases to process -# render_always = true - -[git] -tag_pattern = "^pact-python-cli/.*$" -# parse the commits based on https://www.conventionalcommits.org -conventional_commits = true -# filter out the commits that are not conventional -filter_unconventional = true -# process each line of a commit as an individual commit -split_commits = false -# regex for preprocessing the commit messages -commit_preprocessors = [ - # Remove the PR number added by GitHub when merging PR in UI - { pattern = '\s*\(#([0-9]+)\)$', replace = "" }, - # Check spelling of the commit with https://github.com/crate-ci/typos - { pattern = ".*", replace_command = "typos --write-changes -" }, -] -# regex for parsing and grouping commits -commit_parsers = [ - # Ignore deps commits from the changelog - { message = "^(chore|fix)\\(deps.*\\)", skip = true }, - { message = "^chore: update changelog.*", skip = true }, - # Group commits by type - { group = "๐Ÿš€ Features", message = "^feat" }, - { group = "๐Ÿ› Bug Fixes", message = "^fix" }, - { group = "๐Ÿšœ Refactor", message = "^refactor" }, - { group = "โšก Performance", message = "^perf" }, - { group = "๐ŸŽจ Styling", message = "^style" }, - { group = "๐Ÿ“š Documentation", message = "^docs" }, - { group = "๐Ÿงช Testing", message = "^test" }, - { group = "โ—€๏ธ Revert", message = "^revert" }, - { group = "โš™๏ธ Miscellaneous Tasks", message = "^chore" }, - { group = "๏ฟฝ Other", message = ".*" }, -] -# filter out the commits that are not matched by commit parsers -filter_commits = false -# sort the tags topologically -topo_order = false -# sort the commits inside sections by oldest/newest order -sort_commits = "oldest" -# Only include the current directory (relative to the .git directory) -include_paths = ["pact-python-cli/"] - -[remote.github] -owner = "pact-foundation" -repo = "pact-python" diff --git a/pact-python-cli/docs/SUMMARY.md b/pact-python-cli/docs/SUMMARY.md deleted file mode 100644 index 7ea99c1c4..000000000 --- a/pact-python-cli/docs/SUMMARY.md +++ /dev/null @@ -1,5 +0,0 @@ - - -- [`pact-cli`](README.md) -- [Changelog](CHANGELOG.md) -- [API](api/) diff --git a/pact-python-cli/hatch_build.py b/pact-python-cli/hatch_build.py deleted file mode 100644 index 72c75de82..000000000 --- a/pact-python-cli/hatch_build.py +++ /dev/null @@ -1,382 +0,0 @@ -""" -Hatchling build hook. - -This hook is responsible for downloading and packaging the Pact CLI. -""" - -from __future__ import annotations - -import os -import shutil -import sys -import tarfile -import tempfile -import urllib.request -import zipfile -from pathlib import Path -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: - from collections.abc import Mapping, MutableMapping, Sequence - -from hatchling.builders.config import BuilderConfig -from hatchling.builders.hooks.plugin.interface import BuildHookInterface -from packaging.tags import sys_tags -from packaging.version import Version - -PKG_DIR = Path(__file__).parent.resolve() / "src" / "pact_cli" - -# Remove when pact-standalone is removed -PACT_CLI_URL = "https://github.com/pact-foundation/pact-standalone/releases/download/v{version}/pact-{version}-{os}-{machine}.{ext}" - -# Remove fixed version and infer from package metadata when pact-cli versioning -# is adopted. -PACT_RUST_CLI_VERSION = "0.10.2" -PACT_RUST_CLI_URL = "https://github.com/pact-foundation/pact-cli/releases/download/v{version}/pact-{arch}-{os}{ext}" - - -class UnsupportedPlatformError(RuntimeError): - """ - Custom error raised when the current platform is not supported. - """ - - def __init__(self, platform: str) -> None: - """ - Initialize the exception. - - Args: - platform: - The unsupported platform. - """ - self.platform = platform - super().__init__(f"Unsupported platform {platform}") - - -class PactCliBuildHook(BuildHookInterface[BuilderConfig]): - """ - Custom hook to download Pact CLI binaries. - - This build hook is invoked by Hatch during the build process. Within - `pyproject.toml`, it takes the special name of `custom` (despite the name - below). - - For more references, see [Build hook - plugins](https://hatch.pypa.io/1.3/plugins/build-hook/reference/). - """ - - PLUGIN_NAME = "pact-cli" - - def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401 - """ - Initialize the build hook. - - For this hook, we additionally define the lib extension based on the - current platform. - """ - super().__init__(*args, **kwargs) - self.tmpdir = Path(tempfile.TemporaryDirectory().name) - self.tmpdir.mkdir(parents=True, exist_ok=True) - - def __del__(self) -> None: - """ - Clean up temporary files. - """ - shutil.rmtree(self.tmpdir, ignore_errors=True) - - def clean(self, versions: Sequence[str]) -> None: # noqa: ARG002 - """ - Code called to clean. - - This is called by `hatch clean` or when the `-c`/`--clean` flag is - passed to `hatch build`. - """ - for subdir in ["bin", "lib", "data"]: - # TODO(epoch-transition): Remove "lib" when standalone dropped # noqa: TD003 - shutil.rmtree(PKG_DIR / subdir, ignore_errors=True) - - def initialize( - self, - version: str, # noqa: ARG002 - build_data: MutableMapping[str, object], - ) -> None: - """ - Code called immediately before each build. - - The CLI version is inferred from the package metadata. Specifically, the - first three segments of the version string are used. - - Args: - version: - Not used (but required by the parent class). - - build_data: - A mutable mapping to modify in-place used by Hatch when creating - the final wheel. - - Raises: - UnsupportedPlatformError: - If the CLI cannot be built (presumably due to an - incompatible platform). - """ - cli_version = ".".join(self.metadata.version.split(".")[:3]) - if not cli_version: - msg = "Failed to determine Pact CLI version." - self.app.display_error(msg) - raise ValueError(msg) - - try: - force_include = self._install_ruby_cli(cli_version) - except UnsupportedPlatformError as err: - # The Ruby standalone is deprecated and not available on all - # platforms (e.g. Windows ARM). Warn and continue rather than - # failing the build. - self.app.display_warning( - f"Pact Ruby standalone CLI not available for {err.platform}; skipping." - ) - force_include = {} - - try: - force_include = {**force_include, **self._install_rust_cli()} - except UnsupportedPlatformError as err: - msg = f"Pact CLI is not available for {err.platform}." - self.app.display_error(msg) - raise - - build_data["force_include"] = force_include - build_data["tag"] = self._infer_tag() - - def _sys_tag_platform(self) -> str: - """ - Get the platform tag from the current system tags. - - This is used to determine the target platform for the Pact binaries. - - On Linux, the generic `linux_{arch}` tag conveys no information about - the platform's C library, so the more specific `manylinux`/`musllinux` - tag is preferred when one is available. - - Returns: - The most specific platform tag supported by the current system. - """ - platforms = (t.platform for t in sys_tags()) - preferred = next(platforms) - if preferred.startswith("linux_"): - return next( - (p for p in platforms if not p.startswith("linux_")), - preferred, - ) - return preferred - - def _install_ruby_cli(self, version: str) -> Mapping[str, str]: - """ - Install the Pact Ruby standalone binaries. - - The binaries are installed in `src/pact_cli/bin`, and the relevant - version for the current operating system is determined automatically. - - Args: - version: - The Pact CLI version to install. - - Returns: - A mapping of `src` to `dst` to be used by Hatch when creating the - wheel. Each `src` is a full path in the current filesystem, and the - `dst` is the corresponding path within the wheel. - """ - url = self._pact_ruby_bin_url(version) - artefact = self._download(url) - self._extract(artefact) - return { - str(PKG_DIR / "bin"): "pact_cli/bin", - str(PKG_DIR / "lib"): "pact_cli/lib", - } - - def _pact_ruby_bin_url(self, version: str) -> str: - """ - Generate the download URL for the Pact Ruby binaries. - - Args: - version: - The Pact CLI version to download. - - Returns: - The URL to download the Pact Ruby binaries from. If the platform is - not supported, the resulting URL may be invalid. - """ - platform = self._sys_tag_platform() - - if platform.startswith("macosx"): - # Upstream renamed the macOS assets from `osx` to `macos` in 2.6.1. - os_name = "macos" if Version(version) >= Version("2.6.1") else "osx" - ext = "tar.gz" - elif "linux" in platform: - os_name = "linux" - ext = "tar.gz" - elif platform.startswith("win"): - if platform.endswith(("arm64", "aarch64")): - # The Ruby standalone has no Windows ARM release. - raise UnsupportedPlatformError(platform) - os_name = "windows" - ext = "zip" - else: - raise UnsupportedPlatformError(platform) - - if platform.endswith(("arm64", "aarch64")): - machine = "arm64" - elif platform.endswith(("x86_64", "amd64")): - machine = "x86_64" - else: - raise UnsupportedPlatformError(platform) - - return PACT_CLI_URL.format( - version=version, - os=os_name, - machine=machine, - ext=ext, - ) - - def _pact_rust_bin_url(self, version: str) -> str: - """ - Generate the download URL for the Rust pact binary from pact-cli. - - The pact-cli release assets are plain binaries (not archives) named - ``pact-{arch}-{os}`` (e.g. ``pact-aarch64-macos``), with ``.exe`` - appended on Windows. - - Args: - version: - The pact-cli version to download. - - Returns: - The URL to the pact binary asset. - - Raises: - UnsupportedPlatformError: - If the current platform's OS or architecture is not recognised. - """ - platform = self._sys_tag_platform() - - if platform.startswith("macosx"): - os_name = "macos" - ext = "" - elif "linux" in platform: - # musl-based Linux targets (e.g. Alpine) are not distinguished; - # the linux-gnu binary is used for all Linux targets. - os_name = "linux-gnu" - ext = "" - elif platform.startswith("win"): - os_name = "windows-msvc" - ext = ".exe" - else: - raise UnsupportedPlatformError(platform) - - if platform.endswith(("arm64", "aarch64")): - arch = "aarch64" - elif platform.endswith(("x86_64", "amd64")): - arch = "x86_64" - else: - raise UnsupportedPlatformError(platform) - - return PACT_RUST_CLI_URL.format( - version=version, - arch=arch, - os=os_name, - ext=ext, - ) - - def _install_rust_cli(self) -> Mapping[str, str]: - """ - Install the Rust pact binary from pact-cli. - - Overwrites the `pact` binary bundled with pact-standalone. - - The binary is downloaded from the pact-cli GitHub release as a plain - executable (not an archive) and placed in `bin/` as `pact` - (or `pact.exe` on Windows). - """ - url = self._pact_rust_bin_url(PACT_RUST_CLI_VERSION) - artefact = self._download(url) - - bin_name = "pact.exe" if sys.platform == "win32" else "pact" - dest = PKG_DIR / "bin" / bin_name - dest.parent.mkdir(parents=True, exist_ok=True) - shutil.copy2(artefact, dest) - if sys.platform != "win32": - dest.chmod(0o755) - - return {str(PKG_DIR / "bin"): "pact_cli/bin"} - - def _extract(self, artefact: Path) -> None: - """ - Extract the Pact binaries. - - The binaries in the `bin` directory require the underlying Ruby runtime - to be present, which is included in the `lib` directory. - - Args: - artefact: - The path to the downloaded artefact. - """ - if str(artefact).endswith(".zip"): - with zipfile.ZipFile(artefact) as f: - f.extractall(self.tmpdir) # noqa: S202 - - if str(artefact).endswith(".tar.gz"): - with tarfile.open(artefact) as f: - f.extractall(self.tmpdir) # noqa: S202 - - for d in ["bin", "lib"]: - if (PKG_DIR / d).is_dir(): - shutil.rmtree(PKG_DIR / d) - shutil.copytree( - Path(self.tmpdir) / "pact" / d, - PKG_DIR / d, - ) - - def _download(self, url: str) -> Path: - """ - Download the target URL. - - This will download the target URL to the `src/pact_cli/data` directory. - If the download artefact is already present, the existing artefact's - path will be returned without downloading it again. - - Args: - url: - The URL to download - - Returns: - The path to the downloaded artefact. - """ - filename = url.rsplit("/", maxsplit=1)[-1] - artefact = PKG_DIR / "data" / filename - artefact.parent.mkdir(parents=True, exist_ok=True) - - if not artefact.exists(): - urllib.request.urlretrieve(url, artefact) # noqa: S310 - return artefact - - def _infer_tag(self) -> str: - """ - Infer the tag for the current build. - - Since we have a pure Python wrapper around a binary CLI, we are not - tied to any specific Python version or ABI. As a result, we generate - `py3-none-{platform}` tags for the wheels. - """ - platform = self._sys_tag_platform() - - # On macOS, the version needs to be set based on the deployment target - # (i.e., the version of the system libraries). - if sys.platform == "darwin" and ( - deployment_target := os.environ.get("MACOSX_DEPLOYMENT_TARGET") - ): - target = deployment_target.replace(".", "_") - if platform.endswith("_arm64"): - platform = f"macosx_{target}_arm64" - elif platform.endswith("_x86_64"): - platform = f"macosx_{target}_x86_64" - else: - raise UnsupportedPlatformError(platform) - - return f"py3-none-{platform}" diff --git a/pact-python-cli/pyproject.toml b/pact-python-cli/pyproject.toml deleted file mode 100644 index b81add323..000000000 --- a/pact-python-cli/pyproject.toml +++ /dev/null @@ -1,141 +0,0 @@ -#:schema https://www.schemastore.org/pyproject.json - -[project] -name = "pact-python-cli" -version = "2.6.4.0" -description = "Pact CLI bundle for Python" -readme = "README.md" -license = "MIT" -keywords = ["cli", "contract-testing", "pact", "pact-python"] - -authors = [{ name = "Joshua Ellis", email = "josh@jpellis.me" }] -maintainers = [{ name = "Joshua Ellis", email = "josh@jpellis.me" }] - -classifiers = [ - "Development Status :: 5 - Production/Stable", - "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: MacOS :: MacOS X", - "Operating System :: Microsoft :: Windows", - "Operating System :: POSIX :: Linux", - "Programming Language :: Python", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - "Programming Language :: Python :: 3.15", - "Topic :: Software Development :: Testing", -] - -requires-python = ">=3.11" - - [project.urls] - "Bug Tracker" = "https://github.com/pact-foundation/pact-python/issues" - "Changelog" = "https://github.com/pact-foundation/pact-python/blob/main/pact-python-cli/CHANGELOG.md" - "Documentation" = "https://docs.pact.io" - "Homepage" = "https://pact.io" - "Repository" = "https://github.com/pact-foundation/pact-python" - - [project.scripts] - pact = "pact_cli:_exec" - pact-broker = "pact_cli:_exec" - pact-message = "pact_cli:_exec" - pact_mock_server_cli = "pact_cli:_exec" - pact-mock-service = "pact_cli:_exec" - pact-plugin-cli = "pact_cli:_exec" - pact-provider-verifier = "pact_cli:_exec" - pact-stub-server = "pact_cli:_exec" - pact-stub-service = "pact_cli:_exec" - pact_verifier_cli = "pact_cli:_exec" - pactflow = "pact_cli:_exec" - -[dependency-groups] -# Linting and formatting tools use a more narrow specification to ensure -# developper consistency. All other dependencies are as above. -dev = ["ruff==0.17.0", { include-group = "test" }, { include-group = "types" }] -test = ["pytest~=9.0", "pytest-cov~=7.0"] -types = ["mypy==2.4.0"] - -## Build System -[build-system] -requires = ["hatchling", "packaging"] -build-backend = "hatchling.build" - -[tool] - [tool.cibuildwheel] - # The repair tool unfortunately did not like the bundled Ruby distributable, - # with false-positives missing libraries despite being bundled. - repair-wheel-command = "" - - [tool.cibuildwheel.windows] - archs = ["auto64"] - - [tool.coverage] - [tool.coverage.paths] - pact-cli = ["/src/pact_cli"] - tests = ["/tests"] - - [tool.coverage.report] - exclude_lines = [ - "@(abc\\.)?abstractmethod", # Ignore abstract methods - "if TYPE_CHECKING:", # Ignore typing - "if __name__ == .__main__.:", # Ignore non-runnable code - "raise NotImplementedError", # Ignore defensive assertions - ] - - [tool.hatch] - [tool.hatch.build] - packages = ["src/pact_cli"] - - [tool.hatch.build.targets.wheel.hooks.custom] - patch = "hatch_build.py" - - [tool.hatch.envs] - [tool.hatch.envs.default] - installer = "uv" - path = ".venv" - extra-dependencies = [ - "hatchling", - "packaging", - "requests", - "setuptools ; python_version >= '3.12'", - ] - dependency-groups = ["dev"] - - [tool.hatch.envs.default.scripts] - all = ["format", "lint", "test", "typecheck"] - format = "ruff format {args}" - lint = "ruff check --show-fixes {args}" - test = "pytest tests/ {args}" - typecheck = ["typecheck-src", "typecheck-tests"] - typecheck-src = "mypy src/ {args}" - typecheck-tests = "mypy tests/ {args}" - - # Test environment for running unit tests. - [tool.hatch.envs.test] - installer = "uv" - path = ".venv/test" - dependency-groups = ["test"] - - [[tool.hatch.envs.test.matrix]] - python = ["3.11", "3.12", "3.13", "3.14", "3.15"] - - ## PyTest Configuration - [tool.pytest] - addopts = [ - # Coverage options - "--cov-config=pyproject.toml", - "--cov-report=xml", - "--cov=pact_cli", - "--import-mode=importlib", - ] - - log_date_format = "%H:%M:%S" - log_format = "%(asctime)s.%(msecs)03d [%(levelname)-8s] %(name)s: %(message)s" - log_level = "NOTSET" - - ## Ruff Configuration - [tool.ruff] - extend = "../pyproject.toml" diff --git a/pact-python-cli/src/pact_cli/.gitkeep b/pact-python-cli/src/pact_cli/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/pact-python-cli/src/pact_cli/__init__.py b/pact-python-cli/src/pact_cli/__init__.py deleted file mode 100644 index 1e65d5fb1..000000000 --- a/pact-python-cli/src/pact_cli/__init__.py +++ /dev/null @@ -1,281 +0,0 @@ -""" -Locate and provide paths to bundled or system Pact CLI executables. - -This module exists solely to bundle and distribute the Pact CLI tools as a -Python package. It does not provide any substantive functionality beyond -locating the Pact CLI executables and providing their paths for use in Python -code. - -The module exposes constants for the absolute paths to the Pact CLI executables (such as -`pact-broker`, `pact-message`, `pact-mock-service`, and `pact-provider-verifier`). - -By default, the module will use the binaries bundled with the package. If the -environment variable `PACT_USE_SYSTEM_BINS` is set to `TRUE` or `YES`, or if the bundled -binaries are unavailable, it will fall back to using the system-installed Pact CLI tools -if found in the system PATH. - -This package is intended for use as a dependency to ensure the Pact CLI is available in -Python environments, such as CI pipelines or local development, without requiring a -separate installation step. - -For more information, see the project README or -https://github.com/pact-foundation/pact-python. -""" - -from __future__ import annotations - -__author__ = "Pact Foundation" -__license__ = "MIT" -__url__ = "https://github.com/pact-foundation/pact-python" - -import os -import shutil -import sys -import sysconfig -import warnings -from pathlib import Path -from typing import TYPE_CHECKING - -from pact_cli.__version__ import ( - __version__ as __version__, -) -from pact_cli.__version__ import ( - __version_tuple__ as __version_tuple__, -) - -if TYPE_CHECKING: - from collections.abc import Iterator, Mapping - -_USE_SYSTEM_BINS = os.getenv("PACT_USE_SYSTEM_BINS", "").upper() in ("TRUE", "YES") -_BIN_DIR = Path(__file__).parent.resolve() / "bin" -_DEPRECATED_COMMANDS: Mapping[str, str | None] = { - "pact-broker": "pact broker", - "pact-message": None, # being removed; no Rust equivalent - "pact-mock-service": "pact mock", - "pact-plugin-cli": "pact plugin", - "pact-provider-verifier": "pact verifier", - "pact-stub-server": "pact stub", - "pact-stub-service": "pact stub", - "pact_mock_server_cli": "pact mock", - "pact_verifier_cli": "pact verifier", - "pactflow": "pact pactflow", -} - - -def _telemetry_env() -> Mapping[str, str]: - """ - Get environment variables with Pact telemetry data. - - Returns a copy of the current environment with the following two keys added: - - - `PACT_EXECUTING_LANGUAGE`: Set to "python". - - `PACT_EXECUTING_LANGUAGE_VERSION`: Set to the current Python version - in "major.minor" format. - - Returns: - Environment dictionary Pact telemetry added. - """ - env = os.environ.copy() - env["PACT_EXECUTING_LANGUAGE"] = "python" - version = f"{sys.version_info.major}.{sys.version_info.minor}" - env["PACT_EXECUTING_LANGUAGE_VERSION"] = version - return env - - -def _exec() -> None: - """ - Execute Pact CLI tools routed through the generated entry points. - - This function is exposed via `pyproject.toml` console scripts and forwards - the provided command-line arguments to the matching Pact CLI binary. - - Raises: - SystemExit: - If the requested command is unknown or an executable cannot be - located. - """ - import sys # noqa: PLC0415 - - command = Path(sys.argv[0]).name - args = sys.argv[1:] if len(sys.argv) > 1 else [] - - if command not in ( - "pact", - "pact-broker", - "pact-message", - "pact-mock-service", - "pact-plugin-cli", - "pact-provider-verifier", - "pact-stub-server", - "pact-stub-service", - "pact_mock_server_cli", - "pact_verifier_cli", - "pactflow", - ): - print("Unknown command:", command, file=sys.stderr) # noqa: T201 - sys.exit(1) - - if command in _DEPRECATED_COMMANDS: - replacement = _DEPRECATED_COMMANDS[command] - if replacement: - print( # noqa: T201 - f"WARNING: '{command}' is deprecated and will be removed in a " - f"future release. Use '{replacement}' instead.\n", - file=sys.stderr, - flush=True, - ) - else: - print( # noqa: T201 - f"WARNING: '{command}' is deprecated and will be removed in a " - "future release.\n", - file=sys.stderr, - flush=True, - ) - - # To avoid finding the same executable, remove the current entry point's - # directory from PATH before searching. - script_dir = Path(sys.argv[0]).parent.resolve() - os.environ["PATH"] = os.pathsep.join( - p - for p in os.getenv("PATH", "").split(os.pathsep) - if Path(p).resolve() != script_dir - ) - executable = _find_executable(command) - - if not executable: - print(f"Command '{command}' not found.", file=sys.stderr) # noqa: T201 - sys.exit(1) - - os.execve(executable, [executable, *args], _telemetry_env()) # noqa: S606 - - -def _bundled_bin_dirs() -> Iterator[Path]: - """ - Determine the directories that may contain the bundled Pact binaries. - - The binaries are normally bundled in a `bin` directory alongside this - module, which is the first location searched. Relying solely on the - module-relative path is fragile, however: for editable or relocated - installs, or when a virtual environment is reused across several checkouts, - `__file__` may resolve to a directory that no longer contains the binaries. - The installation's `site-packages` directories are therefore also derived - from `sysconfig` so the bundled `bin` directory can be located independently - of `__file__`. - - Returns: - The candidate directories to search, in order of preference and - deduplicated. - """ - seen: set[Path] = set() - resolved_bin_dir = _BIN_DIR.resolve() - if resolved_bin_dir.is_dir(): - yield resolved_bin_dir - seen.add(resolved_bin_dir) - for name in ("platlib", "purelib"): - if ( - (path := sysconfig.get_path(name)) - and (candidate := Path(path).resolve() / "pact_cli" / "bin") - and candidate.is_dir() - and candidate not in seen - ): - yield candidate - seen.add(candidate) - - -def _find_executable(executable: str) -> str | None: - """ - Find the path to an executable. - - This inspects the environment variable `PACT_USE_SYSTEM_BINS` to determine - whether to use the bundled Pact binaries or the system ones. Note that if - the local executables are not found, this will fall back to the system - executables (if found). - - Args: - executable: - The name of the executable to find without the extension. Python - will automatically append the correct extension for the current - platform. - - Returns: - The absolute path to the executable. - - Warns: - RuntimeWarning: - If the executable cannot be found in the system path. - """ - if _USE_SYSTEM_BINS: - bin_path = shutil.which(executable) - else: - search_path = os.pathsep.join(str(d) for d in _bundled_bin_dirs()) - bin_path = shutil.which(executable, path=search_path) if search_path else None - if bin_path is None: - system_path = shutil.which(executable) - if system_path is not None: - warnings.warn( - f"Bundled {executable} binary not found; " - "using system installation instead.", - RuntimeWarning, - stacklevel=2, - ) - bin_path = system_path - if bin_path is None: - msg = f"Unable to find {executable} binary executable." - warnings.warn(msg, RuntimeWarning, stacklevel=2) - return bin_path - - -PACT_PATH = _find_executable("pact") -""" -Path to the Pact executable -""" -BROKER_PATH = _find_executable("pact-broker") -""" -Path to the Pact Broker executable -""" -BROKER_CLIENT_PATH = _find_executable("pact-broker") -""" -Path to the Pact Broker executable - -This value is identical to `BROKER_PATH` and is provided for backward -compatibility. -""" -MESSAGE_PATH = _find_executable("pact-message") -""" -Path to the Pact Message executable -""" -MOCK_SERVICE_PATH = _find_executable("pact-mock-service") -""" -Path to the Pact Mock Service executable -""" -PLUGIN_CLI_PATH = _find_executable("pact-plugin-cli") -""" -Path to the Pact Plugin CLI executable -""" -VERIFIER_PATH = _find_executable("pact-provider-verifier") -""" -Path to the Pact Provider Verifier executable -""" -STUB_SERVER_PATH = _find_executable("pact-stub-server") -""" -Path to the Pact Stub Server executable -""" -STUB_SERVICE_PATH = _find_executable("pact-stub-service") -""" -Path to the Pact Stub Service executable -""" -MOCK_SERVER_PATH = _find_executable("pact_mock_server_cli") -""" -Path to the Pact Mock Server CLI executable -""" -VERIFIER_CLI_PATH = _find_executable("pact_verifier_cli") -""" -Path to the Pact Verifier CLI executable - -This is distinct to the `VERIFIER_PATH` which points to the older Ruby-based -CLI. -""" -PACTFLOW_PATH = _find_executable("pactflow") -""" -Path to the PactFlow CLI executable -""" diff --git a/pact-python-cli/src/pact_cli/__version__.py b/pact-python-cli/src/pact_cli/__version__.py deleted file mode 100644 index 9ecfb7454..000000000 --- a/pact-python-cli/src/pact_cli/__version__.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Version information for pact-python-cli.""" - -from importlib.metadata import PackageNotFoundError, version - -try: - __version__ = version("pact-python-cli") -except PackageNotFoundError: - __version__ = "unknown" - -__version_tuple__ = tuple(int(x) for x in __version__.split(".") if x.isdigit()) diff --git a/pact-python-cli/src/pact_cli/py.typed b/pact-python-cli/src/pact_cli/py.typed deleted file mode 100644 index e69de29bb..000000000 diff --git a/pact-python-cli/tests/test_init.py b/pact-python-cli/tests/test_init.py deleted file mode 100644 index 1fc560b51..000000000 --- a/pact-python-cli/tests/test_init.py +++ /dev/null @@ -1,283 +0,0 @@ -"""Test the values in exported constants.""" - -from __future__ import annotations - -import os -import shutil -import subprocess -import sys -import sysconfig -import time -from pathlib import Path -from unittest.mock import patch - -import pytest - -import pact_cli - - -def bin_to_sitepackages(exec_path: str | Path) -> Path: - """ - Compute the expected site-packages directory for a Pact executable. - - Args: - exec_path: - Path to the binary whose site-packages root should be derived. - - Returns: - Path to the site-packages directory associated with the executable. - """ - if os.name == "nt": - return Path(exec_path).parents[1] / "Lib" / "site-packages" - return ( - Path(exec_path).parents[1] - / "lib" - / f"python{sys.version_info.major}.{sys.version_info.minor}" - / "site-packages" - ) - - -def assert_in_sys_path(p: str | Path) -> None: - """ - Assert that a resolved path exists in ``sys.path``. - - This performs normalization on case-insensitive filesystems to avoid - comparison errors. - - Args: - p: - Path that should be discoverable via `sys.path`. - """ - if os.name == "nt": - assert str(p).lower() in (path.lower() for path in sys.path) - else: - assert str(p) in sys.path - - -@pytest.mark.parametrize( - ("constant", "expected"), - [ - pytest.param("BROKER_CLIENT_PATH", "pact-broker", id="pact-broker"), - pytest.param("BROKER_PATH", "pact-broker", id="pact-broker"), - pytest.param("MESSAGE_PATH", "pact-message", id="pactmessage"), - pytest.param( - "MOCK_SERVER_PATH", "pact_mock_server_cli", id="pact_mock_server_cli" - ), - pytest.param("MOCK_SERVICE_PATH", "pact-mock-service", id="pact-mock-service"), - pytest.param("PACTFLOW_PATH", "pactflow", id="pactflow"), - pytest.param("PACT_PATH", "pact", id="pact"), - pytest.param("PLUGIN_CLI_PATH", "pact-plugin-cli", id="pact-plugin-cli"), - pytest.param("STUB_SERVER_PATH", "pact-stub-server", id="pact-stub-server"), - pytest.param("STUB_SERVICE_PATH", "pact-stub-service", id="pact-stub-service"), - pytest.param("VERIFIER_CLI_PATH", "pact_verifier_cli", id="pact_verifier_cli"), - pytest.param( - "VERIFIER_PATH", "pact-provider-verifier", id="pact-provider-verifier" - ), - ], -) -def test_constants_are_valid_executable_paths(constant: str, expected: str) -> None: - value: str = getattr(pact_cli, constant) - if os.name == "nt": - # As the Windows filesystem is case insensitive, we must normalize it. - assert value.lower().endswith((f"{expected}.bat", f"{expected}.exe")) - else: - assert value.endswith(expected) - - -@pytest.mark.parametrize( - "executable", - [ - pytest.param("pact", id="pact"), - pytest.param("pact-broker", id="pact-broker"), - pytest.param("pact-message", id="pact-message"), - pytest.param("pact-plugin-cli", id="pact-plugin-cli"), - pytest.param("pact-provider-verifier", id="pact-provider-verifier"), - pytest.param("pact-stub-server", id="pact-stub-server"), - pytest.param("pact-stub-service", id="pact-stub-service"), - pytest.param("pact_mock_server_cli", id="pact_mock_server_cli"), - pytest.param("pact_verifier_cli", id="pact_verifier_cli"), - pytest.param("pactflow", id="pactflow"), - ], -) -def test_cli_exec_wrapper(executable: str) -> None: - exec_path = shutil.which(executable) - assert exec_path - - site_packages = bin_to_sitepackages(exec_path) - assert site_packages.is_dir() - assert_in_sys_path(site_packages) - - result = subprocess.run( # noqa: S603 - [exec_path, "--help"], - check=False, # Some CLIs return non-zero for --help - text=True, - capture_output=True, - ) - assert "pact" in (result.stdout + result.stderr).lower() - - result = subprocess.run( # noqa: S603 - [exec_path], - check=False, # Some CLIs return non-zero for --help - text=True, - capture_output=True, - ) - assert "pact" in (result.stdout + result.stderr).lower() - - -def test_cli_exec_wrapper_for_mock_service() -> None: - """ - Same as `test_cli_exec_wrapper` for the `pact-mock-server`. - - The Pact mock service is a long running service, as it is expected to run a - mock service which can be tested against. The test pattern above doesn't - work, and instead, we spawn the process, wait a bit, terminate it, and then - check the output. - """ - executable = "pact-mock-service" - exec_path = shutil.which(executable) - assert exec_path - site_packages = bin_to_sitepackages(exec_path) - assert site_packages.is_dir() - assert_in_sys_path(site_packages) - - process = subprocess.Popen( # noqa: S603 - [exec_path], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - time.sleep(2) - process.terminate() - process.wait() - - stdout, stderr = process.communicate() - assert "pact" in (stdout + stderr).lower() - - -@pytest.mark.parametrize( - "executable", - [ - pytest.param("pact", id="pact"), - pytest.param("pact-broker", id="pact-broker"), - pytest.param("pact-message", id="pact-message"), - pytest.param("pact-mock-service", id="pact-mock-service"), - pytest.param("pact-plugin-cli", id="pact-plugin-cli"), - pytest.param("pact-provider-verifier", id="pact-provider-verifier"), - pytest.param("pact-stub-server", id="pact-stub-server"), - pytest.param("pact-stub-service", id="pact-stub-service"), - pytest.param("pact_mock_server_cli", id="pact_mock_server_cli"), - pytest.param("pact_verifier_cli", id="pact_verifier_cli"), - pytest.param("pactflow", id="pactflow"), - ], -) -def test_exec_directly(executable: str) -> None: - """ - Invoke ``pact_cli._exec`` directly to confirm ``execv`` receives the command. - """ - cmd: str - args: list[str] - - with ( - patch.object(sys, "argv", new=[executable, "--help"]), - patch("os.execve") as mock_execve, - ): - pact_cli._exec() # noqa: SLF001 - mock_execve.assert_called_once() - cmd, args, env = mock_execve.call_args[0] - assert (os.sep + executable) in cmd - assert args == [cmd, "--help"] - assert env - - patch.object(sys, "argv", new=[executable]) - with ( - patch.object(sys, "argv", new=[executable]), - patch("os.execve") as mock_execve, - ): - pact_cli._exec() # noqa: SLF001 - mock_execve.assert_called_once() - cmd, args, env = mock_execve.call_args[0] - assert (os.sep + executable) in cmd - assert args == [cmd] - assert env - - -def test_deprecated_command_warns_with_replacement( - capsys: pytest.CaptureFixture[str], -) -> None: - """Ruby commands with a Rust equivalent print a hint to stderr.""" - with ( - patch.object(sys, "argv", new=["pact-broker", "--help"]), - patch("os.execve"), - ): - pact_cli._exec() # noqa: SLF001 - captured = capsys.readouterr() - assert "deprecated" in captured.err - assert "pact broker" in captured.err - - -def test_deprecated_command_warns_without_replacement( - capsys: pytest.CaptureFixture[str], -) -> None: - """Ruby commands without a Rust equivalent print a generic warning.""" - with ( - patch.object(sys, "argv", new=["pact-message", "--help"]), - patch("os.execve"), - ): - pact_cli._exec() # noqa: SLF001 - captured = capsys.readouterr() - assert "deprecated" in captured.err - assert "Use " not in captured.err # no replacement hint - - -def test_pact_command_does_not_warn(capsys: pytest.CaptureFixture[str]) -> None: - """The Rust pact binary does not trigger any deprecation warning.""" - with ( - patch.object(sys, "argv", new=["pact", "--help"]), - patch("os.execve"), - ): - pact_cli._exec() # noqa: SLF001 - captured = capsys.readouterr() - assert "deprecated" not in captured.err - - -def test_find_executable_resolves_via_sysconfig( - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - """ - Bundled binaries are found via the install location, not just `__file__`. - - This reproduces the cross-checkout failure: a virtual environment is reused - across multiple checkouts, so the module-relative `bin` directory (derived - from `__file__`) points at a location that no longer contains the binary. - The binary remains discoverable through the install's `site-packages` - location reported by `sysconfig`. - """ - # The module-relative bin directory exists but does not contain the binary. - empty_bin = tmp_path / "stale" / "pact_cli" / "bin" - empty_bin.mkdir(parents=True) - monkeypatch.setattr(pact_cli, "_BIN_DIR", empty_bin) - monkeypatch.setattr(pact_cli, "_USE_SYSTEM_BINS", False) - - # The binary is only present in the install's site-packages bin directory. - site_packages = tmp_path / "site-packages" - real_bin = site_packages / "pact_cli" / "bin" - real_bin.mkdir(parents=True) - name = "pact.exe" if os.name == "nt" else "pact" - binary = real_bin / name - binary.write_text("") - binary.chmod(0o755) - - monkeypatch.setattr( - sysconfig, - "get_path", - lambda key, *_args, **_kwargs: ( - str(site_packages) if key in ("platlib", "purelib") else None - ), - ) - # Ensure the binary is not discoverable on PATH. - monkeypatch.setenv("PATH", str(tmp_path / "nonexistent")) - - found = pact_cli._find_executable("pact") # noqa: SLF001 - assert found is not None - assert Path(found).resolve() == binary.resolve() diff --git a/pact-python-cli/tests/test_telemetry.py b/pact-python-cli/tests/test_telemetry.py deleted file mode 100644 index 0437dd22a..000000000 --- a/pact-python-cli/tests/test_telemetry.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Tests for telemetry environment variables.""" - -from __future__ import annotations - -import sys -from unittest.mock import patch - -from pact_cli import _telemetry_env - - -def test_telemetry_env_sets_language() -> None: - env = _telemetry_env() - assert env["PACT_EXECUTING_LANGUAGE"] == "python" - - -def test_telemetry_env_sets_version() -> None: - env = _telemetry_env() - expected_version = f"{sys.version_info.major}.{sys.version_info.minor}" - assert env["PACT_EXECUTING_LANGUAGE_VERSION"] == expected_version - - -def test_telemetry_env_preserves_existing_env() -> None: - mock_env = {"EXISTING_VAR": "existing_value", "PATH": "/usr/bin"} - with patch("os.environ", mock_env): - env = _telemetry_env() - assert env["EXISTING_VAR"] == "existing_value" - assert env["PATH"] == "/usr/bin" - assert "PACT_EXECUTING_LANGUAGE" in env - assert "PACT_EXECUTING_LANGUAGE_VERSION" in env - - -def test_telemetry_env_returns_copy() -> None: - env1 = _telemetry_env() - env2 = _telemetry_env() - assert env1 is not env2 diff --git a/prek.toml b/prek.toml index a485cc29c..fbfe4172c 100644 --- a/prek.toml +++ b/prek.toml @@ -91,7 +91,7 @@ repo = "local" language = "system" types = ["python"] exclude = '''(?x)( - ^pact-python-(cli|ffi)/ + ^pact-python-ffi/ | ^examples/plugins/proto/.*_pb2(_grpc)?\.py )''' stages = ["pre-push"] diff --git a/pyproject.toml b/pyproject.toml index 9094856d8..88c2298ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,6 @@ example = [ test = [ "aiohttp~=3.0", "flask~=3.0", - "pact-python-cli", "pytest~=9.0", "pytest-asyncio~=1.0", "pytest-bdd~=9.0", @@ -229,7 +228,7 @@ build-backend = "hatchling.build" convention = "google" [tool.ruff.lint.isort] - known-first-party = ["pact", "pact_cli", "pact_ffi"] + known-first-party = ["pact", "pact_ffi"] [tool.ruff.lint.flake8-tidy-imports] ban-relative-imports = "all" @@ -263,11 +262,10 @@ build-backend = "hatchling.build" [tool.uv] [tool.uv.sources] - pact-python-cli = { workspace = true } pact-python-ffi = { workspace = true } [tool.uv.workspace] - members = ["pact-python-cli", "pact-python-ffi"] + members = ["pact-python-ffi"] [tool.yamlfix] line_length = 100 diff --git a/scripts/release.py b/scripts/release.py index c1c617f83..c9468bca7 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -5,11 +5,9 @@ Usage: python scripts/release.py prepare core [--dry-run] python scripts/release.py prepare ffi [--dry-run] - python scripts/release.py prepare cli [--dry-run] python scripts/release.py tag core python scripts/release.py tag ffi - python scripts/release.py tag cli """ from __future__ import annotations @@ -203,7 +201,7 @@ def compute_next_version(self) -> str | None: - **Core** (`upstream_repo` is `None`): asks git cliff for the next semver implied by unreleased conventional commits; returns `None` when there is nothing to release. - - **FFI / CLI** (`upstream_repo` set): fetches the latest upstream GitHub + - **FFI** (`upstream_repo` set): fetches the latest upstream GitHub release and derives the 4-part version via `compute_wrapper_version`. Returns: @@ -516,7 +514,6 @@ def tag(self, *, dry_run: bool = False) -> None: # Each package uses a different versioning strategy: # core โ€” git cliff --bumped-version derives the next semver from commit history. # ffi โ€” tracks pact-foundation/pact-reference; version is {upstream}.{N}. -# cli โ€” tracks pact-foundation/pact-standalone; version is {upstream}.{N}. # The {N} suffix increments when the upstream semver is unchanged, resets to 0 # on a new upstream release (see compute_wrapper_version). PACKAGES: dict[str, Package] = { @@ -539,15 +536,6 @@ def tag(self, *, dry_run: bool = False) -> None: upstream_tag_prefix="libpact_ffi-v", release_branch="release/pact-python-ffi", ), - "cli": Package( - name="pact-python-cli", - key="cli", - directory=ROOT / "pact-python-cli", - tag_prefix="pact-python-cli/", - upstream_repo="pact-foundation/pact-standalone", - upstream_tag_prefix="v", - release_branch="release/pact-python-cli", - ), } @@ -649,7 +637,7 @@ def main() -> None: description="Release management for pact-python packages" ) parser.add_argument("command", choices=["prepare", "tag"]) - parser.add_argument("package", choices=["core", "ffi", "cli"]) + parser.add_argument("package", choices=["core", "ffi"]) parser.add_argument( "--dry-run", action="store_true", diff --git a/src/pact/verifier.py b/src/pact/verifier.py index 0f1e12b66..cd01fef8a 100644 --- a/src/pact/verifier.py +++ b/src/pact/verifier.py @@ -1612,7 +1612,7 @@ def consumer_version( # noqa: PLR0913 deployed or released and currently supported in any environment. Use of this selector requires that the deployment of the consumer application is recorded in the Pact Broker using the - `pact-broker record-deployment` or `pact-broker record-release` + `pact broker record-deployment` or `pact broker record-release` CLI. deployed: @@ -1622,7 +1622,7 @@ def consumer_version( # noqa: PLR0913 pacts for all versions of the consumer that are currently deployed to any environment. Use of this selector requires that the deployment of the consumer application is recorded in the - Pact Broker using the `pact-broker record-deployment` CLI. + Pact Broker using the `pact broker record-deployment` CLI. released: Applications that have been released. @@ -1631,7 +1631,7 @@ def consumer_version( # noqa: PLR0913 pacts for all versions of the consumer that are released and currently supported in any environment. Use of this selector requires that the deployment of the consumer application is - recorded in the Pact Broker using the `pact-broker + recorded in the Pact Broker using the `pact broker record-release` CLI. environment: diff --git a/tests/compatibility_suite/util/provider.py b/tests/compatibility_suite/util/provider.py index e065144b4..39c6599cc 100644 --- a/tests/compatibility_suite/util/provider.py +++ b/tests/compatibility_suite/util/provider.py @@ -19,6 +19,7 @@ import json import logging import re +import shutil import subprocess import warnings from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer @@ -33,7 +34,6 @@ from pytest_bdd import given, parsers, then, when from yarl import URL -import pact_cli from pact import __version__ from pact._server import MessageProducer from pact._util import find_free_port @@ -341,10 +341,10 @@ def __init__( self.provider = provider self.consumer = consumer - if bin_path := pact_cli.BROKER_CLIENT_PATH: - self.broker_bin = bin_path + if bin_path := shutil.which("pact"): + self.pact_bin = bin_path else: - msg = "pact-broker not found" + msg = "pact not found; run `mise install` to install the Pact CLI" raise RuntimeError(msg) def reset(self) -> None: @@ -378,7 +378,8 @@ def publish(self, directory: Path | str, version: str | None = None) -> None: The version of the consumer application. """ cmd = [ - self.broker_bin, + self.pact_bin, + "broker", "publish", str(directory), "--broker-base-url",