Skip to content

Commit c609333

Browse files
committed
feat(ffi): add standalone ffi package
Signed-off-by: JP-Ellis <[email protected]>
1 parent 063f31b commit c609333

File tree

15 files changed

+8900
-10
lines changed

15 files changed

+8900
-10
lines changed

.github/workflows/build-ffi.yml

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
---
2+
name: build ffi
3+
4+
on:
5+
push:
6+
tags:
7+
- pact-python-ffi/*
8+
branches:
9+
- main
10+
pull_request:
11+
branches:
12+
- main
13+
14+
permissions:
15+
contents: read
16+
17+
concurrency:
18+
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
19+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
20+
21+
env:
22+
STABLE_PYTHON_VERSION: '39'
23+
HATCH_VERBOSE: '1'
24+
FORCE_COLOR: '1'
25+
CIBW_BUILD_FRONTEND: build
26+
27+
jobs:
28+
complete:
29+
name: Build FFI completion check
30+
if: always()
31+
32+
permissions:
33+
contents: none
34+
35+
runs-on: ubuntu-latest
36+
needs:
37+
- build-sdist
38+
- build-wheels
39+
40+
steps:
41+
- name: Failed
42+
run: exit 1
43+
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
44+
45+
build-sdist:
46+
name: Build FFI source distribution
47+
48+
runs-on: ubuntu-latest
49+
50+
steps:
51+
- name: Checkout code
52+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
53+
with:
54+
fetch-depth: 0
55+
56+
- name: Set up uv
57+
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
58+
with:
59+
enable-cache: true
60+
61+
- name: Install Python
62+
run: uv python install ${{ env.STABLE_PYTHON_VERSION }}
63+
64+
- name: Install hatch
65+
run: uv tool install hatch
66+
67+
- name: Create source distribution
68+
working-directory: pact-python-ffi
69+
run: hatch build --target sdist
70+
71+
- name: Upload sdist
72+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
73+
with:
74+
name: wheels-sdist
75+
path: pact-python-ffi/dist/*.tar*
76+
if-no-files-found: error
77+
compression-level: 0
78+
79+
build-wheels:
80+
name: Build FFI wheels on ${{ matrix.os }}
81+
82+
runs-on: ${{ matrix.os }}
83+
strategy:
84+
fail-fast: false
85+
matrix:
86+
include:
87+
- os: macos-13
88+
- os: macos-latest
89+
- os: ubuntu-24.04-arm
90+
- os: ubuntu-latest
91+
- os: windows-11-arm
92+
- os: windows-latest
93+
94+
steps:
95+
- name: Checkout code
96+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
97+
with:
98+
fetch-depth: 0
99+
100+
- name: Create wheels
101+
uses: pypa/cibuildwheel@95d2f3a92fbf80abe066b09418bbf128a8923df2 # v3.0.1
102+
with:
103+
package-dir: pact-python-ffi
104+
env:
105+
CIBW_BUILD: cp${{ env.STABLE_PYTHON_VERSION }}-*
106+
HATCH_VERBOSE: '1'
107+
108+
- name: Upload wheels
109+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
110+
with:
111+
name: wheels-${{ matrix.os }}
112+
path: wheelhouse/*.whl
113+
if-no-files-found: error
114+
compression-level: 0
115+
116+
publish:
117+
name: Publish FFI wheels and sdist
118+
119+
if: >-
120+
github.event_name == 'push' &&
121+
startsWith(github.event.ref, 'refs/tags/pact-python-ffi/')
122+
runs-on: ubuntu-latest
123+
environment:
124+
name: pypi
125+
url: https://pypi.org/p/pact-python-ffi
126+
127+
needs:
128+
- build-sdist
129+
- build-wheels
130+
131+
permissions:
132+
# Required for creating the release
133+
contents: write
134+
# Required for trusted publishing
135+
id-token: write
136+
137+
steps:
138+
- name: Checkout code
139+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
140+
with:
141+
fetch-depth: 0
142+
143+
- name: Install git cliff and typos
144+
uses: taiki-e/install-action@c07504cae06f832dc8de08911c9a9c5cddb0d2d3 # v2.56.13
145+
with:
146+
tool: git-cliff,typos
147+
148+
- name: Update changelog
149+
run: git cliff --verbose
150+
working-directory: pact-python-ffi
151+
env:
152+
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
153+
154+
- name: Generate release changelog
155+
id: release-changelog
156+
working-directory: pact-python-ffi
157+
run: |
158+
git cliff \
159+
--current \
160+
--strip header \
161+
--output ${{ runner.temp }}/release-changelog.md
162+
163+
echo -e "\n\n## Pull Requests\n\n" >> ${{ runner.temp }}/release-changelog.md
164+
env:
165+
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
166+
167+
- name: Download wheels and sdist
168+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
169+
with:
170+
path: wheelhouse
171+
merge-multiple: true
172+
173+
- name: Generate release
174+
id: release
175+
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
176+
with:
177+
files: wheelhouse/*
178+
body_path: ${{ runner.temp }}/release-changelog.md
179+
draft: false
180+
prerelease: false
181+
generate_release_notes: true
182+
183+
- name: Push build artifacts to PyPI
184+
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
185+
with:
186+
skip-existing: true
187+
packages-dir: wheelhouse
188+
189+
- name: Create PR for changelog update
190+
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
191+
with:
192+
token: ${{ secrets.GH_TOKEN }}
193+
commit-message: 'docs: update changelog for ${{ github.ref_name }}'
194+
title: 'docs: update ffi changelog'
195+
body: |
196+
This PR updates the changelog for ${{ github.ref_name }}.
197+
branch: docs/update-changelog
198+
base: main

.github/workflows/test.yml

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ jobs:
121121
working-directory: pact-python-cli
122122
run: hatch run test
123123

124+
- name: Run tests (FFI)
125+
working-directory: pact-python-ffi
126+
run: hatch run test
127+
124128
- name: Upload coverage
125129
if: matrix.python-version == env.STABLE_PYTHON_VERSION && matrix.os == 'ubuntu-latest'
126130
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
@@ -177,6 +181,15 @@ jobs:
177181
- name: Install Python
178182
run: uv python install ${{ matrix.python-version }}
179183

184+
- name: Set PATH on Windows
185+
if: startsWith(matrix.os, 'windows-')
186+
shell: pwsh
187+
run: echo "$pwd/pact-python-ffi/src/pact_ffi" >> $env:GITHUB_PATH
188+
189+
- name: Set DYLD_LIBRARY_PATH on macOS
190+
if: startsWith(matrix.os, 'macos-')
191+
run: echo "DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:$PWD/pact-python-ffi/src/pact_ffi" >> $GITHUB_ENV
192+
180193
- name: Install Hatch
181194
run: uv tool install hatch
182195

@@ -187,6 +200,10 @@ jobs:
187200
working-directory: pact-python-cli
188201
run: hatch run test
189202

203+
- name: Run tests (FFI)
204+
working-directory: pact-python-ffi
205+
run: hatch run test
206+
190207
example:
191208
name: Example
192209

@@ -279,6 +296,9 @@ jobs:
279296
working-directory: pact-python-cli
280297
run: hatch run format
281298

299+
- name: Format (FFI)
300+
working-directory: pact-python-ffi
301+
run: hatch run format
282302
lint:
283303
name: Lint
284304

@@ -301,13 +321,17 @@ jobs:
301321
- name: Install Hatch
302322
run: uv tool install hatch
303323

304-
- name: Format
324+
- name: Lint
305325
run: hatch run lint
306326

307-
- name: Format (CLI)
327+
- name: Lint (CLI)
308328
working-directory: pact-python-cli
309329
run: hatch run lint
310330

331+
- name: Lint (FFI)
332+
working-directory: pact-python-ffi
333+
run: hatch run lint
334+
311335
typecheck:
312336
name: Typecheck
313337

pact-python-cli/pyproject.toml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,7 @@ requires-python = ">=3.9"
6464
################################################################################
6565
[build-system]
6666
build-backend = "hatchling.build"
67-
requires = [
68-
"hatch-vcs",
69-
"hatchling",
70-
"packaging",
71-
# "setuptools ; python_version >= '3.12'",
72-
]
67+
requires = ["hatch-vcs", "hatchling", "packaging"]
7368

7469
[tool.hatch]
7570

pact-python-ffi/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
src/pact_ffi/data
2+
src/pact_ffi/__version__.py

pact-python-ffi/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Pact Foundation
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

pact-python-ffi/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Pact Python FFI
2+
3+
> [!NOTE]
4+
>
5+
> This package provides direct access to the Pact Foreign Function Interface (FFI) with minimal abstraction. It is intended for advanced users who need low-level control over Pact operations in Python.
6+
7+
---
8+
9+
This sub-package is part of the [Pact Python](https://github.com/pact-foundation/pact-python) project and exists to expose the [Pact FFI](https://github.com/pact-foundation/pact-reference) directly to Python. 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).
10+
11+
## Overview
12+
13+
- The module provides a thin Python wrapper around the Pact FFI (C API).
14+
- Most classes correspond directly to structs from the FFI, and are designed to wrap the underlying C pointers.
15+
- Many classes implement the `__del__` method to ensure memory allocated by the Rust library is freed when the Python object is destroyed, preventing memory leaks.
16+
- Functions from the FFI are exposed directly: if a function `foo` exists in the FFI, it is accessible as `pact_ffi.foo(...)`.
17+
- The API is not guaranteed to be stable and is intended for use by advanced users or for building higher-level libraries. For typical contract testing, use the main Pact Python client library.
18+
19+
## Installation
20+
21+
You can install this package via pip:
22+
23+
```console
24+
pip install pact-python-ffi
25+
```
26+
27+
## Usage
28+
29+
This package exposes the raw FFI bindings for Pact. It is suitable for advanced use cases, custom integrations, or for building higher-level libraries. For typical contract testing, prefer using the main Pact Python library.
30+
31+
## Contributing
32+
33+
As this is a relatively thin wrapper around the Pact FFI, the code is unlikely to change frequently; however, contributions to improve the coverage of the FFI bindings or to improve existing functionality are welcome. See the [main contributing guide](https://github.com/pact-foundation/pact-python/blob/main/CONTRIBUTING.md) for details.
34+
35+
To release a new version of `pact-python-ffi`, simply push a tag in the format `pact-python-ffi/x.y.z.w`. This will automatically trigger a release process, pulling in version `x.y.z` of the underlying Pact FFI. Before creating and pushing such a tag, please ensure that the Python wrapper has been updated to reflect any changes or updates in the corresponding FFI version.
36+
37+
Higher-level abstractions or utilities should be implemented in separate libraries (such as [`pact-python`](https://github.com/pact-foundation/pact-python)).
38+
39+
---
40+
41+
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)
42+
43+
---

0 commit comments

Comments
 (0)