pact-python

repository·main·Indexed 20 days ago

https://github.com/pact-foundation/pact-python

A contract testing library for Python (version 3.4.0) used for creating and verifying consumer-driven contracts using the Pact framework. It enables fast, reliable API and microservice testing by replacing end-to-end integration tests with consumer-driven contract tests, supporting various patterns including async aiohttp and Flask, synchronous requests and FastAPI, XML payloads, and dual-role services.

Tokens
29.7K
Snippets
82
Records
117
Agent score
71%

What's inside pact-python

  1. Overview of the aiohttp and Flask example components

    main

    The example is divided into four main functional areas:

    • Consumer: An async HTTP client implemented using aiohttp.
    • Provider: A web server implemented using Flask.
    • Consumer Tests: Contains the logic for contract definition and consumer-side testing.
    • Provider Tests: Contains the logic for provider verification against the defined contracts.

    This structure demonstrates a contract-driven development workflow where the consumer defines expectations and the provider verifies it can meet them.

  2. Overview of the requests and FastAPI example architecture

    main

    This example is structured into four main components to demonstrate a complete contract-driven development workflow:

    • Consumer: A synchronous HTTP client implemented using the requests library.
    • Provider: A web server implemented using FastAPI.
    • Consumer Tests: Where the contract is defined and the consumer is tested against a Pact mock server.
    • Provider Tests: Where the provider is verified against the consumer-generated contracts.

    This setup demonstrates how to handle different HTTP response scenarios (success, not found, etc.), manage provider state for different test scenarios, and maintain type safety using Python type hints.

  3. Overview of Pact Python

    main

    Pact Python is an API contract testing tool designed to replace brittle end-to-end integration tests with fast, reliable unit tests. It supports:

    • HTTP/REST and event-driven systems.
    • Configurable mock servers.
    • Powerful matching rules to prevent brittle tests.
    • CI/CD integration via Pact Broker or PactFlow.

    It is used to prevent breaking changes, document APIs, and reduce the cost and complexity of API integration testing.

  4. Service as Consumer and Provider Pattern Example

    main

    This example demonstrates a microservice architecture where a single service (user-service) acts as both a Provider and a Consumer in contract testing scenarios:

    1. As a Provider: It serves a frontend-web client. The frontend-web consumer tests define expectations for the user-service.
    2. As a Consumer: It calls an upstream auth-service. The user-service consumer tests define expectations for the auth-service.

    Key architectural patterns demonstrated:

    • Dual Contracts: One service managing two separate contracts in opposite directions.
    • Provider States: Using state handlers to model upstream dependency behavior (like the auth-service) during verification without requiring the actual service to be running.
    • Protocol-based Seams: Using Python Protocol to allow the real application (e.g., a FastAPI app) to run during provider verification while replacing upstream dependencies in-process.
  5. Explore HTTP-based contract testing examples

    main

    The examples/http/ directory provides several implementation patterns for HTTP-based contract testing using Pact Python. You can use these examples to understand how to integrate Pact with different Python web frameworks and HTTP clients:

    • Async patterns: Use aiohttp_and_flask/ to see an asynchronous aiohttp consumer interacting with a Flask provider.
    • Synchronous patterns: Use requests_and_fastapi/ to see a requests client interacting with a FastAPI provider.
    • Dual-role services: Use service_consumer_provider/ to see how a single service can be configured to act as both a consumer and a provider.
    • XML payloads: Use xml_example/ to see how to perform contract testing when using requests and FastAPI with XML bodies instead of JSON.
  6. Explore Pact Python examples

    main

    The examples/ directory contains self-contained scenarios. Each example uses its own pyproject.toml for dependency management, allowing you to run them independently.

    Available Example Categories

    • Patterns Catalog: Focused code snippets demonstrating specific Pact patterns (examples/catalog/).
    • HTTP Examples:
      • aiohttp client and Flask server (examples/http/aiohttp_and_flask/).
      • requests client and FastAPI server (examples/http/requests_and_fastapi/).
      • A single service acting as both Consumer and Provider (examples/http/service_consumer_provider/).
    • Message Examples: (Work in progress) examples/message/.
    • Plugin Examples: (Work in progress) examples/plugins/.
  7. What is the `pact.v3` module?

    main
    The pact.v3 module is a preview of the Pact Python v3 architecture. It provides full support for Pact Specifications v3 and v4. Unlike the legacy implementation, pact.v3 leverages Rust's foreign function interface (FFI) to provide enhanced performance and reliability, and it is designed to make incorporating upstream changes easier.
  8. Understand the Pact contract testing workflow

    main

    Pact is a consumer-driven contract testing tool that ensures services (consumers and providers) can communicate correctly.

    The Workflow

    1. Consumer Side: The consumer defines expected interactions (Given a state, Upon receiving a request, Will respond with a response). Consumer tests run against a Pact mock server that simulates the provider. Once tests pass, the generated contract is published to a Pact Broker.
    2. Provider Side: The provider retrieves contracts from the Pact Broker. Provider tests run against the actual provider implementation. Pact uses a mock client to replay the requests defined in the contract and verifies that the provider's responses match the expectations.
    3. Pact Broker: Acts as the central repository for storing contracts, verification results, and managing the compatibility matrix between consumers and providers.
  9. How Pact Python integrates with the Rust core library

    main

    Starting with Pact Python version 3, the library integrates with a Rust-based core library. This Rust core handles fundamental Pact operations including:

    • Parsing and serializing Pact files.
    • Matching requests with responses.
    • Generating new Pact contracts.
    • Providing mocking capabilities to simulate a provider during consumer verification.
    • Replaying consumer requests against a provider during provider verification.

    This integration uses a Python C Foreign Function Interface (CFFI) to bridge the gap between the Python interpreter and the high-performance Rust binary, allowing the Rust core to be imported and used as a standard Python module.

  10. Understand pact-python-cli versioning

    main

    The versioning of pact-python-cli is aligned with the Pact CLI versioning. The version number follows the pattern [Pact CLI version].[release index].

    For example, version 2.4.26.2 corresponds to Pact CLI version 2.4.26, where the .2 indicates that this is the third release of that specific Pact CLI version within the Python package (the first release being .0).

  11. Combine matchers and generators for robust contracts

    main

    For the most robust contracts, use matchers to validate incoming request data and generators to produce dynamic response data. This ensures you validate the correct structure while simulating varied, real-world API behavior.

    # Request validation with matchers
    request_body = {
        "email": match.regex("user@example.com", regex=r".+@.+\..+"),
        "age": match.int(25, min=18, max=100),
        "preferences": match.array_containing([match.str("notifications")]),
    }
    
    # Response generation with dynamic data
    response_body = {
        "id": generate.int(min=100000, max=999999),
        "email": match.str("user@example.com"),  # Echo back the input
        "verification_token": generate.uuid(),  # Fresh token each time
        "created_at": generate.datetime("%Y-%m-%dT%H:%M:%S%z"),
        "profile_url": generate.mock_server_url(
            example="/profiles/12345", regex=r"/profiles/\d+"
        ),
    }
  12. Handle errors from the Rust core library

    main

    Errors in the Rust core library are handled in two ways:

    1. Panics: Unrecoverable errors in Rust are caught by the library before they reach the Python interpreter, allowing them to be safely ignored without crashing the Python process.
    2. Return Codes: Since the C foreign function interface (FFI) cannot directly return Rust's Result type, the library uses integer return codes to indicate success or failure.

    Error Code Pattern

    • A return code of 0 typically indicates success.
    • Non-zero integers represent specific error types. These should be mapped to Python exceptions (like RuntimeError) to provide a Pythonic interface.

    Always check the integer return value of FFI calls to ensure the operation succeeded before proceeding.

    ret: int = lib.pactffi_write_pact_file(handle, path, overwrite)
    if ret == 0:
        return  # Success
    elif ret == 1:
        raise RuntimeError("The function panicked...")
    elif ret == 2:
        raise RuntimeError("The Pact file could not be written...")
    else:
        raise RuntimeError("An unknown error occurred...")