From dab6dbac1c4e697a757a6af2b77c7c642fd58b9c Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Wed, 11 Oct 2023 12:26:22 +1100 Subject: [PATCH] style: fix pre-commit lints This commit fixes pre-commit linting and formatting issues. To avoid refactoring all of the existing Python code (which will is going to be deprecated), the pre-commit configuration has been updated to ignore these Python files. All other changes in this commit are purely stylistic. Signed-off-by: JP-Ellis --- .github/semantic.yml | 2 - .../workflows/trigger_pact_docs_update.yml | 2 +- .pre-commit-config.yaml | 6 + Dockerfile.ubuntu | 2 +- README.md | 245 +++++++----------- RELEASING.md | 70 +++-- docker/README.md | 24 +- examples/README.md | 77 +----- run-docker.sh | 2 +- script/commit_message.py | 21 +- script/release_prep.sh | 2 - 11 files changed, 173 insertions(+), 280 deletions(-) diff --git a/.github/semantic.yml b/.github/semantic.yml index af6bf3c91..918f44ded 100644 --- a/.github/semantic.yml +++ b/.github/semantic.yml @@ -1,4 +1,2 @@ titleAndCommits: true allowMergeCommits: true - - diff --git a/.github/workflows/trigger_pact_docs_update.yml b/.github/workflows/trigger_pact_docs_update.yml index 109521d95..634d7a804 100644 --- a/.github/workflows/trigger_pact_docs_update.yml +++ b/.github/workflows/trigger_pact_docs_update.yml @@ -5,7 +5,7 @@ on: branches: - master paths: - - '**.md' + - "**.md" jobs: run: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 68f0845b0..88eb34759 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,6 +41,9 @@ repos: rev: v0.0.289 hooks: - id: ruff + # Exclude python files in pact/** and tests/**, except for the + # files in pact/v3/** and tests/v3/**. + exclude: ^(pact|tests)/(?!v3/).*\.py$ args: [--fix, --exit-non-zero-on-fix] stages: [pre-push] @@ -48,6 +51,9 @@ repos: rev: 23.9.1 hooks: - id: black + # Exclude python files in pact/** and tests/**, except for the + # files in pact/v3/** and tests/v3/**. + exclude: ^(pact|tests)/(?!v3/).*\.py$ stages: [pre-push] - repo: https://github.com/commitizen-tools/commitizen diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu index 74efa1c69..c841bc1cc 100644 --- a/Dockerfile.ubuntu +++ b/Dockerfile.ubuntu @@ -3,7 +3,7 @@ ENV DEBIAN_FRONTEND=noninteractive ARG PYTHON_VERSION 3.9 #Set of all dependencies needed for pyenv to work on Ubuntu -RUN apt-get update \ +RUN apt-get update \ && apt-get install -y --no-install-recommends make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget ca-certificates curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev mecab-ipadic-utf8 git # Set-up necessary Env vars for PyEnv diff --git a/README.md b/README.md index 230d6fd9d..6f046fe56 100644 --- a/README.md +++ b/README.md @@ -4,25 +4,24 @@ [![License](https://img.shields.io/github/license/pact-foundation/pact-python.svg?maxAge=2592000)](https://github.com/pact-foundation/pact-python/blob/master/LICENSE) [![Build and Test](https://github.com/pact-foundation/pact-python/actions/workflows/build_and_test.yml/badge.svg)](https://github.com/pact-foundation/pact-python/actions/workflows/build_and_test.yml) -Python version of Pact. Enables consumer driven contract testing, -providing a mock service and DSL for the consumer project, and -interaction playback and verification for the service provider project. -Currently supports version 2 of the [Pact specification]. +Python version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project. Currently supports version 2 of the [Pact specification]. -For more information about what Pact is, and how it can help you -test your code more efficiently, check out the [Pact documentation]. +For more information about what Pact is, and how it can help you test your code more efficiently, check out the [Pact documentation]. Note: As of Version 1.0 deprecates support for python 2.7 to allow us to incorporate python 3.x features more readily. If you want to still use Python 2.7 use the 0.x.y versions. Only bug fixes will now be added to that release. # How to use pact-python ## Installation + ``` pip install pact-python ``` ## Getting started + + A guide follows but if you go to the [examples](https://github.com/pact-foundation/pact-python/tree/master/examples). This has a consumer, provider and pact-broker set of tests for both FastAPI and Flask. ## Writing a Pact @@ -34,16 +33,13 @@ Creating a complete contract is a two step process: ## Writing the Consumer Test -If we have a method that communicates with one of our external services, which we'll call -`Provider`, and our product, `Consumer` is hitting an endpoint on `Provider` at -`/users/` to get information about a particular user. +If we have a method that communicates with one of our external services, which we'll call `Provider`, and our product, `Consumer` is hitting an endpoint on `Provider` at `/users/` to get information about a particular user. If the code to fetch a user looked like this: ```python import requests - def user(user_name): """Fetch a user object by user_name from the server.""" uri = 'http://localhost:1234/users/' + user_name @@ -87,16 +83,12 @@ class GetUserInfoContract(unittest.TestCase): This does a few important things: - - Defines the Consumer and Provider objects that describe our product and our service under test - - Uses `given` to define the setup criteria for the Provider `UserA exists and is not an administrator` - - Defines what the request that is expected to be made by the consumer will contain - - Defines how the server is expected to respond +- Defines the Consumer and Provider objects that describe our product and our service under test +- Uses `given` to define the setup criteria for the Provider `UserA exists and is not an administrator` +- Defines what the request that is expected to be made by the consumer will contain +- Defines how the server is expected to respond -Using the Pact object as a [context manager], we call our method under test -which will then communicate with the Pact mock service. The mock service will respond with -the items we defined, allowing us to assert that the method processed the response and -returned the expected value. If you want more control over when the mock service is -configured and the interactions verified, use the `setup` and `verify` methods, respectively: +Using the Pact object as a [context manager], we call our method under test which will then communicate with the Pact mock service. The mock service will respond with the items we defined, allowing us to assert that the method processed the response and returned the expected value. If you want more control over when the mock service is configured and the interactions verified, use the `setup` and `verify` methods, respectively: ```python (pact @@ -114,8 +106,7 @@ configured and the interactions verified, use the `setup` and `verify` methods, ### Requests -When defining the expected HTTP request that your code is expected to make you -can specify the method, path, body, headers, and query: +When defining the expected HTTP request that your code is expected to make you can specify the method, path, body, headers, and query: ```python pact.with_request( @@ -125,8 +116,7 @@ pact.with_request( ) ``` -`query` is used to specify URL query parameters, so the above example expects -a request made to `/api/v1/my-resources/?search=example`. +`query` is used to specify URL query parameters, so the above example expects a request made to `/api/v1/my-resources/?search=example`. ```python pact.with_request( @@ -137,12 +127,9 @@ pact.with_request( ) ``` -You can define exact values for your expected request like the examples above, -or you can use the matchers defined later to assist in handling values that are -variable. +You can define exact values for your expected request like the examples above, or you can use the matchers defined later to assist in handling values that are variable. -The default hostname and port for the Pact mock service will be -`localhost:1234` but you can adjust this during Pact creation: +The default hostname and port for the Pact mock service will be `localhost:1234` but you can adjust this during Pact creation: ```python from pact import Consumer, Provider @@ -150,28 +137,22 @@ pact = Consumer('Consumer').has_pact_with( Provider('Provider'), host_name='mockservice', port=8080) ``` -This can be useful if you need to run to create more than one Pact for your test -because your code interacts with two different services. It is important to note -that the code you are testing with this contract _must_ contact the mock service. -So in this example, the `user` method could accept an argument to specify the -location of the server, or retrieve it from an environment variable so you can -change its URI during the test. +This can be useful if you need to run to create more than one Pact for your test because your code interacts with two different services. It is important to note that the code you are testing with this contract _must_ contact the mock service. So in this example, the `user` method could accept an argument to specify the location of the server, or retrieve it from an environment variable so you can change its URI during the test. The mock service offers you several important features when building your contracts: -- It provides a real HTTP server that your code can contact during the test and provides the responses you defined. -- You provide it with the expectations for the request your code will make and it will assert the contents of the actual requests made based on your expectations. -- If a request is made that does not match one you defined or if a request from your code is missing it will return an error with details. -- Finally, it will record your contracts as a JSON file that you can store in your repository or publish to a Pact broker. + +- It provides a real HTTP server that your code can contact during the test and provides the responses you defined. +- You provide it with the expectations for the request your code will make and it will assert the contents of the actual requests made based on your expectations. +- If a request is made that does not match one you defined or if a request from your code is missing it will return an error with details. +- Finally, it will record your contracts as a JSON file that you can store in your repository or publish to a Pact broker. ## Expecting Variable Content -The above test works great if that user information is always static, but what happens if -the user has a last updated field that is set to the current time every time the object is -modified? To handle variable data and make your tests more robust, there are 3 helpful matchers: + +The above test works great if that user information is always static, but what happens if the user has a last updated field that is set to the current time every time the object is modified? To handle variable data and make your tests more robust, there are 3 helpful matchers: ### Term(matcher, generate) -Asserts the value should match the given regular expression. You could use this -to expect a timestamp with a particular format in the request or response where -you know you need a particular format, but are unconcerned about the exact date: + +Asserts the value should match the given regular expression. You could use this to expect a timestamp with a particular format in the request or response where you know you need a particular format, but are unconcerned about the exact date: ```python from pact import Term @@ -188,12 +169,10 @@ body = { .will_respond_with(200, body=body)) ``` -When you run the tests for the consumer, the mock service will return the value you provided -as `generate`, in this case `2016-12-15T20:16:01`. When the contract is verified on the -provider, the regex will be used to search the response from the real provider service -and the test will be considered successful if the regex finds a match in the response. +When you run the tests for the consumer, the mock service will return the value you provided as `generate`, in this case `2016-12-15T20:16:01`. When the contract is verified on the provider, the regex will be used to search the response from the real provider service and the test will be considered successful if the regex finds a match in the response. ### Like(matcher) + Asserts the element's type matches the matcher. For example: ```python @@ -202,6 +181,7 @@ Like(123) # Matches if the value is an integer Like('hello world') # Matches if the value is a string Like(3.14) # Matches if the value is a float ``` + The argument supplied to `Like` will be what the mock service responds with. When a dictionary is used as an argument for Like, all the child objects (and their child objects etc.) will be matched according to their types, unless you use a more specific matcher like a Term. @@ -220,8 +200,8 @@ Like({ ``` ### EachLike(matcher, minimum=1) -Asserts the value is an array type that consists of elements -like the one passed in. It can be used to assert simple arrays: + +Asserts the value is an array type that consists of elements like the one passed in. It can be used to assert simple arrays: ```python from pact import EachLike @@ -240,11 +220,9 @@ EachLike({ }) ``` -> Note, you do not need to specify everything that will be returned from the Provider in a -> JSON response, any extra data that is received will be ignored and the tests will still pass. +> Note, you do not need to specify everything that will be returned from the Provider in a JSON response, any extra data that is received will be ignored and the tests will still pass. -> Note, to get the generated values from an object that can contain matchers like Term, Like, EachLike, etc. -> for assertion in self.assertEqual(result, expected) you may need to use get_generated_values() helper function: +> Note, to get the generated values from an object that can contain matchers like Term, Like, EachLike, etc. for assertion in self.assertEqual(result, expected) you may need to use get_generated_values() helper function: ```python from pact.matchers import get_generated_values @@ -252,6 +230,7 @@ self.assertEqual(result, get_generated_values(expected)) ``` ### Match common formats + Often times, you find yourself having to re-write regular expressions for common formats. ```python @@ -262,20 +241,20 @@ Format().ip_address # Matches if the value is an ip address We've created a number of them for you to save you the time: -| matcher | description | -|-------------------|-------------------------------------------------------------------------------------------------------------------------| -| `identifier` | Match an ID (e.g. 42) | -| `integer` | Match all numbers that are integers (both ints and longs) | -| `decimal` | Match all real numbers (floating point and decimal) | -| `hexadecimal` | Match all hexadecimal encoded strings | -| `date` | Match string containing basic ISO8601 dates (e.g. 2016-01-01) | -| `timestamp` | Match a string containing an RFC3339 formatted timestamp (e.g. Mon, 31 Oct 2016 15:21:41 -0400) | -| `time` | Match string containing times in ISO date format (e.g. T22:44:30.652Z) | -| `iso_datetime` | Match string containing ISO 8601 formatted dates (e.g. 2015-08-06T16:53:10+01:00) | -| `iso_datetime_ms` | Match string containing ISO 8601 formatted dates, enforcing millisecond precision (e.g. 2015-08-06T16:53:10.123+01:00) | -| `ip_address` | Match string containing IP4 formatted address | -| `ipv6_address` | Match string containing IP6 formatted address | -| `uuid` | Match strings containing UUIDs | +| matcher | description | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `identifier` | Match an ID (e.g. 42) | +| `integer` | Match all numbers that are integers (both ints and longs) | +| `decimal` | Match all real numbers (floating point and decimal) | +| `hexadecimal` | Match all hexadecimal encoded strings | +| `date` | Match string containing basic ISO8601 dates (e.g. 2016-01-01) | +| `timestamp` | Match a string containing an RFC3339 formatted timestamp (e.g. Mon, 31 Oct 2016 15:21:41 -0400) | +| `time` | Match string containing times in ISO date format (e.g. T22:44:30.652Z) | +| `iso_datetime` | Match string containing ISO 8601 formatted dates (e.g. 2015-08-06T16:53:10+01:00) | +| `iso_datetime_ms` | Match string containing ISO 8601 formatted dates, enforcing millisecond precision (e.g. 2015-08-06T16:53:10.123+01:00) | +| `ip_address` | Match string containing IP4 formatted address | +| `ipv6_address` | Match string containing IP6 formatted address | +| `uuid` | Match strings containing UUIDs | These can be used to replace other matchers @@ -329,42 +308,36 @@ output, logs = verifier.verify_pacts('./userserviceclient-userservice.json') ``` The parameters for this differ slightly in naming from their CLI equivalents: -| CLI | native Python | +| CLI | native Python | |-----------------------------------|-----------------------------------| -| `--branch` | `branch` | -| `--build-url` | `build_url` | -| `--auto-detect-version-properties`| `auto_detect_version_properties` | -| `--tag=TAG` | `consumer_tags` | -| `--tag-with-git-branch` | `tag_with_git_branch` | -| `PACT_DIRS_OR_FILES` | `pact_dir` | -| `--consumer-app-version` | `version` | -| `n/a` | `consumer_name` | +| `--branch` | `branch` | +| `--build-url` | `build_url` | +| `--auto-detect-version-properties`| `auto_detect_version_properties` | +| `--tag=TAG` | `consumer_tags` | +| `--tag-with-git-branch` | `tag_with_git_branch` | +| `PACT_DIRS_OR_FILES` | `pact_dir` | +| `--consumer-app-version` | `version` | +| `n/a` | `consumer_name` | ## Verifying Pacts Against a Service -In addition to writing Pacts for Python consumers, you can also verify those Pacts -against a provider of any language. There are two ways to do this. +In addition to writing Pacts for Python consumers, you can also verify those Pacts against a provider of any language. There are two ways to do this. ### CLI -After installing pact-python a `pact-verifier` -application should be available. To get details about its use you can call it with the -help argument: +After installing pact-python a `pact-verifier` application should be available. To get details about its use you can call it with the help argument: ```bash pact-verifier --help ``` -The simplest example is verifying a server with locally stored Pact files and no provider -states: +The simplest example is verifying a server with locally stored Pact files and no provider states: ```bash pact-verifier --provider-base-url=http://localhost:8080 --pact-url=./pacts/consumer-provider.json ``` -Which will immediately invoke the Pact verifier, making HTTP requests to the server located -at `http://localhost:8080` based on the Pacts in `./pacts/consumer-provider.json` and -reporting the results. +Which will immediately invoke the Pact verifier, making HTTP requests to the server located at `http://localhost:8080` based on the Pacts in `./pacts/consumer-provider.json` and reporting the results. There are several options for configuring how the Pacts are verified: @@ -374,9 +347,7 @@ Required. Defines the URL of the server to make requests to when verifying the P ###### --pact-url -Required if --pact-urls not specified. The location of a Pact file you want -to verify. This can be a URL to a [Pact Broker] or a local path, to provide -multiple files, specify multiple arguments. +Required if --pact-urls not specified. The location of a Pact file you want to verify. This can be a URL to a [Pact Broker] or a local path, to provide multiple files, specify multiple arguments. ``` pact-verifier --provider-base-url=http://localhost:8080 --pact-url=./pacts/one.json --pact-url=./pacts/two.json @@ -384,14 +355,11 @@ pact-verifier --provider-base-url=http://localhost:8080 --pact-url=./pacts/one.j ###### --pact-urls -Required if --pact-url not specified. The location of the Pact files you want -to verify. This can be a URL to a [Pact Broker] or one or more local paths, separated by a comma. +Required if --pact-url not specified. The location of the Pact files you want to verify. This can be a URL to a [Pact Broker] or one or more local paths, separated by a comma. ###### --provider-states-url -_DEPRECATED AFTER v 0.6.0._ The URL where your provider application will produce the list of available provider states. -The verifier calls this URL to ensure the Pacts specify valid states before making the HTTP -requests. +_DEPRECATED AFTER v 0.6.0._ The URL where your provider application will produce the list of available provider states. The verifier calls this URL to ensure the Pacts specify valid states before making the HTTP requests. ###### --provider-states-setup-url @@ -399,33 +367,27 @@ The URL which should be called to setup a specific provider state before a Pact ###### --pact-broker-url -Base URl for the Pact Broker instance to publish pacts to. Can also be specified via the environment variable -`PACT_BROKER_BASE_URL`. +Base URl for the Pact Broker instance to publish pacts to. Can also be specified via the environment variable `PACT_BROKER_BASE_URL`. ###### --pact-broker-username -The username to use when contacting the Pact Broker. Can also be specified via the environment variable -`PACT_BROKER_USERNAME`. +The username to use when contacting the Pact Broker. Can also be specified via the environment variable `PACT_BROKER_USERNAME`. ###### --pact-broker-password -The password to use when contacting the Pact Broker. You can also specify this value -as the environment variable `PACT_BROKER_PASSWORD`. +The password to use when contacting the Pact Broker. You can also specify this value as the environment variable `PACT_BROKER_PASSWORD`. ###### --pact-broker-token -The bearer token to use when contacting the Pact Broker. You can also specify this value -as the environment variable `PACT_BROKER_TOKEN`. +The bearer token to use when contacting the Pact Broker. You can also specify this value as the environment variable `PACT_BROKER_TOKEN`. ###### --consumer-version-tag -Retrieve the latest pacts with this consumer version tag. Used in conjunction with `--provider`. -May be specified multiple times. +Retrieve the latest pacts with this consumer version tag. Used in conjunction with `--provider`. May be specified multiple times. ###### --consumer-version-selector -You can also retrieve pacts with consumer version selector, a more flexible approach in specifying which pacts you need. -May be specified multiple times. Read more about selectors [here](https://docs.pact.io/pact_broker/advanced_topics/consumer_version_selectors/). +You can also retrieve pacts with consumer version selector, a more flexible approach in specifying which pacts you need. May be specified multiple times. Read more about selectors [here](https://docs.pact.io/pact_broker/advanced_topics/consumer_version_selectors/). ###### --provider-version-tag @@ -453,6 +415,7 @@ The provider application version. Required for publishing verification results. Publish verification results to the broker. ### Python API + You can use the Verifier class. This allows you to write native python code and the test framework of your choice. ```python @@ -481,51 +444,46 @@ success, logs = verifier.verify_with_broker( enable_pending=True, ) assert success == 0 - ``` The parameters for this differ slightly in naming from their CLI equivalents: -| CLI | native Python | -|-----------------------------------|------------------------------- | -| `--log-dir` | `log_dir` | -| `--log-level` | `log_level` | -| `--provider-app-version` | `provider_app_version` | -| `--headers` | `custom_provider_headers` | -| `--consumer-version-tag` | `consumer_tags` | -| `--provider-version-tag` | `provider_tags` | -| `--provider-states-setup-url` | `provider_states_setup_url` | -| `--verbose` | `verbose` | -| `--consumer-version-selector` | `consumer_selectors` | -| `--publish-verification-results` | `publish_verification_results` | -| `--provider-version-branch` | `provider_version_branch` | +| CLI | native Python | +| -------------------------------- | ------------------------------ | +| `--log-dir` | `log_dir` | +| `--log-level` | `log_level` | +| `--provider-app-version` | `provider_app_version` | +| `--headers` | `custom_provider_headers` | +| `--consumer-version-tag` | `consumer_tags` | +| `--provider-version-tag` | `provider_tags` | +| `--provider-states-setup-url` | `provider_states_setup_url` | +| `--verbose` | `verbose` | +| `--consumer-version-selector` | `consumer_selectors` | +| `--publish-verification-results` | `publish_verification_results` | +| `--provider-version-branch` | `provider_version_branch` | You can see more details in the examples -- [Message Provider Verifier Test](https://github.com/pact-foundation/pact-python/tree/master/examples/message/tests/provider/test_message_provider.py) -- [Flask Provider Verifier Test](https://github.com/pact-foundation/pact-python/tree/master/examples/flask_provider/tests/provider/test_provider.py) -- [FastAPI Provider Verifier Test](https://github.com/pact-foundation/pact-python/tree/master/examples/fastapi_provider/tests/provider/test_provider.py) +- [Message Provider Verifier Test](https://github.com/pact-foundation/pact-python/tree/master/examples/message/tests/provider/test_message_provider.py) +- [Flask Provider Verifier Test](https://github.com/pact-foundation/pact-python/tree/master/examples/flask_provider/tests/provider/test_provider.py) +- [FastAPI Provider Verifier Test](https://github.com/pact-foundation/pact-python/tree/master/examples/fastapi_provider/tests/provider/test_provider.py) ### Provider States -In many cases, your contracts will need very specific data to exist on the provider -to pass successfully. If you are fetching a user profile, that user needs to exist, -if querying a list of records, one or more records needs to exist. To support -decoupling the testing of the consumer and provider, Pact offers the idea of provider -states to communicate from the consumer what data should exist on the provider. -When setting up the testing of a provider you will also need to setup the management of -these provider states. The Pact verifier does this by making additional HTTP requests to -the `--provider-states-setup-url` you provide. This URL could be -on the provider application or a separate one. Some strategies for managing state include: +In many cases, your contracts will need very specific data to exist on the provider to pass successfully. If you are fetching a user profile, that user needs to exist, if querying a list of records, one or more records needs to exist. To support decoupling the testing of the consumer and provider, Pact offers the idea of provider states to communicate from the consumer what data should exist on the provider. + +When setting up the testing of a provider you will also need to setup the management of these provider states. The Pact verifier does this by making additional HTTP requests to the `--provider-states-setup-url` you provide. This URL could be on the provider application or a separate one. Some strategies for managing state include: -- Having endpoints in your application that are not active in production that create and delete your datastore state -- A separate application that has access to the same datastore to create and delete, like a separate App Engine module or Docker container pointing to the same datastore -- A standalone application that can start and stop the other server with different datastore states +- Having endpoints in your application that are not active in production that create and delete your datastore state +- A separate application that has access to the same datastore to create and delete, like a separate App Engine module or Docker container pointing to the same datastore +- A standalone application that can start and stop the other server with different datastore states For more information about provider states, refer to the [Pact documentation] on [Provider States]. # Development + + Please read [CONTRIBUTING.md](https://github.com/pact-foundation/pact-python/blob/master/CONTRIBUTING.md) To setup a development environment: @@ -533,21 +491,18 @@ To setup a development environment: 1. If you want to run tests for all Python versions, install 2.7, 3.3, 3.4, 3.5, and 3.6 from source or using a tool like [pyenv] 2. Its recommended to create a Python [virtualenv] for the project -To setup the environment, run tests, and package the application, run: -`make release` +To setup the environment, run tests, and package the application, run: `make release` -If you are just interested in packaging pact-python so you can install it using pip: +If you are just interested in packaging pact-python so you can install it using pip: `make package` -`make package` - -This creates a `dist/pact-python-N.N.N.tar.gz` file, where the Ns are the current version. -From there you can use pip to install it: +This creates a `dist/pact-python-N.N.N.tar.gz` file, where the Ns are the current version. From there you can use pip to install it: `pip install ./dist/pact-python-N.N.N.tar.gz` ## Offline Installation of Standalone Packages Although all Ruby standalone applications are predownloaded into the wheel artifact, it may be useful, for development, purposes to install custom Ruby binaries. In which case, use the `bin-path` flag. + ``` pip install pact-python --bin-path=/absolute/path/to/folder/containing/pact/binaries/for/your/os ``` @@ -568,8 +523,8 @@ Join us in slack: [![slack](https://slack.pact.io/badge.svg)](https://slack.pact or -- Twitter: [@pact_up](https://twitter.com/pact_up) -- Stack Overflow: [stackoverflow.com/questions/tagged/pact](https://stackoverflow.com/questions/tagged/pact) +- Twitter: [@pact_up](https://twitter.com/pact_up) +- Stack Overflow: [stackoverflow.com/questions/tagged/pact](https://stackoverflow.com/questions/tagged/pact) [bundler]: http://bundler.io/ [context manager]: https://en.wikibooks.org/wiki/Python_Programming/Context_Managers diff --git a/RELEASING.md b/RELEASING.md index f51cb722e..43b72f594 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,59 +2,53 @@ ## Preparing the release -The easiest way is to just run the following command from the root folder with -the HEAD commit on trunk and the appropriate version. We follow -`..` versioning. +The easiest way is to just run the following command from the root folder with the HEAD commit on trunk and the appropriate version. We follow `..` versioning. - ```shell - $ script/release_prep.sh X.Y.Z - ``` +```shell +$ script/release_prep.sh X.Y.Z +``` This script effectively runs the following: -1. Increment the version according to semantic versioning rules in `pact/__version__.py` +1. Increment the version according to semantic versioning rules in `pact/__version__.py` -2. Update the `CHANGELOG.md` using: - ```shell - $ git log --pretty=format:' * %h - %s (%an, %ad)' vX.Y.Z..HEAD - ``` +2. Update the `CHANGELOG.md` using: -3. Add files to git - ```shell - $ git add CHANGELOG.md pact/__version__.py - ``` + ```shell + $ git log --pretty=format:' * %h - %s (%an, %ad)' vX.Y.Z..HEAD + ``` -4. Commit - ```shell - $ git commit -m "Releasing version X.Y.Z" - ``` +3. Add files to git -5. Tag - ```shell - $ git tag -a vX.Y.Z -m "Releasing version X.Y.Z" - $ git push origin master --tags - ``` + ```shell + $ git add CHANGELOG.md pact/__version__.py + ``` + +4. Commit + + ```shell + $ git commit -m "Releasing version X.Y.Z" + ``` + +5. Tag + + ```shell + $ git tag -a vX.Y.Z -m "Releasing version X.Y.Z" + $ git push origin master --tags + ``` ## Updating Pact Ruby -To upgrade the versions of `pact-mock_service` and `pact-provider-verifier`, change the -`PACT_STANDALONE_VERSION` in `setup.py` to match the latest version available from the -[pact-ruby-standalone](https://github.com/pact-foundation/pact-ruby-standalone/releases) -repository. Do this before preparing the release. +To upgrade the versions of `pact-mock_service` and `pact-provider-verifier`, change the `PACT_STANDALONE_VERSION` in `setup.py` to match the latest version available from the [pact-ruby-standalone](https://github.com/pact-foundation/pact-ruby-standalone/releases) repository. Do this before preparing the release. ## Publishing to pypi -1. Wait until GitHub Actions have run and the new tag is available at - https://github.com/pact-foundation/pact-python/releases/tag/vX.Y.Z +1. Wait until GitHub Actions have run and the new tag is available at https://github.com/pact-foundation/pact-python/releases/tag/vX.Y.Z -2. Set the title to `pact-python-X.Y.Z` +2. Set the title to `pact-python-X.Y.Z` -3. Save +3. Save -4. Go to GitHub Actions for Pact Python and you should see an 'Upload Python - Package' action blocked for your version. +4. Go to GitHub Actions for Pact Python and you should see an 'Upload Python Package' action blocked for your version. -5. Click this and then 'Review deployments'. Select 'Upload Python Package' - and Approve deploy. If you can't do this you may need an administrator to - give you permissions or do it for you. You should see in Slack #pact-python - that the release has happened. Verify in [pypi](https://pypi.org/project/pact-python/) +5. Click this and then 'Review deployments'. Select 'Upload Python Package' and Approve deploy. If you can't do this you may need an administrator to give you permissions or do it for you. You should see in Slack #pact-python that the release has happened. Verify in [pypi](https://pypi.org/project/pact-python/) diff --git a/docker/README.md b/docker/README.md index 40c8ca230..50ddfe74c 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,27 +1,20 @@ # Introduction -This is for contributors who want to make changes and test for all different -versions of python currently supported. If you don't want to set up and install -all the different python versions locally (and there are some difficulties with -that) you can just run them in docker using containers. +This is for contributors who want to make changes and test for all different versions of python currently supported. If you don't want to set up and install all the different python versions locally (and there are some difficulties with that) you can just run them in docker using containers. # Setup -To build a container say for Python 3.11, change to the root directory of the -project and run: +To build a container say for Python 3.11, change to the root directory of the project and run: ```bash (export PY=3.11 && docker build --build-arg PY="$PY" --build-arg TOXPY="$(sed 's/\.//' <<< "$PY")" -t pactfoundation:python${PY} -f docker/Dockerfile .) ``` -This uses an Alpine based image (currently 3.17), which is available as of -2023-04 for Python versions 3.7 - 3.11. +This uses an Alpine based image (currently 3.17), which is available as of 2023-04 for Python versions 3.7 - 3.11. -Note: To run tox, the Python version without the '.' is required, i.e. '311' -instead of '3.11', so some manipulation with `sed` is used to remove the '.' +Note: To run tox, the Python version without the '.' is required, i.e. '311' instead of '3.11', so some manipulation with `sed` is used to remove the '.' -To build for Python versions which require a different Alpine image, such as if -trying to build against Python 3.6, an extra `ALPINE` arg can be provided: +To build for Python versions which require a different Alpine image, such as if trying to build against Python 3.6, an extra `ALPINE` arg can be provided: ```bash (export PY=3.6 && docker build --build-arg PY="$PY" --build-arg TOXPY="$(sed 's/\.//' <<< "$PY")" --build-arg ALPINE=3.15 -t pactfoundation:python${PY} -f docker/Dockerfile .) @@ -39,16 +32,13 @@ If you need to debug you can change the command to: docker run -it --rm -v "$(pwd)":/home pactfoundation:python3.11 sh ``` -This will open a container with a prompt. From the `/home` location in the -container you can run the same tests manually: +This will open a container with a prompt. From the `/home` location in the container you can run the same tests manually: ```bash tox -e py311-{test,install} ``` -In all the above if you need to run a different version change -`py311`/`python3.11` where appropriate. Or you can run the convenience script -to build: +In all the above if you need to run a different version change `py311`/`python3.11` where appropriate. Or you can run the convenience script to build: ```bash docker/build.sh 3.11 diff --git a/examples/README.md b/examples/README.md index 4520e8fb7..4a91ca5f5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,30 +1,20 @@ # Examples -This directory contains an end-to-end example of using Pact in Python. While -this document and the documentation within the examples themselves are intended -to be mostly self-contained, it is highly recommended that you read the [Pact -Documentation](https://docs.pact.io/) as well. +This directory contains an end-to-end example of using Pact in Python. While this document and the documentation within the examples themselves are intended to be mostly self-contained, it is highly recommended that you read the [Pact Documentation](https://docs.pact.io/) as well. -Assuming you have [hatch](https://hatch.pypa.io/latest/) installed, the example -suite can be executed with: +Assuming you have [hatch](https://hatch.pypa.io/latest/) installed, the example suite can be executed with: ```sh hatch run example ``` -The code within the examples is intended to be well documented and you are -encouraged to look through the code as well (or submit a PR if anything is -unclear!). +The code within the examples is intended to be well documented and you are encouraged to look through the code as well (or submit a PR if anything is unclear!). ## Overview -Pact is a contract testing tool. Contract testing is a way to ensure that -services (such as an API provider and a client) can communicate with each other. -This example focuses on HTTP interactions, but Pact can be used to test more -general interactions as well such as through message queues. +Pact is a contract testing tool. Contract testing is a way to ensure that services (such as an API provider and a client) can communicate with each other. This example focuses on HTTP interactions, but Pact can be used to test more general interactions as well such as through message queues. -An interaction between a HTTP client (the _consumer_) and a server (the -_provider_) would typically look like this: +An interaction between a HTTP client (the _consumer_) and a server (the _provider_) would typically look like this:
@@ -40,11 +30,7 @@ sequenceDiagram
-To test this interaction naively would require both the consumer and provider to -be running at the same time. While this is straightforward in the above example, -this quickly becomes impractical as the number of interactions grows between -many microservices. Pact solves this by allowing the consumer and provider to be -tested independently. +To test this interaction naively would require both the consumer and provider to be running at the same time. While this is straightforward in the above example, this quickly becomes impractical as the number of interactions grows between many microservices. Pact solves this by allowing the consumer and provider to be tested independently. Pact achieves this be mocking the other side of the interaction: @@ -75,34 +61,20 @@ sequenceDiagram -In the first stage, the consumer defines a number of interactions in the form -below. Pact sets up a mock server that will respond to the requests as defined -by the consumer. All these interactions, containing both the request and -expected response, are all sent to the Pact Broker. +In the first stage, the consumer defines a number of interactions in the form below. Pact sets up a mock server that will respond to the requests as defined by the consumer. All these interactions, containing both the request and expected response, are all sent to the Pact Broker. > Given {provider state} \ > Upon receiving {description} \ > With {request} \ > Will respond with {response} -In the second stage, the provider retrieves the interactions from the Pact -Broker. It then sets up a mock client that will make the requests as defined by -the consumer. Pact then verifies that the responses from the provider match the -expected responses defined by the consumer. +In the second stage, the provider retrieves the interactions from the Pact Broker. It then sets up a mock client that will make the requests as defined by the consumer. Pact then verifies that the responses from the provider match the expected responses defined by the consumer. -In this way, Pact is consumer driven and can ensure that the provider is -compatible with the consumer. While this example showcases both sides in Python, -this is absolutely not required. The provider could be written in any language, -and satisfy contracts from a number of consumers all written in different -languages. +In this way, Pact is consumer driven and can ensure that the provider is compatible with the consumer. While this example showcases both sides in Python, this is absolutely not required. The provider could be written in any language, and satisfy contracts from a number of consumers all written in different languages. ### Consumer -The consumer in this example is a simple Python script that makes a HTTP GET -request to a server. It is defined in [`src/consumer.py`](src/consumer.py). The -tests for the consumer are defined in -[`tests/test_00_consumer.py`](tests/test_00_consumer.py). Each interaction is -defined using the format mentioned above. Programmatically, this looks like: +The consumer in this example is a simple Python script that makes a HTTP GET request to a server. It is defined in [`src/consumer.py`](src/consumer.py). The tests for the consumer are defined in [`tests/test_00_consumer.py`](tests/test_00_consumer.py). Each interaction is defined using the format mentioned above. Programmatically, this looks like: ```py expected: dict[str, Any] = { @@ -121,17 +93,9 @@ expected: dict[str, Any] = { ### Provider -This example showcases to different providers, one written in Flask and one -written in FastAPI. Both are simple Python web servers that respond to a HTTP -GET request. The Flask provider is defined in [`src/flask.py`](src/flask.py) and -the FastAPI provider is defined in [`src/fastapi.py`](src/fastapi.py). The -tests for the providers are defined in -[`tests/test_01_provider_flask.py`](tests/test_01_provider_flask.py) and -[`tests/test_01_provider_fastapi.py`](tests/test_01_provider_fastapi.py). +This example showcases to different providers, one written in Flask and one written in FastAPI. Both are simple Python web servers that respond to a HTTP GET request. The Flask provider is defined in [`src/flask.py`](src/flask.py) and the FastAPI provider is defined in [`src/fastapi.py`](src/fastapi.py). The tests for the providers are defined in [`tests/test_01_provider_flask.py`](tests/test_01_provider_flask.py) and [`tests/test_01_provider_fastapi.py`](tests/test_01_provider_fastapi.py). -Unlike the consumer side, the provider side is responsible to responding to the -interactions defined by the consumers. In this regard, the provider testing -is rather simple: +Unlike the consumer side, the provider side is responsible to responding to the interactions defined by the consumers. In this regard, the provider testing is rather simple: ```py code, _ = verifier.verify_with_broker( @@ -142,21 +106,8 @@ code, _ = verifier.verify_with_broker( assert code == 0 ``` -The complication comes from the fact that the provider needs to know what state -to be in before responding to the request. In order to achieve this, a testing -endpoint is defined that sets the state of the provider as defined in the -`provider_states_setup_url` above. For example, the consumer requests has _Given -user 123 exists_ as the provider state, and the provider will need to ensure -that this state is satisfied. This would typically entail setting up a database -with the correct data, but it is advisable to achieve the equivalent state by -mocking the appropriate calls. This has been showcased in both provider -examples. +The complication comes from the fact that the provider needs to know what state to be in before responding to the request. In order to achieve this, a testing endpoint is defined that sets the state of the provider as defined in the `provider_states_setup_url` above. For example, the consumer requests has _Given user 123 exists_ as the provider state, and the provider will need to ensure that this state is satisfied. This would typically entail setting up a database with the correct data, but it is advisable to achieve the equivalent state by mocking the appropriate calls. This has been showcased in both provider examples. ### Broker -The broker acts as the intermediary between these test suites. It stores the -interactions defined by the consumer and makes them available to the provider. -Once the provider has verified that it satisfies all interactions, the broker -also stores the verification results. The example here runs the open source -broker within a Docker container. An alternative is to use the hosted [Pactflow -service](https://pactflow.io). +The broker acts as the intermediary between these test suites. It stores the interactions defined by the consumer and makes them available to the provider. Once the provider has verified that it satisfies all interactions, the broker also stores the verification results. The example here runs the open source broker within a Docker container. An alternative is to use the hosted [Pactflow service](https://pactflow.io). diff --git a/run-docker.sh b/run-docker.sh index 9bbf49a05..004f725b1 100755 --- a/run-docker.sh +++ b/run-docker.sh @@ -1,7 +1,7 @@ #!/bin/bash for arch in arm64 amd64; do - # for version in 3.6; do + # for version in 3.6; do for version in 3.7 3.8 3.9 3.10 3.11; do docker build -t python-$arch-$version --build-arg PYTHON_VERSION=$version --platform=linux/$arch . docker run -it --rm python-$arch-$version diff --git a/script/commit_message.py b/script/commit_message.py index dec30d1ee..840443536 100755 --- a/script/commit_message.py +++ b/script/commit_message.py @@ -1,7 +1,8 @@ #!/usr/bin/env python +# ruff: noqa import re -import sys import subprocess +import sys examples = """+ 61c8ca9 fix: navbar not responsive on mobile + 479c48b test: prepared test cases for user authentication @@ -13,21 +14,21 @@ def main(): - cmd_tag = "git describe --abbrev=0" - tag = subprocess.check_output(cmd_tag, - shell=True).decode("utf-8").split('\n')[0] + tag = subprocess.check_output(cmd_tag, shell=True).decode("utf-8").split("\n")[0] - cmd = "git log --pretty=format:'%s' {}..HEAD".format(tag) + cmd = f"git log --pretty=format:'%s' {tag}..HEAD" commits = subprocess.check_output(cmd, shell=True) - commits = commits.decode("utf-8").split('\n') + commits = commits.decode("utf-8").split("\n") for commit in commits: - - pattern = r'((build|ci|docs|feat|fix|perf|refactor|style|test|chore|revert)(\([\w\-]+\))?:\s.*)|((Merge|Fixed)(\([\w\-]+\))?\s.*)' # noqa + pattern = r"((build|ci|docs|feat|fix|perf|refactor|style|test|chore|revert)(\([\w\-]+\))?:\s.*)|((Merge|Fixed)(\([\w\-]+\))?\s.*)" m = re.match(pattern, commit) if m is None: - print("\nError with git message '{}' style".format(commit)) - print("\nPlease change commit message to the conventional format and try to commit again. Examples:") # noqa + print(f"\nError with git message '{commit}' style") + print( + "\nPlease change commit message to the conventional format and try to" + " commit again. Examples:", + ) print("\n" + examples) sys.exit(1) diff --git a/script/release_prep.sh b/script/release_prep.sh index b6bf29ca2..d664b125b 100755 --- a/script/release_prep.sh +++ b/script/release_prep.sh @@ -28,5 +28,3 @@ git add CHANGELOG.md pact/__version__.py git commit -m "chore: Releasing version $VERSION" git tag -a "$TAG_NAME" -m "Releasing version $VERSION" && git push origin master --tags - -