Open-source project
pact-foundation/pact-python avatar
pact-foundation/pact-python

pact-python: contract testing where the consumer writes the pact

Python version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.

686 stars147 forksPythonMIT

At a glance

What is it?
pact-python is the Python implementation of Pact, giving a consumer project a mock service to code against and a service provider the interactions to replay and verify. It suits teams whose end-to-end integration suites are slow and brittle, and it is a poor fit if your services never change their interfaces or you have no way to run the provider side in CI.
Who is it for?
pact-python fits a team replacing a slow end-to-end integration suite with fast provider-side verification, since the consumer writes the pact, the mock service answers from it locally, and the same file is replayed against the real provider in CI. It is the wrong choice for a service that will not change its interface, for a team that cannot run the provider in CI, and for anyone who needs the broker and PactFlow workflow rather than plain file exchange.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The consumer writes the contract, and both sides check it

pact-python is the Python version of Pact, described as enabling consumer driven contract testing, providing a mock service and a DSL for the consumer project, and interaction playback and verification for the service provider project.

That split is the whole idea, and it is worth being precise about because it is often described loosely. The consumer is the code that calls somebody else's API. In Pact, the consumer writes a pact, which is a plain document describing the requests it expects to make and the responses it expects to get back. That pact becomes the contract.

From that one file come the two halves. The consumer side gets a mock service that answers according to the pact, so the consumer's own unit tests need no network and no other team's service running. The provider side replays the interactions recorded in the pact against the real provider, and the provider's test suite fails if the provider's behaviour no longer matches what the consumer said it needs.

The readme frames Pact as the de-facto API contract testing tool, and the claim is to replace expensive and brittle end-to-end integration tests with fast, reliable and easy to debug unit tests. The listed reasons for using contract testing are worth reading as a checklist of what such a setup gives you: testing locally, deploying faster, reducing the lead time for change, reducing the cost of API integration testing, preventing breaking changes, understanding your system usage, documenting your APIs for free, removing the need for complex data fixtures, and reducing reliance on complex test environments.

Two features in that list are the real payoff for a Python team. Preventing breaking changes is the direct one, since a provider cannot merge a change that breaks a consumer pact without the consumer's test failing. And removing the need for complex data fixtures is the one that changes how the suite is written.

Matching rules are what keep a pact from becoming a brittle snapshot

A contract test that asserts an exact response body is a contract test that breaks when a provider adds a field, and the usual outcome is that teams learn to ignore failures. The readme lists powerful matching rules that prevent brittle tests, and a configurable mock server, and those two are what separate a usable pact from a snapshot of one response.

The mechanism is a matter of how the expectation is expressed. Rather than requiring an exact match, a pact can say that a particular field must exist and be of a given type, that a value must be present and equal to a specific thing, or that a value is of a type and is present. The provider then satisfies the contract without being pinned to a byte-for-byte response, which means adding a field to a response is not a breaking change and the consumer's test stays green.

The mock server is the other half. The consumer's test does not stub the HTTP client by hand, it starts a real mock service that returns responses satisfying the pact, so the consumer exercises its actual HTTP code path rather than a mocked-out one. That is what makes the consumer-side tests genuinely fast without giving up the request the service would really have made.

The scope is not only HTTP. The readme says Pact supports HTTP/REST and event-driven systems, and the documentation is split into a consumer testing page and a provider testing page, with an examples directory that the readme links to separately. The examples tree in the repository backs that up, with separate http/, catalog/, plugins/ and container-compose.yml subdirectories, which is the shape of a project that has to demonstrate both the request and response side of a contract and the non-HTTP case.

The Rust core arrives as a separate FFI package

The dependency list in pyproject.toml is short, and that is because the work happens elsewhere. The only real dependency is pact-python-ffi at version 0.5 or so, plus typing-extensions for Python versions below 3.13. The description of the package is a tool for creating and verifying consumer-driven contracts using the Pact framework, and the FFI package is the engine underneath it.

The repository layout makes the split visible. There is a pact-python-ffi/ directory at the top level, sitting next to src/, tests/, examples/ and docs/. The pact-python-ffi package has its own release line, with 0.5.10.0 tagged on 2026-10-15, the same day as pact-python 4.0.0. So the Python package and the native core are versioned separately, and a change in the core is not necessarily a change in the Python API.

The Python side requires 3.11 or newer, and the classifiers list 3.11 through 3.15, so this is a package that has dropped the older interpreters. The licence is MIT, taken from the LICENSE file, and the development status is marked as Production/Stable.

There is a MIGRATION.md in the repository, and it matters here for a specific reason. Version 4.0.0 was released on 2026-10-15 and version 4.1.0 the following day, 2026-10-16. A major version landing on a Tuesday with a patch on the Wednesday tells you the project is moving quickly, and it tells you that a team upgrading from the 3.x line needs to read that migration document before anything else.

Hatch, Ruff, Mypy, and a coverage gate at 100 percent

The tooling story in this repository is unusually tidy, and the Makefile is a good place to see it because every target is a thin wrapper around hatch.

The test target runs the tests and then enforces coverage:

bash
test:
	hatch run test
	hatch run coverage report -m --fail-under=100

A hundred percent coverage floor is a strict gate, and for a library whose job is to encode and check contracts, it is a defensible one. The release target chains clean, test and package, so a release cannot skip the coverage check, and the ci target is simply test and lint.

The lint target runs three separate things: the linter, the formatter, and the type checker. The readme badges name them, ruff for both linting and style, and Mypy for types, and the pyproject.toml also lists Hatch as the build backend. There is a per-file configuration file, ruff.toml, alongside the project file, which is the usual arrangement when the lint rules and the packaging metadata need to live apart.

The rest of the repository is the housekeeping that a project with this many contributors accumulates: an .editorconfig, a .pre-commit-config.yaml, a .markdownlint-cli2.yaml for the documentation, a .devcontainer/ directory so a contributor can work in a container, a conftest.py at the root for pytest, a committed.toml and cliff.toml for the changelog tooling, a .tombi.toml for the editor schema, a mise.toml for tool versions, and an AGENTS.md alongside a .claude/ directory, which is a newer convention for repository instructions.

The examples directory also carries a container-compose.yml, and the Makefile has an example target whose help text notes that it requires Docker to run the example end-to-end tests.

Where pact-python is the wrong choice

The first limitation is that contract testing only pays off where contracts change. The readme's own list of benefits starts with preventing breaking changes, which means the technique is aimed at services whose interfaces are in flux and whose consumers are numerous enough that coordinating a release is painful. A system of three internal services that change together, with one team owning both ends, gains very little from pacts and pays the cost of a second testing model.

The second is that the provider side has to run somewhere. Provider verification replays interactions against the real service, which means the provider needs a test environment and a CI job that can start it and run the verification. If you cannot run the provider in CI, you can still use the mock service on the consumer side, but then you have half a contract testing setup: the consumer tests a mock, and nothing checks that the provider honours the contract. That is a common way to end up with contracts that drift without anyone noticing.

The third is the multi-language coordination cost. The readme says Pact supports 12 or more languages, and the ecosystem links to a Pact Broker and PactFlow for CI/CD workflows, and to a Slack workspace, a Stack Overflow tag and a set of GitHub discussions. Those are the community channels for a cross-language contract ecosystem. If only one team writes Python, that network is not much of a benefit, and if several teams write different languages, the coordination overhead is real: contracts have to be agreed, versioned and published somewhere.

The fourth is the churn. Two releases in consecutive days with a major version in between means the 4.x line is still settling, and a team that pins a version is choosing an API that is moving. The typing-extensions dependency for Python below 3.13 is a small example of the same pattern.

The fifth is the scope of what a pact covers. A pact describes the requests and responses one consumer expects. It does not cover the behaviour of a service under load, its interaction with a database, or the correctness of a business rule. Contract testing narrows the gap between two services; it does not replace testing the service itself, and treating a green pact suite as evidence that a service works is the mistake this approach is most often misused for.

Editorial conclusion

pact-python fits a team replacing a slow end-to-end integration suite with fast provider-side verification, since the consumer writes the pact, the mock service answers from it locally, and the same file is replayed against the real provider in CI. It is the wrong choice for a service that will not change its interface, for a team that cannot run the provider in CI, and for anyone who needs the broker and PactFlow workflow rather than plain file exchange. Before adopting it, read MIGRATION.md, since version 4.0.0 landed on 2026-10-15 and version 4.1.0 followed the next day, and note that the last push to the repository was on 2026-10-20.

Frequently asked questions

What does pact-python do?

It is the Python implementation of Pact for consumer driven contract testing. The consumer project gets a mock service and a DSL to describe what it expects, and the service provider project gets interaction playback and verification of those expectations.

How do I install pact-python?

The package is published on PyPI as pact-python, and the readme links the installation section of the full documentation. The Makefile builds a wheel and sdist with the package target, and the project is developed with Hatch.

What Python versions does pact-python support?

It requires Python 3.11 or newer, and the package classifiers list 3.11 through 3.15. A typing-extensions dependency is pulled in for Python versions below 3.13.

Why is there a separate pact-python-ffi package?

The native engine lives in pact-python-ffi, which the Python package depends on at version 0.5 or so, and the two have independent release lines, with 0.5.10.0 tagged on 2026-10-15. The FFI sources are in the pact-python-ffi/ directory of the repository.

What licence is pact-python released under?

MIT, with the licence text in the LICENSE file at the root of the repository. The project marks itself as Production/Stable, and version 4.1.0 was published on 2026-10-16 after 4.0.0 on 2026-10-15.

Official sources

  1. License: MIT
  2. pact-foundation/pact-python on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/pact-foundation-pact-python.svg)](https://hysenlabs.com/projects/pact-foundation-pact-python)