Metadata-Version: 2.4
Name: accord-contracts
Version: 0.1.0
Summary: Add your description here
Requires-Python: >=3.13
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.28.1
Requires-Dist: pydantic>=2.12.5
Requires-Dist: werkzeug>=3.1.5

# 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`](https://git.critchlow.net/brodycritchlow/accord/src/commit/3d7d18a8322b223e2744d8586f778887b90d7a26/core/contract.py#L29-L55) decorator, which then automatically takes care of the rest for you:

- Spins up a mock http-server using [werkzeug](https://github.com/pallets/werkzeug)
- Generates a [pact](https://github.com/pact-foundation) compatible JSON contract file

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

```mermaid
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

```mermaid
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

```mermaid
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:

```py
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:

```py
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:

```py
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()
```

