Skip to content

Commit 9896320

Browse files
committed
chore: address pr comments
Signed-off-by: JP-Ellis <[email protected]>
1 parent e86b7eb commit 9896320

14 files changed

+139
-136
lines changed

Makefile

Lines changed: 30 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,55 @@
1-
DOCS_DIR := ./docs
2-
3-
PROJECT := pact-python
4-
PYTHON_MAJOR_VERSION := 3.11
5-
6-
sgr0 := $(shell tput sgr0)
7-
red := $(shell tput setaf 1)
8-
green := $(shell tput setaf 2)
9-
101
help:
112
@echo ""
123
@echo " clean to clear build and distribution directories"
13-
@echo " examples to run the example end to end tests (consumer, fastapi, flask, messaging)"
14-
@echo " consumer to run the example consumer tests"
15-
@echo " fastapi to run the example FastApi provider tests"
16-
@echo " flask to run the example Flask provider tests"
17-
@echo " messaging to run the example messaging e2e tests"
18-
@echo " package to create a distribution package in /dist/"
4+
@echo " package to build a wheel and sdist"
195
@echo " release to perform a release build, including deps, test, and package targets"
20-
@echo " test to run all tests"
216
@echo ""
7+
@echo " test to run all tests on the current python version"
8+
@echo " test-all to run all tests on all supported python versions"
9+
@echo " example to run the example end to end tests (requires docker)"
10+
@echo " lint to run the lints"
11+
@echo " ci to run test and lints"
12+
@echo ""
13+
@echo " help to show this help message"
14+
@echo ""
15+
@echo "Most of these targets are just wrappers around hatch commands."
16+
@echo "See https://hatch.pypa.org for information to install hatch."
2217

2318

2419
.PHONY: release
25-
release: test package
20+
release: clean test package
2621

2722

2823
.PHONY: clean
2924
clean:
3025
hatch clean
3126

3227

33-
define CONSUMER
34-
echo "consumer make"
35-
cd examples/consumer
36-
pip install -q -r requirements.txt
37-
pip install -e ../../
38-
./run_pytest.sh
39-
endef
40-
export CONSUMER
41-
42-
43-
define FLASK_PROVIDER
44-
echo "flask make"
45-
cd examples/flask_provider
46-
pip install -q -r requirements.txt
47-
pip install -e ../../
48-
./run_pytest.sh
49-
endef
50-
export FLASK_PROVIDER
51-
52-
53-
define FASTAPI_PROVIDER
54-
echo "fastapi make"
55-
cd examples/fastapi_provider
56-
pip install -q -r requirements.txt
57-
pip install -e ../../
58-
./run_pytest.sh
59-
endef
60-
export FASTAPI_PROVIDER
61-
62-
63-
define MESSAGING
64-
echo "messaging make"
65-
cd examples/message
66-
pip install -q -r requirements.txt
67-
pip install -e ../../
68-
./run_pytest.sh
69-
endef
70-
export MESSAGING
71-
72-
73-
.PHONY: consumer
74-
consumer:
75-
bash -c "$$CONSUMER"
76-
77-
78-
.PHONY: flask
79-
flask:
80-
bash -c "$$FLASK_PROVIDER"
28+
.PHONY: package
29+
package:
30+
hatch build
8131

8232

83-
.PHONY: fastapi
84-
fastapi:
85-
bash -c "$$FASTAPI_PROVIDER"
33+
.PHONY: test
34+
test:
35+
hatch run test
36+
hatch run coverage report -m --fail-under=100
8637

8738

88-
.PHONY: messaging
89-
messaging:
90-
bash -c "$$MESSAGING"
39+
.PHONY: test-all
40+
test-all:
41+
hatch run test:test
9142

9243

93-
.PHONY: examples
94-
examples: consumer flask fastapi messaging
44+
.PHONY: example
45+
example:
46+
hatch run example
9547

9648

97-
.PHONY: package
98-
package:
99-
hatch build
49+
.PHONY: lint
50+
lint:
51+
hatch run lint
10052

10153

102-
.PHONY: test
103-
test:
104-
hatch run all
105-
hatch run test:all
106-
coverage report -m --fail-under=100
54+
.PHONY: ci
55+
ci: test lint

examples/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from __future__ import annotations
1313

1414
from pathlib import Path
15-
from typing import Any, Generator
15+
from typing import Any, Generator, Union
1616

1717
import pytest
1818
from testcontainers.compose import DockerCompose
@@ -45,7 +45,7 @@ def broker(request: pytest.FixtureRequest) -> Generator[URL, Any, None]:
4545
Otherwise, the Pact broker is started in a container. The URL of the
4646
containerised broker is then returned.
4747
"""
48-
broker_url: str | None = request.config.getoption("--broker-url")
48+
broker_url: Union[str, None] = request.config.getoption("--broker-url")
4949

5050
# If we have been given a broker URL, there's nothing more to do here and we
5151
# can return early.

examples/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ services:
1818
- postgres
1919
ports:
2020
- "9292:9292"
21+
restart: always
2122
environment:
2223
# Basic auth credentials for the Broker
2324
PACT_BROKER_ALLOW_PUBLIC_READ: "true"

examples/src/consumer.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
44
This modules defines a simple
55
[consumer](https://docs.pact.io/getting_started/terminology#service-consumer)
6-
with Pact. As Pact is a consumer-driven framework, the consumer defines the
7-
interactions which the provider must then satisfy.
6+
which will be tested with Pact in the [consumer
7+
test](../tests/test_00_consumer.py). As Pact is a consumer-driven framework, the
8+
consumer defines the interactions which the provider must then satisfy.
89
910
The consumer is the application which makes requests to another service (the
1011
provider) and receives a response to process. In this example, we have a simple
@@ -20,7 +21,7 @@
2021

2122
from dataclasses import dataclass
2223
from datetime import datetime
23-
from typing import Any
24+
from typing import Any, Dict
2425

2526
import requests
2627

@@ -95,7 +96,7 @@ def get_user(self, user_id: int) -> User:
9596
uri = f"{self.base_uri}/users/{user_id}"
9697
response = requests.get(uri, timeout=5)
9798
response.raise_for_status()
98-
data: dict[str, Any] = response.json()
99+
data: Dict[str, Any] = response.json()
99100
return User(
100101
id=data["id"],
101102
name=data["name"],

examples/src/fastapi.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
44
This modules defines a simple
55
[provider](https://docs.pact.io/getting_started/terminology#service-provider)
6-
with Pact. As Pact is a consumer-driven framework, the consumer defines the
7-
contract which the provider must then satisfy.
6+
which will be tested with Pact in the [provider
7+
test](../tests/test_01_provider_fastapi.py). As Pact is a consumer-driven
8+
framework, the consumer defines the contract which the provider must then
9+
satisfy.
810
911
The provider is the application which receives requests from another service
1012
(the consumer) and returns a response. In this example, we have a simple
@@ -17,7 +19,7 @@
1719

1820
from __future__ import annotations
1921

20-
from typing import Any
22+
from typing import Any, Dict
2123

2224
from fastapi import FastAPI
2325
from fastapi.responses import JSONResponse
@@ -28,15 +30,15 @@
2830
As this is a simple example, we'll use a simple dict to represent a database.
2931
This would be replaced with a real database in a real application.
3032
31-
When testing the provider in a real application, the calls to the database
32-
would be mocked out to avoid the need for a real database. An example of this
33-
can be found in the test suite.
33+
When testing the provider in a real application, the calls to the database would
34+
be mocked out to avoid the need for a real database. An example of this can be
35+
found in the [test suite](../tests/test_01_provider_fastapi.py).
3436
"""
35-
FAKE_DB: dict[int, dict[str, Any]] = {}
37+
FAKE_DB: Dict[int, Dict[str, Any]] = {}
3638

3739

3840
@app.get("/users/{uid}")
39-
async def get_user_by_id(uid: int) -> dict[str, Any]:
41+
async def get_user_by_id(uid: int) -> Dict[str, Any]:
4042
"""
4143
Fetch a user by their ID.
4244

examples/src/flask.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
44
This modules defines a simple
55
[provider](https://docs.pact.io/getting_started/terminology#service-provider)
6-
with Pact. As Pact is a consumer-driven framework, the consumer defines the
7-
contract which the provider must then satisfy.
6+
which will be tested with Pact in the [provider
7+
test](../tests/test_01_provider_flask.py). As Pact is a consumer-driven
8+
framework, the consumer defines the contract which the provider must then
9+
satisfy.
810
911
The provider is the application which receives requests from another service
1012
(the consumer) and returns a response. In this example, we have a simple
@@ -17,7 +19,7 @@
1719

1820
from __future__ import annotations
1921

20-
from typing import Any
22+
from typing import Any, Dict, Union
2123

2224
from flask import Flask
2325

@@ -29,13 +31,13 @@
2931
3032
When testing the provider in a real application, the calls to the database
3133
would be mocked out to avoid the need for a real database. An example of this
32-
can be found in the test suite.
34+
can be found in the [test suite](../tests/test_01_provider_flask.py).
3335
"""
34-
FAKE_DB: dict[int, dict[str, Any]] = {}
36+
FAKE_DB: Dict[int, Dict[str, Any]] = {}
3537

3638

3739
@app.route("/users/<uid>")
38-
def get_user_by_id(uid: int) -> dict[str, Any] | tuple[dict[str, Any], int]:
40+
def get_user_by_id(uid: int) -> Union[Dict[str, Any], tuple[Dict[str, Any], int]]:
3941
"""
4042
Fetch a user by their ID.
4143

examples/src/message.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from __future__ import annotations
1010

1111
from pathlib import Path
12-
from typing import Any
12+
from typing import Any, Dict, Union
1313

1414

1515
class Filesystem:
@@ -42,7 +42,7 @@ def __init__(self) -> None:
4242
"""
4343
self.fs = Filesystem()
4444

45-
def process(self, event: dict[str, Any]) -> str | None:
45+
def process(self, event: Dict[str, Any]) -> Union[str, None]:
4646
"""
4747
Process an event from the queue.
4848
@@ -67,7 +67,7 @@ def process(self, event: dict[str, Any]) -> str | None:
6767
raise ValueError(msg)
6868

6969
@staticmethod
70-
def validate_event(event: dict[str, Any] | Any) -> None: # noqa: ANN401
70+
def validate_event(event: Union[Dict[str, Any], Any]) -> None: # noqa: ANN401
7171
"""
7272
Validates the event received from the queue.
7373

examples/tests/test_00_consumer.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
is responding with the expected responses. Once these interactions are
88
validated, the contracts can be published to a Pact Broker. The contracts can
99
then be used to validate the provider's interactions.
10+
11+
A good resource for understanding the consumer tests is the [Pact Consumer
12+
Test](https://docs.pact.io/5-minute-getting-started-guide#scope-of-a-consumer-pact-test)
13+
section of the Pact documentation.
1014
"""
1115

1216
from __future__ import annotations
1317

1418
import logging
1519
from http import HTTPStatus
16-
from typing import TYPE_CHECKING, Any, Generator
20+
from typing import TYPE_CHECKING, Any, Dict, Generator
1721

1822
import pytest
1923
import requests
@@ -40,6 +44,11 @@ def user_consumer() -> UserConsumer:
4044
the consumer to use Pact's mock provider. This allows us to define what
4145
requests the consumer will make to the provider, and what responses the
4246
provider will return.
47+
48+
The ability for the client to specify the expected response from the
49+
provider is critical to Pact's consumer-driven approach as it allows the
50+
consumer to declare the minimal response it requires from the provider (even
51+
if the provider is returning more data than the consumer needs).
4352
"""
4453
return UserConsumer(str(MOCK_URL))
4554

@@ -53,7 +62,7 @@ def pact(broker: URL, pact_dir: Path) -> Generator[Pact, Any, None]:
5362
the provider. This mock provider will expect to receive defined requests
5463
and will respond with defined responses.
5564
56-
The fixture here simply defines the Consumer and Provide, and sets up the
65+
The fixture here simply defines the Consumer and Provider, and sets up the
5766
mock provider. With each test, we define the expected request and response
5867
from the provider as follows:
5968
@@ -90,7 +99,11 @@ def test_get_existing_user(pact: Pact, user_consumer: UserConsumer) -> None:
9099
This test defines the expected request and response from the provider. The
91100
provider will be expected to return a response with a status code of 200,
92101
"""
93-
expected: dict[str, Any] = {
102+
# When setting up the expected response, the consumer should only define
103+
# what it needs from the provider (as opposed to the full schema). Should
104+
# the provider later decide to add or remove fields, Pact's consumer-driven
105+
# approach will ensure that interaction is still valid.
106+
expected: Dict[str, Any] = {
94107
"id": Format().integer,
95108
"name": "Verna Hampton",
96109
"created_on": Format().iso_8601_datetime(),

examples/tests/test_01_provider_fastapi.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616
additional endpoint on the provider, in this case `/_pact/provider_states`.
1717
Calls to this endpoint mock the relevant database calls to set the provider into
1818
the correct state.
19+
20+
A good resource for understanding the provider tests is the [Pact Provider
21+
Test](https://docs.pact.io/5-minute-getting-started-guide#scope-of-a-provider-pact-test)
22+
section of the Pact documentation.
1923
"""
2024

2125
from __future__ import annotations
2226

2327
from multiprocessing import Process
24-
from typing import Any, Generator
28+
from typing import Any, Dict, Generator, Union
2529
from unittest.mock import MagicMock
2630

2731
import pytest
@@ -42,7 +46,9 @@ class ProviderState(BaseModel):
4246

4347

4448
@app.post("/_pact/provider_states")
45-
async def mock_pact_provider_states(state: ProviderState) -> dict[str, str | None]:
49+
async def mock_pact_provider_states(
50+
state: ProviderState,
51+
) -> Dict[str, Union[str, None]]:
4652
"""
4753
Define the provider state.
4854
@@ -102,10 +108,13 @@ def mock_user_123_exists() -> None:
102108
103109
You may notice that the return value here differs from the consumer's
104110
expected response. This is because the consumer's expected response is
105-
guided by what the consumer users.
111+
guided by what the consumer uses.
106112
107113
By using consumer-driven contracts and testing the provider against the
108-
consumer's contract, we can ensure that the provider is only providing what
114+
consumer's contract, we can ensure that the provider is what the consumer
115+
needs. This allows the provider to safely evolve their API (by both adding
116+
and removing fields) without fear of breaking the interactions with the
117+
consumers.
109118
"""
110119
import examples.src.fastapi
111120

0 commit comments

Comments
 (0)