pactship 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pactship-0.1.0/LICENSE +21 -0
- pactship-0.1.0/PKG-INFO +540 -0
- pactship-0.1.0/README.md +510 -0
- pactship-0.1.0/pactship/__init__.py +104 -0
- pactship-0.1.0/pactship/broker.py +181 -0
- pactship-0.1.0/pactship/cli.py +236 -0
- pactship-0.1.0/pactship/config.py +143 -0
- pactship-0.1.0/pactship/contract_io.py +104 -0
- pactship-0.1.0/pactship/diff.py +311 -0
- pactship-0.1.0/pactship/dsl.py +170 -0
- pactship-0.1.0/pactship/filters.py +132 -0
- pactship-0.1.0/pactship/generator.py +218 -0
- pactship-0.1.0/pactship/graph.py +196 -0
- pactship-0.1.0/pactship/hooks.py +124 -0
- pactship-0.1.0/pactship/linter.py +209 -0
- pactship-0.1.0/pactship/matchers.py +455 -0
- pactship-0.1.0/pactship/matrix.py +165 -0
- pactship-0.1.0/pactship/models.py +357 -0
- pactship-0.1.0/pactship/openapi.py +187 -0
- pactship-0.1.0/pactship/reporting.py +143 -0
- pactship-0.1.0/pactship/schema.py +248 -0
- pactship-0.1.0/pactship/stats.py +152 -0
- pactship-0.1.0/pactship/transform.py +130 -0
- pactship-0.1.0/pactship/validator.py +214 -0
- pactship-0.1.0/pactship/verifier.py +245 -0
- pactship-0.1.0/pactship.egg-info/PKG-INFO +540 -0
- pactship-0.1.0/pactship.egg-info/SOURCES.txt +56 -0
- pactship-0.1.0/pactship.egg-info/dependency_links.txt +1 -0
- pactship-0.1.0/pactship.egg-info/entry_points.txt +2 -0
- pactship-0.1.0/pactship.egg-info/requires.txt +11 -0
- pactship-0.1.0/pactship.egg-info/top_level.txt +1 -0
- pactship-0.1.0/pyproject.toml +49 -0
- pactship-0.1.0/setup.cfg +4 -0
- pactship-0.1.0/tests/test_broker.py +184 -0
- pactship-0.1.0/tests/test_cli.py +154 -0
- pactship-0.1.0/tests/test_config.py +141 -0
- pactship-0.1.0/tests/test_contract_io.py +211 -0
- pactship-0.1.0/tests/test_diff.py +245 -0
- pactship-0.1.0/tests/test_dsl.py +200 -0
- pactship-0.1.0/tests/test_edge_cases.py +256 -0
- pactship-0.1.0/tests/test_filters.py +158 -0
- pactship-0.1.0/tests/test_generator.py +210 -0
- pactship-0.1.0/tests/test_graph.py +120 -0
- pactship-0.1.0/tests/test_hooks.py +140 -0
- pactship-0.1.0/tests/test_integration.py +214 -0
- pactship-0.1.0/tests/test_linter.py +185 -0
- pactship-0.1.0/tests/test_matchers.py +361 -0
- pactship-0.1.0/tests/test_matrix.py +195 -0
- pactship-0.1.0/tests/test_models.py +377 -0
- pactship-0.1.0/tests/test_openapi.py +260 -0
- pactship-0.1.0/tests/test_public_api.py +78 -0
- pactship-0.1.0/tests/test_reporting.py +182 -0
- pactship-0.1.0/tests/test_schema.py +200 -0
- pactship-0.1.0/tests/test_stats.py +145 -0
- pactship-0.1.0/tests/test_transform.py +148 -0
- pactship-0.1.0/tests/test_validator.py +300 -0
- pactship-0.1.0/tests/test_verifier.py +117 -0
- pactship-0.1.0/tests/test_verifier_async.py +293 -0
pactship-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JSLEEKR
|
|
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.
|
pactship-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pactship
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight consumer-driven contract testing for microservices APIs
|
|
5
|
+
Author-email: JSLEEKR <93jslee@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: contract-testing,api,microservices,pact,testing
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Testing
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: click>=8.0
|
|
20
|
+
Requires-Dist: pyyaml>=6.0
|
|
21
|
+
Requires-Dist: jsonschema>=4.0
|
|
22
|
+
Requires-Dist: rich>=13.0
|
|
23
|
+
Requires-Dist: httpx>=0.24
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
28
|
+
Requires-Dist: respx>=0.20; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
<div align="center">
|
|
32
|
+
|
|
33
|
+
# :handshake: pactship
|
|
34
|
+
|
|
35
|
+
### Broker-less contract testing for microservices
|
|
36
|
+
|
|
37
|
+
[](https://github.com/JSLEEKR/pactship/stargazers)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
[](https://python.org)
|
|
40
|
+
[](#)
|
|
41
|
+
|
|
42
|
+
<br/>
|
|
43
|
+
|
|
44
|
+
**Define contracts. Verify providers. Catch breaking changes before they ship.**
|
|
45
|
+
|
|
46
|
+
[Quick Start](#quick-start) | [CLI](#cli-commands) | [Matchers](#matchers) | [API](#programmatic-api) | [Architecture](#architecture)
|
|
47
|
+
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Why This Exists
|
|
53
|
+
|
|
54
|
+
Microservices break in production when providers change their APIs without telling consumers. The consumer expects `GET /users/1` to return `{ id, name, email }` -- the provider ships a rename from `name` to `full_name` and three downstream services crash at 2 AM.
|
|
55
|
+
|
|
56
|
+
Existing contract testing tools solve this -- but they bring heavyweight infrastructure with them. Pact JVM needs a broker server. Spring Cloud Contract requires a JVM toolchain. Both demand CI/CD plumbing that takes longer to set up than the contracts themselves.
|
|
57
|
+
|
|
58
|
+
**pactship** is a zero-infrastructure, file-based contract testing tool for Python. Write contracts in YAML or JSON, verify them against live providers with async HTTP, diff versions to detect breaking changes, and store everything locally -- no broker server needed, no background processes, no Docker containers.
|
|
59
|
+
|
|
60
|
+
- **No infrastructure** -- contracts live as files in your repo, verified locally or in CI
|
|
61
|
+
- **Fluent Python DSL** -- build contracts programmatically with type-checked builders
|
|
62
|
+
- **16 matcher types** -- from exact match to regex, UUID, email, ISO dates, nullable, and range
|
|
63
|
+
- **Breaking change detection** -- diff two contract versions with breaking/non-breaking classification
|
|
64
|
+
|
|
65
|
+
## Requirements
|
|
66
|
+
|
|
67
|
+
- Python 3.10+
|
|
68
|
+
- Dependencies: `click`, `pyyaml`, `jsonschema`, `rich`, `httpx`
|
|
69
|
+
|
|
70
|
+
## Quick Start
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install pactship
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Define a Contract (YAML)
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
consumer: order-service
|
|
80
|
+
provider: user-api
|
|
81
|
+
interactions:
|
|
82
|
+
- description: Get user by ID
|
|
83
|
+
request:
|
|
84
|
+
method: GET
|
|
85
|
+
path: /users/1
|
|
86
|
+
response:
|
|
87
|
+
status: 200
|
|
88
|
+
body:
|
|
89
|
+
id: 1
|
|
90
|
+
name: Alice
|
|
91
|
+
email: alice@example.com
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Define a Contract (Python DSL)
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from pactship import ContractBuilder, InteractionBuilder
|
|
98
|
+
from pactship import like, email_match, integer_match
|
|
99
|
+
|
|
100
|
+
contract = (
|
|
101
|
+
ContractBuilder("order-service", "user-api")
|
|
102
|
+
.add_interaction(
|
|
103
|
+
InteractionBuilder("Get user by ID")
|
|
104
|
+
.given("user 1 exists")
|
|
105
|
+
.with_request("GET", "/users/1")
|
|
106
|
+
.will_respond_with(200)
|
|
107
|
+
.with_response_body(
|
|
108
|
+
{"id": 1, "name": "Alice", "email": "alice@example.com"},
|
|
109
|
+
matchers={
|
|
110
|
+
"body.id": integer_match(),
|
|
111
|
+
"body.name": like("string"),
|
|
112
|
+
"body.email": email_match(),
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
.build()
|
|
116
|
+
)
|
|
117
|
+
.build()
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Verify Against a Provider
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
pactship verify contract.yaml http://localhost:8080
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Detect Breaking Changes
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
pactship diff old-contract.yaml new-contract.yaml
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Publish to Local Broker
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
pactship publish contract.yaml --broker-dir .pactship
|
|
137
|
+
pactship list --broker-dir .pactship
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## How It Works
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
Define Verify Diff Report
|
|
144
|
+
────────── ────────── ────────── ──────────
|
|
145
|
+
YAML/JSON → Provider → Version A → Breaking
|
|
146
|
+
or DSL Verification vs B changes
|
|
147
|
+
(async HTTP) classified
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
1. **Define** -- write contracts as YAML/JSON files or build them with the fluent Python DSL
|
|
151
|
+
2. **Verify** -- run contracts against a live provider using async HTTP via `httpx`
|
|
152
|
+
3. **Diff** -- compare two contract versions to detect breaking vs non-breaking changes
|
|
153
|
+
4. **Report** -- get results in JSON, JUnit XML, Markdown, or TAP format
|
|
154
|
+
|
|
155
|
+
## Features
|
|
156
|
+
|
|
157
|
+
### Contract Definition
|
|
158
|
+
- **Fluent DSL** -- `ContractBuilder` and `InteractionBuilder` with method chaining
|
|
159
|
+
- **YAML and JSON** -- read and write contracts in both formats via `load_contract` / `save_contract`
|
|
160
|
+
- **Provider states** -- define preconditions with `.given("user 1 exists")`
|
|
161
|
+
- **Request/response specs** -- method, path, headers, query params, body, status code
|
|
162
|
+
|
|
163
|
+
### Matcher System (16 Types)
|
|
164
|
+
- **Type matching** -- `like("string")` matches any string, `integer_match()` matches any int
|
|
165
|
+
- **Regex patterns** -- `regex(r"\d{3}-\d{4}")` for custom format validation
|
|
166
|
+
- **Structural matchers** -- `array_like(min_len)`, `each_like(example)` for arrays
|
|
167
|
+
- **Domain matchers** -- `email_match()`, `uuid_match()`, `iso_date()`, `iso_datetime()`
|
|
168
|
+
- **Constraint matchers** -- `range_match(0, 100)`, `any_of(["a", "b"])`, `nullable("string")`
|
|
169
|
+
|
|
170
|
+
### Verification
|
|
171
|
+
- **Async HTTP verification** -- verify contracts against running providers using `httpx`
|
|
172
|
+
- **Mock provider** -- in-process mock for consumer-side testing without a real server
|
|
173
|
+
- **Request matching** -- method, path, headers, query params validated against spec
|
|
174
|
+
- **Matcher evaluation** -- response body verified against all declared matchers
|
|
175
|
+
- **Provider state setup** -- optional setup URL for test data preparation
|
|
176
|
+
- **Timeout configuration** -- per-verification timeout control
|
|
177
|
+
|
|
178
|
+
### Breaking Change Detection
|
|
179
|
+
- **Contract diffing** -- `diff_contracts(old, new)` returns a structured diff report
|
|
180
|
+
- **Change classification** -- each change tagged as `breaking` or `non-breaking`
|
|
181
|
+
- **Interaction-level diff** -- detects added, removed, and modified interactions
|
|
182
|
+
- **Field-level diff** -- tracks changes to individual request/response fields
|
|
183
|
+
|
|
184
|
+
### Local Broker
|
|
185
|
+
- **Filesystem-based storage** -- contracts stored as files in a configurable directory
|
|
186
|
+
- **Versioning** -- publish contracts with version numbers, retrieve specific versions
|
|
187
|
+
- **Verification history** -- track which provider versions were verified against which contracts
|
|
188
|
+
- **No server needed** -- everything runs locally, works offline, no network dependency
|
|
189
|
+
|
|
190
|
+
### Contract Linting
|
|
191
|
+
- **REST best practices** -- validate path naming, HTTP method usage, status codes
|
|
192
|
+
- **Custom lint rules** -- extensible linting with severity-based issue reporting
|
|
193
|
+
- **Pre-publish validation** -- catch contract quality issues before sharing
|
|
194
|
+
|
|
195
|
+
### OpenAPI Import
|
|
196
|
+
- **OpenAPI 3.x conversion** -- convert OpenAPI specs to pactship contracts automatically
|
|
197
|
+
- **Path and method extraction** -- generates interactions from OpenAPI path definitions
|
|
198
|
+
- **Response schema mapping** -- maps OpenAPI response schemas to pactship response specs
|
|
199
|
+
|
|
200
|
+
### Code Generation
|
|
201
|
+
- **CRUD generator** -- `generate_crud_contract()` creates full CRUD contracts from resource specs
|
|
202
|
+
- **Endpoint generator** -- `generate_from_endpoints()` builds contracts from endpoint definitions
|
|
203
|
+
- **Customizable templates** -- configure generated interactions per HTTP method
|
|
204
|
+
|
|
205
|
+
### Service Graph
|
|
206
|
+
- **Dependency visualization** -- build service dependency graphs from contract sets
|
|
207
|
+
- **Mermaid diagram output** -- generate Mermaid diagrams for documentation
|
|
208
|
+
- **Cycle detection** -- identify circular dependencies between services
|
|
209
|
+
|
|
210
|
+
### Compatibility Matrix
|
|
211
|
+
- **Version tracking** -- `CompatibilityMatrix` tracks which consumer/provider versions work together
|
|
212
|
+
- **Matrix queries** -- check compatibility between specific version pairs
|
|
213
|
+
- **History management** -- add, query, and export compatibility records
|
|
214
|
+
|
|
215
|
+
### Reporting
|
|
216
|
+
- **JSON reports** -- structured verification results as JSON
|
|
217
|
+
- **JUnit XML** -- integrate with CI systems expecting JUnit format
|
|
218
|
+
- **Markdown reports** -- human-readable reports for PR comments
|
|
219
|
+
- **TAP output** -- Test Anything Protocol for pipeline integration
|
|
220
|
+
|
|
221
|
+
### Configuration
|
|
222
|
+
- **File-based config** -- `.pactship.yaml` or `.pactship.json` project configuration
|
|
223
|
+
- **Environment variables** -- `PACTSHIP_BROKER_DIR`, `PACTSHIP_TIMEOUT`, etc.
|
|
224
|
+
- **Priority ordering** -- env vars override file config, file config overrides defaults
|
|
225
|
+
|
|
226
|
+
### Statistics
|
|
227
|
+
- **Method distribution** -- analyze HTTP method usage across contracts
|
|
228
|
+
- **Path coverage** -- track which API paths are covered by contracts
|
|
229
|
+
- **Complexity metrics** -- measure contract complexity and matcher density
|
|
230
|
+
|
|
231
|
+
### Lifecycle Hooks
|
|
232
|
+
- **Before/after verification** -- run custom logic around verification cycles
|
|
233
|
+
- **Setup/teardown** -- provider state preparation and cleanup
|
|
234
|
+
- **Hook registration** -- register hooks via the `HookRegistry`
|
|
235
|
+
|
|
236
|
+
### Contract Transformation
|
|
237
|
+
- **Path rewriting** -- transform contract paths for different environments
|
|
238
|
+
- **Header injection** -- add/modify headers across all interactions
|
|
239
|
+
- **Body transformation** -- apply transforms to request/response bodies
|
|
240
|
+
|
|
241
|
+
### Filtering
|
|
242
|
+
- **Interaction filters** -- filter by HTTP method, path pattern, or description
|
|
243
|
+
- **Tag-based filtering** -- filter contracts by metadata tags
|
|
244
|
+
- **Composable filters** -- combine multiple filters with AND/OR logic
|
|
245
|
+
|
|
246
|
+
## CLI Commands
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
# Validate a contract file
|
|
250
|
+
pactship validate contract.yaml
|
|
251
|
+
|
|
252
|
+
# Verify against a live provider
|
|
253
|
+
pactship verify contract.yaml http://localhost:8080 \
|
|
254
|
+
--timeout 30 \
|
|
255
|
+
--header "Authorization:Bearer token" \
|
|
256
|
+
--setup-url http://localhost:8080/_setup \
|
|
257
|
+
--output report.json
|
|
258
|
+
|
|
259
|
+
# Diff two contract versions
|
|
260
|
+
pactship diff v1/contract.yaml v2/contract.yaml
|
|
261
|
+
|
|
262
|
+
# Publish to local broker
|
|
263
|
+
pactship publish contract.yaml \
|
|
264
|
+
--broker-dir .pactship \
|
|
265
|
+
--version 1.0.0 \
|
|
266
|
+
--tag production
|
|
267
|
+
|
|
268
|
+
# List contracts in broker
|
|
269
|
+
pactship list --broker-dir .pactship
|
|
270
|
+
|
|
271
|
+
# Convert between formats
|
|
272
|
+
pactship convert contract.yaml contract.json
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
| Command | Description |
|
|
276
|
+
|---------|-------------|
|
|
277
|
+
| `pactship validate <file>` | Validate contract file syntax and structure |
|
|
278
|
+
| `pactship verify <file> <url>` | Verify contract against a running provider |
|
|
279
|
+
| `pactship diff <old> <new>` | Compare two contract versions for breaking changes |
|
|
280
|
+
| `pactship publish <file>` | Publish contract to local filesystem broker |
|
|
281
|
+
| `pactship list` | List all contracts stored in the broker |
|
|
282
|
+
| `pactship convert <in> <out>` | Convert between YAML and JSON formats |
|
|
283
|
+
|
|
284
|
+
## Matchers
|
|
285
|
+
|
|
286
|
+
| Matcher | Description | Example |
|
|
287
|
+
|---------|-------------|---------|
|
|
288
|
+
| `exact(value)` | Exact value match | `exact("hello")` |
|
|
289
|
+
| `like(type)` | Type-based match | `like("string")` |
|
|
290
|
+
| `regex(pattern)` | Regex pattern match | `regex(r"\d{3}-\d{4}")` |
|
|
291
|
+
| `range_match(min, max)` | Numeric range constraint | `range_match(0, 100)` |
|
|
292
|
+
| `array_like(min_len)` | Array with minimum length | `array_like(1)` |
|
|
293
|
+
| `each_like(example)` | Each element matches structure | `each_like({"id": 0})` |
|
|
294
|
+
| `any_of(values)` | One of allowed values | `any_of(["a", "b"])` |
|
|
295
|
+
| `nullable(type)` | Null or specified type | `nullable("string")` |
|
|
296
|
+
| `iso_date()` | ISO 8601 date string | `iso_date()` |
|
|
297
|
+
| `iso_datetime()` | ISO 8601 datetime string | `iso_datetime()` |
|
|
298
|
+
| `uuid_match()` | UUID v4 format | `uuid_match()` |
|
|
299
|
+
| `email_match()` | Email address format | `email_match()` |
|
|
300
|
+
| `integer_match()` | Integer value | `integer_match()` |
|
|
301
|
+
| `decimal_match()` | Decimal number | `decimal_match()` |
|
|
302
|
+
| `boolean_match()` | Boolean value | `boolean_match()` |
|
|
303
|
+
| `string_match()` | String value | `string_match()` |
|
|
304
|
+
|
|
305
|
+
## Programmatic API
|
|
306
|
+
|
|
307
|
+
### Contract Building
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
from pactship import ContractBuilder, InteractionBuilder
|
|
311
|
+
from pactship import like, regex, integer_match, email_match
|
|
312
|
+
|
|
313
|
+
contract = (
|
|
314
|
+
ContractBuilder("order-service", "user-api")
|
|
315
|
+
.with_metadata({"version": "1.0.0"})
|
|
316
|
+
.add_interaction(
|
|
317
|
+
InteractionBuilder("Get user by ID")
|
|
318
|
+
.given("user 1 exists")
|
|
319
|
+
.with_request("GET", "/users/1")
|
|
320
|
+
.with_request_header("Accept", "application/json")
|
|
321
|
+
.will_respond_with(200)
|
|
322
|
+
.with_response_header("Content-Type", "application/json")
|
|
323
|
+
.with_response_body(
|
|
324
|
+
{"id": 1, "name": "Alice", "email": "alice@example.com"},
|
|
325
|
+
matchers={
|
|
326
|
+
"body.id": integer_match(),
|
|
327
|
+
"body.name": like("string"),
|
|
328
|
+
"body.email": email_match(),
|
|
329
|
+
},
|
|
330
|
+
)
|
|
331
|
+
.build()
|
|
332
|
+
)
|
|
333
|
+
.build()
|
|
334
|
+
)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Contract I/O
|
|
338
|
+
|
|
339
|
+
```python
|
|
340
|
+
from pactship import save_contract, load_contract
|
|
341
|
+
|
|
342
|
+
# Save to YAML or JSON (auto-detected from extension)
|
|
343
|
+
save_contract(contract, "contracts/user-api.yaml")
|
|
344
|
+
|
|
345
|
+
# Load from file
|
|
346
|
+
loaded = load_contract("contracts/user-api.yaml")
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Verification
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
from pactship import ProviderVerifier, MockProvider
|
|
353
|
+
|
|
354
|
+
# Verify against a live provider
|
|
355
|
+
verifier = ProviderVerifier(base_url="http://localhost:8080", timeout=30.0)
|
|
356
|
+
report = await verifier.verify(contract)
|
|
357
|
+
print(f"Passed: {report.success}")
|
|
358
|
+
for result in report.results:
|
|
359
|
+
print(f" {result.interaction}: {'PASS' if result.passed else 'FAIL'}")
|
|
360
|
+
|
|
361
|
+
# Use mock provider for consumer testing
|
|
362
|
+
mock = MockProvider(contract)
|
|
363
|
+
response = mock.handle_request("GET", "/users/1")
|
|
364
|
+
assert response.status == 200
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Breaking Change Detection
|
|
368
|
+
|
|
369
|
+
```python
|
|
370
|
+
from pactship import diff_contracts
|
|
371
|
+
|
|
372
|
+
diff = diff_contracts(old_contract, new_contract)
|
|
373
|
+
print(f"Breaking changes: {diff.has_breaking_changes}")
|
|
374
|
+
for change in diff.changes:
|
|
375
|
+
print(f" [{change.change_type}] {change.description}")
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Local Broker
|
|
379
|
+
|
|
380
|
+
```python
|
|
381
|
+
from pactship import ContractBroker
|
|
382
|
+
|
|
383
|
+
broker = ContractBroker(broker_dir=".pactship")
|
|
384
|
+
broker.publish(contract, version="1.0.0", tags=["production"])
|
|
385
|
+
contracts = broker.list_contracts()
|
|
386
|
+
specific = broker.get_contract("order-service", "user-api", version="1.0.0")
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### OpenAPI Import
|
|
390
|
+
|
|
391
|
+
```python
|
|
392
|
+
from pactship.openapi import openapi_to_contracts
|
|
393
|
+
|
|
394
|
+
contracts = openapi_to_contracts("openapi.yaml", consumer="my-service")
|
|
395
|
+
for contract in contracts:
|
|
396
|
+
save_contract(contract, f"contracts/{contract.provider}.yaml")
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Service Graph
|
|
400
|
+
|
|
401
|
+
```python
|
|
402
|
+
from pactship import ServiceGraph
|
|
403
|
+
|
|
404
|
+
graph = ServiceGraph()
|
|
405
|
+
graph.add_contract(contract)
|
|
406
|
+
mermaid = graph.to_mermaid()
|
|
407
|
+
print(mermaid)
|
|
408
|
+
# graph TD
|
|
409
|
+
# order-service --> user-api
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Contract Linting
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
from pactship import lint_contract
|
|
416
|
+
|
|
417
|
+
result = lint_contract(contract)
|
|
418
|
+
print(f"Passed: {result.passed}")
|
|
419
|
+
for issue in result.issues:
|
|
420
|
+
print(f" [{issue.severity}] {issue.rule}: {issue.message}")
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Reporting
|
|
424
|
+
|
|
425
|
+
```python
|
|
426
|
+
from pactship.reporting import (
|
|
427
|
+
report_json,
|
|
428
|
+
report_junit,
|
|
429
|
+
report_markdown,
|
|
430
|
+
report_tap,
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
# Generate reports in multiple formats
|
|
434
|
+
json_report = report_json(verification_report)
|
|
435
|
+
junit_xml = report_junit(verification_report)
|
|
436
|
+
markdown = report_markdown(verification_report)
|
|
437
|
+
tap_output = report_tap(verification_report)
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Statistics
|
|
441
|
+
|
|
442
|
+
```python
|
|
443
|
+
from pactship.stats import contract_stats
|
|
444
|
+
|
|
445
|
+
stats = contract_stats(contract)
|
|
446
|
+
print(f"Methods: {stats['method_distribution']}")
|
|
447
|
+
print(f"Paths: {stats['path_count']}")
|
|
448
|
+
print(f"Matchers: {stats['matcher_count']}")
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## Architecture
|
|
452
|
+
|
|
453
|
+
```
|
|
454
|
+
pactship/
|
|
455
|
+
__init__.py # Public API exports (54 symbols)
|
|
456
|
+
models.py # Core data models (Contract, Interaction, Matcher, etc.)
|
|
457
|
+
dsl.py # Fluent builder DSL (ContractBuilder, InteractionBuilder)
|
|
458
|
+
matchers.py # 16 matcher types (exact, like, regex, range, etc.)
|
|
459
|
+
validator.py # Contract structure validation
|
|
460
|
+
verifier.py # Async HTTP provider verification + MockProvider
|
|
461
|
+
contract_io.py # YAML/JSON serialization and deserialization
|
|
462
|
+
schema.py # JSON Schema generation from contracts
|
|
463
|
+
diff.py # Contract version diffing with change classification
|
|
464
|
+
broker.py # Filesystem-based contract broker with versioning
|
|
465
|
+
cli.py # Click CLI (validate, verify, diff, publish, list, convert)
|
|
466
|
+
config.py # File + env var configuration loading
|
|
467
|
+
generator.py # CRUD and endpoint-based contract generation
|
|
468
|
+
graph.py # Service dependency graph with Mermaid output
|
|
469
|
+
matrix.py # Consumer/provider compatibility matrix
|
|
470
|
+
openapi.py # OpenAPI 3.x to pactship contract conversion
|
|
471
|
+
linter.py # Contract linting with REST best practice rules
|
|
472
|
+
reporting.py # Multi-format reports (JSON, JUnit, Markdown, TAP)
|
|
473
|
+
stats.py # Contract statistics and complexity metrics
|
|
474
|
+
hooks.py # Lifecycle hook registry (before/after verification)
|
|
475
|
+
transform.py # Contract transformation (paths, headers, bodies)
|
|
476
|
+
filters.py # Interaction filtering (method, path, tags)
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Data Flow
|
|
480
|
+
|
|
481
|
+
```
|
|
482
|
+
┌─────────────┐
|
|
483
|
+
│ YAML/JSON │
|
|
484
|
+
│ Contract │
|
|
485
|
+
└──────┬──────┘
|
|
486
|
+
│
|
|
487
|
+
┌────────────┼────────────┐
|
|
488
|
+
│ │ │
|
|
489
|
+
┌─────▼─────┐ ┌───▼───┐ ┌─────▼─────┐
|
|
490
|
+
│ Validator │ │ Diff │ │ Linter │
|
|
491
|
+
└─────┬─────┘ └───┬───┘ └─────┬─────┘
|
|
492
|
+
│ │ │
|
|
493
|
+
┌─────▼─────┐ ┌───▼───┐ ┌─────▼─────┐
|
|
494
|
+
│ Verifier │ │Report │ │ Issues │
|
|
495
|
+
│(async HTTP)│ │ │ │ │
|
|
496
|
+
└─────┬─────┘ └───────┘ └───────────┘
|
|
497
|
+
│
|
|
498
|
+
┌─────▼─────┐
|
|
499
|
+
│ Report │
|
|
500
|
+
│JSON/JUnit │
|
|
501
|
+
│ MD / TAP │
|
|
502
|
+
└───────────┘
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
## CI/CD Integration
|
|
506
|
+
|
|
507
|
+
### GitHub Actions
|
|
508
|
+
|
|
509
|
+
```yaml
|
|
510
|
+
name: Contract Tests
|
|
511
|
+
on: [push, pull_request]
|
|
512
|
+
|
|
513
|
+
jobs:
|
|
514
|
+
contracts:
|
|
515
|
+
runs-on: ubuntu-latest
|
|
516
|
+
steps:
|
|
517
|
+
- uses: actions/checkout@v4
|
|
518
|
+
- uses: actions/setup-python@v5
|
|
519
|
+
with:
|
|
520
|
+
python-version: "3.12"
|
|
521
|
+
- run: pip install pactship
|
|
522
|
+
- run: |
|
|
523
|
+
for f in contracts/*.yaml; do
|
|
524
|
+
pactship validate "$f"
|
|
525
|
+
done
|
|
526
|
+
- run: pactship diff contracts/v1.yaml contracts/v2.yaml || true
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Pre-commit Hook
|
|
530
|
+
|
|
531
|
+
```bash
|
|
532
|
+
#!/bin/sh
|
|
533
|
+
for f in contracts/*.yaml; do
|
|
534
|
+
pactship validate "$f" || exit 1
|
|
535
|
+
done
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
## License
|
|
539
|
+
|
|
540
|
+
MIT
|