Interaction¶
Interaction module.
This module defines the classes that are used to define individual interactions
within a Pact
between a consumer and a provider. These
interactions can be of different types, such as HTTP requests, synchronous
messages, or asynchronous messages.
An interaction is a specific request that the consumer makes to the provider, and the response that the provider should return. On the consumer side, the interaction clearly defines the request that the consumer will make to the provider and the response that the consumer expects to receive. On the provider side, the interaction is replayed to the provider to ensure that the provider is able to handle the request and return the expected response.
Best Practices¶
When defining an interaction, it is important to ensure that the interaction is as minimal as possible (which is in contrast to the way specifications like OpenAPI are often written). This is because the interaction is used to verify that the consumer and provider can communicate correctly, not to define the entire API.
For example, consider a simple user API that has a GET /user/{id}
endpoint
which returns an object of the form:
{
"id": 123,
"username": "Alice"
"email": "alice@example.com",
"registered": "2021-02-26T10:17:51+11:00",
"last_login": "2024-07-04T13:25:45+10:00"
}
The user client might have two functionalities:
- To check if the user exists, and
- To retrieve the user's username.
The implementation of these two would be:
from pact.v3 import Pact
pact = Pact(consumer="UserClient", provider="UserService")
# Checking if a user exists
(
pact.upon_receiving("A request to check if a user exists")
.given("A user with ID 123 exists")
.with_request("GET", "/user/123")
.will_respond_with(200)
)
# Getting a user's username
(
pact.upon_receiving("A request to get a user's username")
.given("A user with ID 123 exists")
.with_request("GET", "/user/123")
.will_respond_with(200)
.with_body({"username": "Alice"})
)
Importantly, even if the server returns more information than just the username, since the client does not care about this information, it should not be included in the interaction.
Classes¶
AsyncMessageInteraction(pact_handle: pact.v3.ffi.PactHandle, description: str)
¶
Bases: Interaction
An asynchronous message interaction.
This class defines an asynchronous message interaction between a consumer and a provider. It defines the kind of messages a consumer can accept, and the is agnostic of the underlying protocol, be it a message queue, Apache Kafka, or some other asynchronous protocol.
Warning
This class is not yet fully implemented and is not yet usable.
This function should not be called directly. Instead, an
AsyncMessageInteraction should be created using the
upon_receiving(...)
method of a
Pact
instance using the "Async"
interaction type.
PARAMETER | DESCRIPTION |
---|---|
pact_handle
|
The Pact instance this interaction belongs to.
TYPE:
|
description
|
Description of the interaction. This must be unique within the Pact.
TYPE:
|
Source code in src/pact/v3/interaction/_async_message_interaction.py
HttpInteraction(pact_handle: pact.v3.ffi.PactHandle, description: str)
¶
Bases: Interaction
A synchronous HTTP interaction.
This class defines a synchronous HTTP interaction between a consumer and a provider. It defines a specific request that the consumer makes to the provider, and the response that the provider should return.
This class provides a simple way to define the request and response for an
HTTP interaction. As many elements are shared between the request and
response, this class provides a common interface for both. The functions
intelligently determine whether the element should be added to the request
or the response based on whether
will_respond_with(...)
has been called.
For example, the following two interactions are equivalent:
(
pact.upon_receiving("a request")
.with_request("GET", "/")
.with_header("X-Foo", "bar")
.will_respond_with(200)
.with_header("X-Hello", "world")
)
(
pact.upon_receiving("a request")
.with_request("GET", "/")
.will_respond_with(200)
.with_header("X-Foo", "bar", part="Request")
.with_header("X-Hello", "world", part="Response")
)
This class should not be instantiated directly. Instead, an
HttpInteraction
should be created using the
upon_receiving(...)
method of a
Pact
instance.
Source code in src/pact/v3/interaction/_http_interaction.py
Functions¶
set_header(name: str, value: str, part: Literal['Request', 'Response'] | None = None) -> Self
¶
Add a header to the request.
Unlike
with_header(...)
,
this function does no additional processing of the header value. This is
useful for headers that contain a JSON object.
PARAMETER | DESCRIPTION |
---|---|
name
|
Name of the header.
TYPE:
|
value
|
Value of the header.
TYPE:
|
part
|
Whether the header should be added to the request or the
response. If
TYPE:
|
Source code in src/pact/v3/interaction/_http_interaction.py
set_headers(headers: dict[str, str] | Iterable[tuple[str, str]], part: Literal['Request', 'Response'] | None = None) -> Self
¶
Add multiple headers to the request.
This function intelligently determines whether the header should be
added to the request or the response, based on whether the
will_respond_with(...)
method has been called.
See set_header(...)
for
more information.
PARAMETER | DESCRIPTION |
---|---|
headers
|
Headers to add to the request. |
part
|
Whether the headers should be added to the request or the
response. If
TYPE:
|
Source code in src/pact/v3/interaction/_http_interaction.py
will_respond_with(status: int) -> Self
¶
Set the response status.
Ideally, this function is called once all of the request information has
been set. This allows functions such as
with_header(...)
to intelligently determine whether this is a request or response header.
Alternatively, the part
argument can be used to explicitly specify
whether the header should be added to the request or the response.
PARAMETER | DESCRIPTION |
---|---|
status
|
Status for the response.
TYPE:
|
Source code in src/pact/v3/interaction/_http_interaction.py
with_header(name: str, value: str | dict[str, str] | Matcher[Any], part: Literal['Request', 'Response'] | None = None) -> Self
¶
Add a header to the request.
Repeated Headers¶
If the same header has multiple values (see RFC9110 §5.2), then the same header must be specified multiple times with order being preserved. For example
will expect a request with the following headers:
Note that repeated headers are case insensitive in accordance with RFC 9110 §5.1.
JSON Matching¶
Pact's matching rules are defined in the upstream
documentation
and support a wide range of matching rules. These can be specified
using a JSON object as a strong using json.dumps(...)
. For example,
the above rule whereby the X-Foo
header has multiple values can be
specified as:
(
pact.upon_receiving("a request")
.with_header(
"X-Foo",
json.dumps({
"value": ["bar", "baz"],
}),
)
)
It is also possible to have a more complicated Regex pattern for the
header. For example, a pattern for an Accept-Version
header might be
specified as:
(
pact.upon_receiving("a request").with_header(
"Accept-Version",
json.dumps({
"value": "1.2.3",
"pact:matcher:type": "regex",
"regex": r"\d+\.\d+\.\d+",
}),
)
)
If the value of the header is expected to be a JSON object and clashes
with the above syntax, then it is recommended to make use of the
set_header(...)
method instead.
PARAMETER | DESCRIPTION |
---|---|
name
|
Name of the header.
TYPE:
|
value
|
Value of the header. |
part
|
Whether the header should be added to the request or the
response. If
TYPE:
|
Source code in src/pact/v3/interaction/_http_interaction.py
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
|
with_headers(headers: dict[str, str] | Iterable[tuple[str, str]], part: Literal['Request', 'Response'] | None = None) -> Self
¶
Add multiple headers to the request.
Note that due to the requirement of Python dictionaries to have unique keys, it is not possible to specify a header multiple times to create a multi-valued header. Instead, you may:
-
Use an alternative data structure. Any iterable of key-value pairs is accepted, including a list of tuples, a list of lists, or a dictionary view.
-
Make multiple calls to
with_header(...)
orwith_headers(...)
. -
Specify the multiple values in a JSON object of the form:
python ( pact.upon_receiving("a request") .with_headers({ "X-Foo": json.dumps({ "value": ["bar", "baz"], }), ) )
See
with_header(...)
for more information.
PARAMETER | DESCRIPTION |
---|---|
headers
|
Headers to add to the request. |
part
|
Whether the header should be added to the request or the
response. If
TYPE:
|
Source code in src/pact/v3/interaction/_http_interaction.py
with_query_parameter(name: str, value: str | dict[str, str] | Matcher[Any]) -> Self
¶
Add a query to the request.
This is the query parameter(s) that the consumer will send to the provider.
If the same parameter can support multiple values, then the same parameter can be specified multiple times:
(
pact.upon_receiving("a request")
.with_query_parameter("name", "John")
.with_query_parameter("name", "Mary")
)
The above can equivalently be specified as:
(
pact.upon_receiving("a request").with_query_parameter(
"name",
json.dumps({
"value": ["John", "Mary"],
}),
)
)
It is also possible to have a more complicated Regex pattern for the
parameter. For example, a pattern for an version
parameter might be
specified as:
(
pact.upon_receiving("a request").with_query_parameter(
"version",
json.dumps({
"value": "1.2.3",
"pact:matcher:type": "regex",
"regex": r"\d+\.\d+\.\d+",
}),
)
)
For more information on the format of the JSON object, see the upstream documentation.
PARAMETER | DESCRIPTION |
---|---|
name
|
Name of the query parameter.
TYPE:
|
value
|
Value of the query parameter. |
Source code in src/pact/v3/interaction/_http_interaction.py
with_query_parameters(parameters: dict[str, Any] | Iterable[tuple[str, Any]]) -> Self
¶
Add multiple query parameters to the request.
See
with_query_parameter(...)
for more information.
PARAMETER | DESCRIPTION |
---|---|
parameters
|
Query parameters to add to the request. |
Source code in src/pact/v3/interaction/_http_interaction.py
with_request(method: str, path: str | Matcher[Any]) -> Self
¶
Set the request.
This is the request that the consumer will make to the provider.
PARAMETER | DESCRIPTION |
---|---|
method
|
HTTP method for the request.
TYPE:
|
path
|
Path for the request. |
Source code in src/pact/v3/interaction/_http_interaction.py
Interaction(description: str)
¶
Bases: ABC
Interaction between a consumer and a provider.
This abstract class defines an interaction between a consumer and a provider. The concrete subclasses define the type of interaction, and include:
Interaction Part¶
For HTTP and synchronous message interactions, the interaction is split into
two parts: the request and the response. The interaction part is used to
specify which part of the interaction is being set. This is specified using
the part
argument of various methods (which defaults to an intelligent
choice based on the order of the methods called).
The asynchronous message interaction does not have parts, as the interaction contains a single message from the provider (a.ka. the producer of the message) to the consumer. An attempt to set a response part will raise an error.
As this class is abstract, this function should not be called directly but should instead be called through one of the concrete subclasses.
PARAMETER | DESCRIPTION |
---|---|
description
|
Description of the interaction. This must be unique within the Pact.
TYPE:
|
Source code in src/pact/v3/interaction/_base.py
Functions¶
add_text_comment(comment: str) -> Self
¶
Add a text comment for the interaction.
This is used by V4 interactions to set arbitrary text comments for the interaction.
PARAMETER | DESCRIPTION |
---|---|
comment
|
Text of the comment.
TYPE:
|
Warning¶
Internally, the comments are appended to an array under the text
comment key. Care should be taken to ensure that conflicts are not
introduced by
set_comment
.
Source code in src/pact/v3/interaction/_base.py
given(state: str, *, name: str | None = None, value: str | None = None, parameters: dict[str, Any] | str | None = None) -> Self
¶
Set the provider state.
This is the state that the provider should be in when the Interaction is executed. When the provider is being verified, the provider state is passed to the provider so that its internal state can be set to match the provider state.
In its simplest form, the provider state is a string. For example, to
match a provider state of a user exists
, you would use:
It is also possible to specify a parameter that will be used to match
the provider state. For example, to match a provider state of a user
exists
with a parameter id
that has the value 123
, you would use:
Lastly, it is possible to specify multiple parameters that will be used
to match the provider state. For example, to match a provider state of
a user exists
with a parameter id
that has the value 123
and a
parameter name
that has the value John
, you would use:
(
pact.upon_receiving("a request").given(
"a user exists",
parameters={
"id": "123",
"name": "John",
},
)
)
This function can be called repeatedly to specify multiple provider
states for the same Interaction. If the same state
is specified with
different parameters, then the parameters are merged together. The above
example with multiple parameters can equivalently be specified as:
(
pact.upon_receiving("a request")
.given("a user exists", name="id", value="123")
.given("a user exists", name="name", value="John")
)
PARAMETER | DESCRIPTION |
---|---|
state
|
Provider state for the Interaction.
TYPE:
|
name
|
Name of the parameter. This must be specified in conjunction
with
TYPE:
|
value
|
Value of the parameter. This must be specified in conjunction
with
TYPE:
|
parameters
|
Key-value pairs of parameters to use for the provider state.
These must be encodable using If the string does not contain a valid JSON object, then the string is passed directly as follows: |
RAISES | DESCRIPTION |
---|---|
ValueError
|
If the combination of arguments is invalid or inconsistent. |
Source code in src/pact/v3/interaction/_base.py
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
|
set_comment(key: str, value: Any | None) -> Self
¶
Set a comment for the interaction.
This is used by V4 interactions to set a comment for the interaction. A comment consists of a key-value pair, where the key is a string and the value is anything that can be encoded as JSON.
PARAMETER | DESCRIPTION |
---|---|
key
|
Key for the comment.
TYPE:
|
value
|
Value for the comment. This must be encodable using
TYPE:
|
Warning¶
This function will overwrite any existing comment with the same key. In
particular, the text
key is used by add_text_comment
.
Source code in src/pact/v3/interaction/_base.py
set_key(key: str | None) -> Self
¶
Sets the key for the interaction.
This is used by V4 interactions to set the key of the interaction, which can subsequently used to reference the interaction.
Source code in src/pact/v3/interaction/_base.py
set_pending(*, pending: bool) -> Self
¶
Mark the interaction as pending.
This is used by V4 interactions to mark the interaction as pending, in which case the provider is not expected to honour the interaction.
Source code in src/pact/v3/interaction/_base.py
test_name(name: str) -> Self
¶
Set the test name annotation for the interaction.
This is used by V4 interactions to set the name of the test.
PARAMETER | DESCRIPTION |
---|---|
name
|
Name of the test.
TYPE:
|
Source code in src/pact/v3/interaction/_base.py
with_binary_body(body: bytes | None, content_type: str | None = None, part: Literal['Request', 'Response'] | None = None) -> Self
¶
Adds a binary body to the request or response.
Note that for HTTP interactions, this function will overwrite the body
if it has been set using
with_body(...)
.
PARAMETER | DESCRIPTION |
---|---|
part
|
Whether the body should be added to the request or the response.
If
TYPE:
|
content_type
|
Content type of the body. This is ignored if the
TYPE:
|
body
|
Body of the request.
TYPE:
|
Source code in src/pact/v3/interaction/_base.py
with_body(body: str | dict[str, Any] | Matcher[Any] | None = None, content_type: str | None = None, part: Literal['Request', 'Response'] | None = None) -> Self
¶
Set the body of the request or response.
PARAMETER | DESCRIPTION |
---|---|
body
|
Body of the request. If this is
TYPE:
|
content_type
|
Content type of the body. This is ignored if the
TYPE:
|
part
|
Whether the body should be added to the request or the response.
If
TYPE:
|
Source code in src/pact/v3/interaction/_base.py
with_generators(generators: dict[str, Any] | str, part: Literal['Request', 'Response'] | None = None) -> Self
¶
Add generators to the interaction.
Generators are used to adjust how parts of the request or response are generated when the Pact is being tested. This can be useful for fields that vary each time the request is made, such as a timestamp.
PARAMETER | DESCRIPTION |
---|---|
generators
|
Generators to add to the interaction. This must be encodable using
|
part
|
Whether the generators should be added to the request or the
response. If
TYPE:
|
Source code in src/pact/v3/interaction/_base.py
with_matching_rules(rules: dict[str, Any] | str, part: Literal['Request', 'Response'] | None = None) -> Self
¶
Add matching rules to the interaction.
Matching rules are used to specify how the request or response should be matched. This is useful for specifying that certain parts of the request or response are flexible, such as the date or time.
PARAMETER | DESCRIPTION |
---|---|
rules
|
Matching rules to add to the interaction. This must be
encodable using |
part
|
Whether the matching rules should be added to the request or the
response. If
TYPE:
|
Source code in src/pact/v3/interaction/_base.py
with_metadata(__metadata: dict[str, str] | None = None, __part: Literal['Request', 'Response'] | None = None, /, **kwargs: str) -> Self
¶
Set metadata for the interaction.
This function may either be called with a single dictionary of metadata, or with keyword arguments that are the key-value pairs of the metadata (or a combination therefore):
interaction.with_metadata({"key": "value", "key two": "value two"})
interaction.with_metadata(foo="bar", baz="qux")
The value of None
will remove the metadata key from the interaction.
This is distinct from using an empty string or a string containing the
JSON null
value, which will set the metadata key to an empty string or
the JSON null
value, respectively.
Note
There are two special keys which cannot be used as keyword
arguments: __metadata
and __part
. Should there ever be a need
to set metadata with one of these keys, they must be passed through
as a dictionary:
PARAMETER | DESCRIPTION |
---|---|
___metadata
|
Dictionary of metadata keys and associated values.
|
__part
|
Whether the metadata should be added to the request or the
response. If
TYPE:
|
**kwargs
|
Additional metadata key-value pairs.
TYPE:
|
RETURNS | DESCRIPTION |
---|---|
Self
|
The current instance of the interaction. |
Source code in src/pact/v3/interaction/_base.py
with_multipart_file(part_name: str, path: Path | None, content_type: str | None = None, part: Literal['Request', 'Response'] | None = None, boundary: str | None = None) -> Self
¶
Adds a binary file as the body of a multipart request or response.
The content type of the body will be set to a MIME multipart message.
Source code in src/pact/v3/interaction/_base.py
with_plugin_contents(contents: dict[str, Any] | str, content_type: str, part: Literal['Request', 'Response'] | None = None) -> Self
¶
Set the interaction content using a plugin.
The value of contents
is passed directly to the plugin as a JSON
string. The plugin will document the format of the JSON content.
PARAMETER | DESCRIPTION |
---|---|
contents
|
Body of the request. If this is |
content_type
|
Content type of the body. This is ignored if the
TYPE:
|
part
|
Whether the body should be added to the request or the response.
If
TYPE:
|
Source code in src/pact/v3/interaction/_base.py
SyncMessageInteraction(pact_handle: pact.v3.ffi.PactHandle, description: str)
¶
Bases: Interaction
A synchronous message interaction.
This class defines a synchronous message interaction between a consumer and
a provider. As with HttpInteraction
, it
defines a specific request that the consumer makes to the provider, and the
response that the provider should return.
Warning
This class is not yet fully implemented and is not yet usable.
This function should not be called directly. Instead, an
AsyncMessageInteraction should be created using the
upon_receiving(...)
method of a
Pact
instance using the "Sync"
interaction type.
PARAMETER | DESCRIPTION |
---|---|
pact_handle
|
Handle for the Pact.
TYPE:
|
description
|
Description of the interaction. This must be unique within the Pact.
TYPE:
|