Add your description here
Project description
Accord
Accord is a Python library for consumer-driven contract testing between web services, you define a Contract once using Pydantic models and an @endpoint decorator, which then automatically takes care of the rest for you:
This ensures both sides of a service boundary stay in sync without requiring a shared test environment.
Why not Pact?
Pact is definitely the industry-standard library, and if you are working with multiple different languages or environments then it is the better choice. But if your stack is Python-only, Pact comes with a lot of overhead; that you may not need:
- A separate DSL
- Pact has its own way of defining contracts that lives outside your existing code. Accord uses the Pydantic models you're already writing.
- The broker
- Pact strongly encourages (and in practice, requires) a Pact broker to share contracts between teams. Accord generates a JSON file and you share it however you want: a shared repo, a CI artifact, or S3.
- Usage time
- Getting Pact fully set up across two services takes time. Accord is designed to be useful in an afternoon.
Accord is not supposed to replace Pact, it is for simplicity and less overhead. In reality, Pact is more useful across the board.
Learn how to use Accord
Before you learn how to use Accord, here are some simple diagrams that showcase how our Contracts & Systems work.
Contracts
sequenceDiagram
participant Dev as Developer
participant Endpoint as @endpoint
participant Contract as Contract Class
Dev->>Endpoint: Decorates method with http_method and path
Endpoint->>Endpoint: Extracts return type from type hints
Endpoint->>Endpoint: Attaches EndpointMetadata to function
Dev->>Contract: Defines Contract subclass
Contract->>Contract: __init_subclass__ runs
Contract->>Contract: Scans for functions with accord_endpoint
Contract->>Contract: Registers each into interactions dict
Consumer Flow
sequenceDiagram
participant Consumer
participant MockServer as Accord MockServer
participant File as accords/*.json
Consumer->>MockServer: MockServer(Contract)
MockServer->>MockServer: Register routes from interactions
MockServer->>MockServer: Start Werkzeug server in background thread
Consumer->>MockServer: given("get_user").example(UserResponse(...))
MockServer->>MockServer: Store example in examples dict
Consumer->>MockServer: HTTP GET /users/1
MockServer->>MockServer: Match route, look up example
MockServer->>Consumer: Return example as JSON response
Consumer->>Consumer: Assert response
Consumer->>MockServer: Exit context manager
MockServer->>File: write_contract() — Pact-compatible JSON
MockServer->>MockServer: Shutdown server
Producer Verification
sequenceDiagram
participant File as accords/*.json
participant Verifier as Accord ContractVerifier
participant Provider
Verifier->>File: read_contract()
File->>Verifier: ParsedContract with interactions
loop For each interaction
Verifier->>Provider: HTTP request using interaction method and path
Provider->>Verifier: Actual response
Verifier->>Verifier: Parse response body
loop For each field in matchingRules
Verifier->>Verifier: Map JSON type to Python type
Verifier->>Verifier: isinstance check
alt Type mismatch
Verifier->>Verifier: Raise ValueError
end
end
end
Verifier->>Verifier: All interactions verified
Now with all of those flows out of the way, the example may make more sense that we provide here.
A basic contract is defined as such:
from pydantic import BaseModel
from core.contract import Contract, endpoint
class UserResponse(BaseModel):
id: int
name: str
class GetUserContract(Contract):
consumer = "order-service"
producer = "user-service"
@endpoint(http_method="GET", path="/users/{id}")
def get_user(self, user_id: int) -> UserResponse: ...
Once we have this contract defined, we use it to spin up a MockServer to test the Consumer:
import httpx
from server.mock import MockServer
with MockServer(GetUserContract) as server:
server.given("get_user").example(UserResponse(id=1, name="Alice"))
response = httpx.get("http://127.0.0.1:5000/users/1")
assert response.status_code == 200
assert response.json()["name"] == "Alice"
This writes the contract to ./accords/ once the ContextManager exits. The other side of the testing flow is producer, which you can do as such:
from pathlib import Path
from verification.verifier import ContractVerifier
# This assumes your server is running on 8080
verifier = ContractVerifier(
contract_path=Path("accords/order-service-user-service.json"),
base_url="http://127.0.0.1:8080",
)
verifier.verify()
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file accord_contracts-0.1.0.tar.gz.
File metadata
- Download URL: accord_contracts-0.1.0.tar.gz
- Upload date:
- Size: 6.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5db9dd0676f72e5e1dec94ddcbe16072e85fc264c16d0fb088af5433966edb4c
|
|
| MD5 |
79343bbcdbcdb06ef0de08853584c97d
|
|
| BLAKE2b-256 |
d9857150f25832e5d64ab41ef3f516e3c371a83637cba3abb0e82fe4965b0c39
|
File details
Details for the file accord_contracts-0.1.0-py3-none-any.whl.
File metadata
- Download URL: accord_contracts-0.1.0-py3-none-any.whl
- Upload date:
- Size: 8.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eb2c17d05694e3f4a6906f97b3611bfb851996e5073f61ac483b4b2a3548f1c9
|
|
| MD5 |
2a4b2b0aa355cdade6ec3409d991066d
|
|
| BLAKE2b-256 |
7683b3988db8f84ebe45e3e189ebfbc1f669e084c72494a9097de3a0480ebce5
|