-
Notifications
You must be signed in to change notification settings - Fork 137
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: migrate to pyproject.toml and hatch
The previous build process relied on `distutils` which is due to be deprecated in 3.12. Furthermore, the use of `setup.py` is now discouraged. The choice to migrate to `hatch` was made for the following reasons: - It offers a very simple management of the venv. No more need to `python -m venv .venv` and `pip install`, `hatch` handles all of that automatically when creating the virtual environment. - `hatch` supercedes `tox`, allowing for multiple python versions to be tested in a single command. - `hatch` manages the build process, and offers a nicer way to hook in a custom build process to download the `pact` standalone binaries. A minor change to the packaging of the library now places the binaries in `pact/bin` instead of `pact/bin/pact/bin`. The `constants.py` file has been accordingly updated to reflect this change in case anyone was making direct use of the binaries. While this change is rather significant, it should not affect the end user experience. Users will still be able to `pip install pact-python` from PyPI. Other than for the aforementioned, there has been no changes to the library code. Resolves: #369 Refs: https://docs.python.org/3/whatsnew/3.10.html#distutils-deprecated Refs: https://setuptools.pypa.io/en/latest/userguide/quickstart.html#setuppy-discouraged Signed-off-by: JP-Ellis <josh@jpellis.me>
- Loading branch information
Showing
11 changed files
with
337 additions
and
463 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
"""Hatchling build hook for Pact binary download.""" | ||
|
||
from __future__ import annotations | ||
|
||
import os | ||
import shutil | ||
import typing | ||
from pathlib import Path | ||
from typing import Any | ||
|
||
from hatchling.builders.hooks.plugin.interface import BuildHookInterface | ||
from packaging.tags import sys_tags | ||
|
||
ROOT_DIR = Path(__file__).parent.resolve() | ||
PACT_VERSION = "2.0.3" | ||
PACT_URL = "https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v{version}/pact-{version}-{os}-{machine}.{ext}" | ||
PACT_DISTRIBUTIONS: list[tuple[str, str, str]] = [ | ||
("linux", "arm64", "tar.gz"), | ||
("linux", "x86_64", "tar.gz"), | ||
("osx", "arm64", "tar.gz"), | ||
("osx", "x86_64", "tar.gz"), | ||
("windows", "x86", "zip"), | ||
("windows", "x86_64", "zip"), | ||
] | ||
|
||
|
||
class PactBuildHook(BuildHookInterface): | ||
"""Custom hook to download Pact binaries.""" | ||
|
||
PLUGIN_NAME = "custom" | ||
|
||
def clean(self, versions: list[str]) -> None: # noqa: ARG002 | ||
"""Clean up any files created by the build hook.""" | ||
for subdir in ["bin", "lib", "data"]: | ||
shutil.rmtree(ROOT_DIR / "pact" / subdir, ignore_errors=True) | ||
|
||
def initialize( | ||
self, | ||
version: str, # noqa: ARG002 | ||
build_data: dict[str, Any], | ||
) -> None: | ||
"""Hook into Hatchling's build process.""" | ||
build_data["infer_tag"] = True | ||
build_data["pure_python"] = False | ||
|
||
pact_version = os.getenv("PACT_VERSION", PACT_VERSION) | ||
self.install_pact_binaries(pact_version) | ||
|
||
def install_pact_binaries(self, version: str) -> None: # noqa: PLR0912 | ||
""" | ||
Install the Pact standalone binaries. | ||
The binaries are installed in `pact/bin`, and the relevant version for | ||
the current operating system is determined automatically. | ||
Args: | ||
version: The Pact version to install. Defaults to the value in | ||
`PACT_VERSION`. | ||
""" | ||
platform = typing.cast(str, next(sys_tags()).platform) | ||
|
||
if platform.startswith("macosx"): | ||
os = "osx" | ||
if platform.endswith("arm64"): | ||
machine = "arm64" | ||
elif platform.endswith("x86_64"): | ||
machine = "x86_64" | ||
else: | ||
msg = f"Unknown macOS machine {platform}" | ||
raise ValueError(msg) | ||
url = PACT_URL.format(version=version, os=os, machine=machine, ext="tar.gz") | ||
|
||
elif platform.startswith("win"): | ||
os = "windows" | ||
|
||
if platform.endswith("amd64"): | ||
machine = "x86_64" | ||
elif platform.endswith("x86"): | ||
machine = "x86" | ||
else: | ||
msg = f"Unknown Windows machine {platform}" | ||
raise ValueError(msg) | ||
|
||
url = PACT_URL.format(version=version, os=os, machine=machine, ext="zip") | ||
|
||
elif "linux" in platform: | ||
os = "linux" | ||
if platform.endswith("x86_64"): | ||
machine = "x86_64" | ||
elif platform.endswith("aarch64"): | ||
machine = "arm64" | ||
else: | ||
msg = f"Unknown Linux machine {platform}" | ||
raise ValueError(msg) | ||
|
||
url = PACT_URL.format(version=version, os=os, machine=machine, ext="tar.gz") | ||
|
||
else: | ||
msg = f"Unknown platform {platform}" | ||
raise ValueError(msg) | ||
|
||
self.download_and_extract_pact(url) | ||
|
||
def download_and_extract_pact(self, url: str) -> None: | ||
""" | ||
Download and extract the Pact binaries. | ||
If the download artifact is already present, it will be used instead of | ||
downloading it again. | ||
Args: | ||
url: The URL to download the Pact binaries from. | ||
""" | ||
filename = url.split("/")[-1] | ||
artifact = ROOT_DIR / "pact" / "data" / filename | ||
artifact.parent.mkdir(parents=True, exist_ok=True) | ||
|
||
if not filename.endswith((".zip", ".tar.gz")): | ||
msg = f"Unknown artifact type {filename}" | ||
raise ValueError(msg) | ||
|
||
if not artifact.exists(): | ||
import requests | ||
|
||
response = requests.get(url, timeout=30) | ||
response.raise_for_status() | ||
with artifact.open("wb") as f: | ||
f.write(response.content) | ||
|
||
if filename.endswith(".zip"): | ||
import zipfile | ||
|
||
with zipfile.ZipFile(artifact) as f: | ||
f.extractall(ROOT_DIR) | ||
if filename.endswith(".tar.gz"): | ||
import tarfile | ||
|
||
with tarfile.open(artifact) as f: | ||
f.extractall(ROOT_DIR) | ||
|
||
# Move the README that is extracted from the Ruby standalone binaries to | ||
# the `data` subdirectory. | ||
if (ROOT_DIR / "pact" / "README.md").exists(): | ||
shutil.move( | ||
ROOT_DIR / "pact" / "README.md", | ||
ROOT_DIR / "pact" / "data" / "README.md", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,48 +1,37 @@ | ||
"""Constant values for the pact-python package.""" | ||
import os | ||
from os.path import join, dirname, normpath | ||
from pathlib import Path | ||
|
||
|
||
def broker_client_exe(): | ||
def broker_client_exe() -> str: | ||
"""Get the appropriate executable name for this platform.""" | ||
if os.name == 'nt': | ||
return 'pact-broker.bat' | ||
else: | ||
return 'pact-broker' | ||
if os.name == "nt": | ||
return "pact-broker.bat" | ||
return "pact-broker" | ||
|
||
|
||
def message_exe(): | ||
def message_exe() -> str: | ||
"""Get the appropriate executable name for this platform.""" | ||
if os.name == 'nt': | ||
return 'pact-message.bat' | ||
else: | ||
return 'pact-message' | ||
if os.name == "nt": | ||
return "pact-message.bat" | ||
return "pact-message" | ||
|
||
|
||
def mock_service_exe(): | ||
def mock_service_exe() -> str: | ||
"""Get the appropriate executable name for this platform.""" | ||
if os.name == 'nt': | ||
return 'pact-mock-service.bat' | ||
else: | ||
return 'pact-mock-service' | ||
if os.name == "nt": | ||
return "pact-mock-service.bat" | ||
return "pact-mock-service" | ||
|
||
|
||
def provider_verifier_exe(): | ||
def provider_verifier_exe() -> str: | ||
"""Get the appropriate provider executable name for this platform.""" | ||
if os.name == 'nt': | ||
return 'pact-provider-verifier.bat' | ||
else: | ||
return 'pact-provider-verifier' | ||
|
||
|
||
BROKER_CLIENT_PATH = normpath(join( | ||
dirname(__file__), 'bin', 'pact', 'bin', broker_client_exe())) | ||
|
||
MESSAGE_PATH = normpath(join( | ||
dirname(__file__), 'bin', 'pact', 'bin', message_exe())) | ||
|
||
MOCK_SERVICE_PATH = normpath(join( | ||
dirname(__file__), 'bin', 'pact', 'bin', mock_service_exe())) | ||
|
||
VERIFIER_PATH = normpath(join( | ||
dirname(__file__), 'bin', 'pact', 'bin', provider_verifier_exe())) | ||
if os.name == "nt": | ||
return "pact-provider-verifier.bat" | ||
return "pact-provider-verifier" | ||
|
||
ROOT_DIR = Path(__file__).parent.resolve() | ||
BROKER_CLIENT_PATH = ROOT_DIR / "bin" / broker_client_exe() | ||
MESSAGE_PATH = ROOT_DIR / "bin" / message_exe() | ||
MOCK_SERVICE_PATH = ROOT_DIR / "bin" / mock_service_exe() | ||
VERIFIER_PATH = ROOT_DIR / "bin" / provider_verifier_exe() |
Oops, something went wrong.