diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index aebbe9cf2..ff87b9d9d 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -12,6 +12,9 @@ on: # change this workflow's environment. - flake.nix - flake.lock + push: + tags: + - "payjoin-python-[0-9]*" jobs: build-python-and-test: @@ -29,3 +32,150 @@ jobs: uses: ./.github/actions/setup-nix - name: "Build and test" run: nix develop .#python --command bash ./payjoin-ffi/python/contrib/test.sh + + build-wheel: + # The wheel bundles a prebuilt native library, so each supported + # platform packs its own wheel: x86_64 on Linux (retagged manylinux by + # auditwheel) and a fat universal2 dylib for macOS. Both wheels build + # on a Linux host. The smoke jobs below verify each wheel on real hardware. + name: "Build wheel" + runs-on: ubuntu-26.04 + strategy: + fail-fast: false + matrix: + platform: [linux-x64, macos-universal2] + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: "Use cache" + uses: Swatinem/rust-cache@v2 + - name: Set up nix + uses: ./.github/actions/setup-nix + - name: Build the wheel + run: nix develop .#python --command bash ./payjoin-ffi/python/contrib/build-wheel.sh + env: + PAYJOIN_WHEEL_PLATFORM: ${{ matrix.platform }} + - name: Upload wheel + uses: actions/upload-artifact@v4 + with: + name: payjoin-python-wheel-${{ matrix.platform }} + path: payjoin-ffi/python/dist/*.whl + if-no-files-found: error + + smoke-wheel: + name: "Smoke test wheel" + runs-on: ${{ matrix.os }} + needs: build-wheel + strategy: + matrix: + os: [ubuntu-26.04, macos-latest, macos-15-intel] + steps: + - name: Download wheels + uses: actions/download-artifact@v4 + with: + pattern: payjoin-python-wheel-* + merge-multiple: true + path: dist + - name: Install Python + uses: actions/setup-python@v5 + with: + # Deliberately not the version the wheel was built with: the + # wheels are tagged py3-none (the bindings load the library + # through ctypes), and installing on a different CPython proves + # the retag. + python-version: "3.12" + - name: Install and exercise the wheel + shell: bash + run: | + set -euo pipefail + shopt -s nullglob + pkgs=(dist/payjoin-*.whl) + # download-artifact succeeds even when the pattern matches nothing. + if [ "${#pkgs[@]}" -eq 0 ]; then + echo "::error::no payjoin wheels found in dist/" + exit 1 + fi + version="$(basename "${pkgs[0]}" | cut -d- -f2)" + # pip picks whichever wheel matches this runner's platform; + # dependencies come from PyPI. + python -m pip install --only-binary payjoin --find-links dist "payjoin==$version" + python -c ' + import payjoin + payjoin.Url.parse("bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com") + print("smoke ok") + ' + + verify-tag: + name: "Verify release tag" + if: startsWith(github.ref, 'refs/tags/payjoin-python-') + permissions: + contents: read + uses: ./.github/workflows/verify-tag-hygiene.yml + + publish-pypi: + name: "Publish to PyPI (trusted publishing / OIDC)" + runs-on: ubuntu-26.04 + needs: [build-python-and-test, build-wheel, smoke-wheel, verify-tag] + if: startsWith(github.ref, 'refs/tags/payjoin-python-') + environment: release + permissions: + id-token: write # OIDC: PyPI trusted publishing and its PEP 740 attestations + attestations: write # actions/attest-build-provenance writes the attestation + contents: read # needed only to check out the in-repo verify-tag-version action + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Download wheels + uses: actions/download-artifact@v4 + with: + pattern: payjoin-python-wheel-* + merge-multiple: true + path: dist + + - name: Locate packed artifacts + id: locate + shell: bash + run: | + set -euo pipefail + shopt -s nullglob + pkgs=(dist/payjoin-*.whl) + if [ "${#pkgs[@]}" -ne 2 ]; then + echo "::error::expected exactly two wheels in dist/, found ${#pkgs[@]}: ${pkgs[*]:-none}" + exit 1 + fi + # payjoin-0.24.0-py3-none-.whl -> 0.24.0 + versions="$(for pkg in "${pkgs[@]}"; do basename "$pkg" | cut -d- -f2; done | sort -u)" + if [ "$(echo "$versions" | wc -l)" -ne 1 ]; then + echo "::error::wheels disagree on version: $versions; refusing to publish" + exit 1 + fi + echo "version=$versions" >> "$GITHUB_OUTPUT" + + - name: Verify tag matches packed artifact version + uses: ./.github/actions/verify-tag-version + with: + tag-prefix: payjoin-python- + version: ${{ steps.locate.outputs.version }} + + - name: Attest build provenance (wheels) + # A consumer runs: gh attestation verify .whl -R payjoin/rust-payjoin + uses: actions/attest-build-provenance@v4 + with: + subject-path: dist/*.whl + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist + + github-release: + name: "Attach wheels + SHA256SUMS to the GitHub release" + needs: [publish-pypi] + if: startsWith(github.ref, 'refs/tags/payjoin-python-') + permissions: + contents: write # create/update the Release for this tag and upload assets + uses: ./.github/workflows/release-assets.yml + with: + artifact-pattern: payjoin-python-wheel-* + tag-prefix: payjoin-python- diff --git a/flake.nix b/flake.nix index 7d86c760e..a24aa0953 100644 --- a/flake.nix +++ b/flake.nix @@ -397,14 +397,16 @@ "rustfmt" "llvm-tools-preview" ]; - targets = - pkgs.lib.optionals pkgs.stdenv.isDarwin [ - "aarch64-apple-darwin" - "x86_64-apple-darwin" - ] - ++ pkgs.lib.optionals pkgs.stdenv.isLinux [ - "x86_64-unknown-linux-gnu" - ]; + targets = [ + # On Darwin the native test builds lipo both arches; on Linux + # cargo-zigbuild cross-links the release wheel's universal2 + # dylib from these same targets. + "aarch64-apple-darwin" + "x86_64-apple-darwin" + ] + ++ pkgs.lib.optionals pkgs.stdenv.isLinux [ + "x86_64-unknown-linux-gnu" + ]; }; pythonDevShell = pkgs.mkShell { @@ -416,11 +418,20 @@ uv pythonRustToolchain bzip2 # needed for some machines to have access to libzip at runtime + # Provides the wheel CLI; the uv2nix venv omits build backends. + python3Packages.wheel ] ++ lib.optionals pkgs.stdenv.isLinux [ pkg-config openssl clang + # Applies the manylinux platform tag the release wheel satisfies; + # PyPI rejects raw linux_x86_64 wheels. + auditwheel + # Cross-links the macOS release wheel against zig's bundled + # Apple SDK stubs, so the dylib records system install names + # instead of nix store paths. + cargo-zigbuild ]; env = { diff --git a/payjoin-ffi/python/RELEASING.md b/payjoin-ffi/python/RELEASING.md new file mode 100644 index 000000000..839261268 --- /dev/null +++ b/payjoin-ffi/python/RELEASING.md @@ -0,0 +1,61 @@ +# Releasing the payjoin Python package + +Maintainer documentation for publishing the `payjoin` package to +[PyPI](https://pypi.org/project/payjoin/). Consumer documentation lives in +[`README.md`](README.md). + +## Versioning + +- The package version is the `payjoin-ffi` crate version: `setup.py` reads + it from `payjoin-ffi/Cargo.toml` at build time, so a release always + requires the crate version to be correct first. +- There is no separate Python version to maintain: the publish job derives + the version from the built wheels and refuses to publish if it does not + match the pushed tag. + +## Producing the wheels + +CI is the release path. On every pull request touching `payjoin-ffi/**`, +the `Build and Test Python` workflow builds release wheels with +[`contrib/build-wheel.sh`](contrib/build-wheel.sh) (release profile, no +`_test-utils`) and smoke-installs them on every supported platform: + +- `manylinux` x86_64, tagged by auditwheel with the glibc floor the binary + actually satisfies; +- macOS `universal2` (a fat x86_64 + arm64 dylib), cross-compiled from the + Linux host with cargo-zigbuild, so the dylib links the Apple SDK stubs + zig bundles and records system install names rather than nix store paths. + +The wheels are tagged `py3-none` because the generated bindings load the +bundled library through `ctypes` and do not depend on a CPython ABI; any +CPython satisfying `requires-python` can install them. + +## Publishing + +1. Confirm every `Build and Test Python` job is green on the release + commit in `master`, including the per-platform smoke tests. +2. Tag that commit `payjoin-python-`, where `` is the + `payjoin-ffi` crate version exactly. The tag must be annotated and + signed by a maintainer key in `contrib/release/keys/`, and the tagged + commit must be on `master`; `verify-tag` refuses to publish otherwise. + + ```shell + git tag -s payjoin-python-0.24.0 -m payjoin-python-0.24.0 + git push upstream payjoin-python-0.24.0 + ``` + + The tag reruns the full build/wheel/smoke graph at the tagged commit, + then `publish-pypi` verifies the tag matches the built wheels, attests + build provenance, and uploads through PyPI + [trusted publishing](https://docs.pypi.org/trusted-publishers/) (OIDC) + with PEP 740 attestations, so no long-lived token is stored anywhere. + The job runs in the `release` environment: approve the paused run before + anything reaches the registry. + +3. `github-release` attaches the wheels and a generated `SHA256SUMS` to + the tag's GitHub release. Optionally sign `SHA256SUMS` locally and + upload `SHA256SUMS.asc`. +4. Verify the publication: the PyPI listing shows the new version, + `pip install payjoin==` resolves on a supported platform, and + `gh attestation verify -R payjoin/rust-payjoin` passes against + a release asset. diff --git a/payjoin-ffi/python/contrib/build-wheel.sh b/payjoin-ffi/python/contrib/build-wheel.sh new file mode 100755 index 000000000..2f2ffd476 --- /dev/null +++ b/payjoin-ffi/python/contrib/build-wheel.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Build the production wheel for one platform into dist/: release profile, +# no test utils. Select the platform with PAYJOIN_WHEEL_PLATFORM +# (linux-x64, the default, or macos-universal2). +# +# Both wheels build on a Linux host: a dylib linked inside the nix dev shell +# on a mac records nix store install names that exist on no user machine, +# while cargo-zigbuild links against zig's bundled Apple SDK stubs and records +# the system ones. + +if [[ "$(uname -s)" != Linux ]]; then + echo "error: release wheels build on a Linux host only" >&2 + exit 1 +fi + +PLATFORM=${PAYJOIN_WHEEL_PLATFORM:-linux-x64} + +# Build against the maintained lockfile instead of resolving the dependency +# graph fresh on every run. use_lockfile copies Cargo-recent.lock into place +# and restores the previous state when this script exits. +REPO_ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +cd "$REPO_ROOT" +source contrib/lockfile.sh +use_lockfile Cargo-recent.lock + +cd "$REPO_ROOT/payjoin-ffi/python" + +echo "==> Generating production FFI bindings..." +PAYJOIN_FFI_FEATURES="" PAYJOIN_FFI_PROFILE=release bash ./scripts/generate_bindings.sh + +# generate_bindings.sh stages the host's library; make sure the wheel +# ships exactly one platform's binary. +case "$PLATFORM" in + linux-x64) + rm -f src/payjoin/libpayjoin_ffi.dylib + ;; + macos-universal2) + echo "==> Cross-compiling the universal2 macOS library..." + (cd "$REPO_ROOT/payjoin-ffi" && + cargo zigbuild --profile release --target universal2-apple-darwin) + rm -f src/payjoin/libpayjoin_ffi.so + cp "$REPO_ROOT/target/universal2-apple-darwin/release/libpayjoin_ffi.dylib" \ + src/payjoin/ + ;; + *) + echo "error: unsupported PAYJOIN_WHEEL_PLATFORM: $PLATFORM" >&2 + exit 1 + ;; +esac + +echo "==> Building the wheel..." +# Drop setuptools' build/ staging dir too: it survives across runs and +# would leak the other platform's library into this wheel. +rm -rf build dist +uv build --wheel + +# The generated bindings load the bundled library through ctypes, so the +# wheel does not depend on a CPython ABI; setup.py tags it with the +# building interpreter's version only because has_ext_modules marks the +# wheel platform-specific. +echo "==> Retagging the wheel..." +wheel tags --python-tag py3 --abi-tag none --remove dist/*.whl + +if [[ $PLATFORM == linux-x64 ]]; then + # PyPI rejects the raw linux_x86_64 platform tag. auditwheel verifies + # the library's external dependencies and applies the manylinux tag the + # binary actually satisfies; nothing is grafted into the wheel since + # the library links only glibc. + auditwheel repair --wheel-dir dist dist/*-linux_x86_64.whl + rm dist/*-linux_x86_64.whl +else + # setup.py tagged the wheel with the build machine's (Linux) platform. + # The macOS floor is zig's: its bundled SDK stubs currently support + # macOS >= 13, and that is the minos both slices record. Keep this tag + # in sync with the LC_BUILD_VERSION of the built dylib if zig moves. + wheel tags --platform-tag macosx_13_0_universal2 --remove dist/*.whl +fi + +echo "==> Built wheel:" +ls -l dist diff --git a/payjoin-ffi/python/contrib/test.sh b/payjoin-ffi/python/contrib/test.sh index ac43385d0..03ebd007c 100755 --- a/payjoin-ffi/python/contrib/test.sh +++ b/payjoin-ffi/python/contrib/test.sh @@ -18,6 +18,7 @@ echo "==> Generating FFI bindings..." bash ./scripts/generate_bindings.sh echo "==> Building wheel..." +rm -rf dist uv build --wheel echo "==> Installing wheel..." diff --git a/payjoin-ffi/python/pyproject.toml b/payjoin-ffi/python/pyproject.toml index d3383ec09..8ac9a8b63 100644 --- a/payjoin-ffi/python/pyproject.toml +++ b/payjoin-ffi/python/pyproject.toml @@ -9,14 +9,7 @@ readme = "README.md" requires-python = ">=3.10" license = "MIT" dynamic = ["version"] -dependencies = [ - "build==1.3.0", - "semantic-version==2.9.0", - "setuptools==83.0.0", - "typing-extensions==4.0.1", - "wheel==0.46.3", - "httpx==0.28.1", -] +dependencies = ["httpx>=0.28.1,<1.0"] [tool.setuptools] packages = ["payjoin"] diff --git a/payjoin-ffi/python/scripts/generate_bindings.sh b/payjoin-ffi/python/scripts/generate_bindings.sh index 04253d5f2..d1c431c5c 100644 --- a/payjoin-ffi/python/scripts/generate_bindings.sh +++ b/payjoin-ffi/python/scripts/generate_bindings.sh @@ -26,32 +26,46 @@ ensure_target() { fi } +PAYJOIN_FFI_FEATURES=${PAYJOIN_FFI_FEATURES-_test-utils} +PAYJOIN_FFI_PROFILE=${PAYJOIN_FFI_PROFILE:-dev} +if [[ $PAYJOIN_FFI_PROFILE == "dev" ]]; then + TARGET_PROFILE_DIR=debug +else + TARGET_PROFILE_DIR=$PAYJOIN_FFI_PROFILE +fi +FEATURE_ARGS=() +if [[ -n $PAYJOIN_FFI_FEATURES ]]; then + FEATURE_ARGS=(--features "$PAYJOIN_FFI_FEATURES") +fi + cd ../ -# This is a test script the actual release should not include the test utils feature -cargo build --features _test-utils --profile dev -cargo run --features _test-utils --profile dev --bin uniffi-bindgen generate --library ../target/debug/$LIBNAME --language python --out-dir python/src/payjoin/ +cargo build "${FEATURE_ARGS[@]}" --profile "$PAYJOIN_FFI_PROFILE" +cargo run "${FEATURE_ARGS[@]}" --profile dev --bin uniffi-bindgen generate --library "../target/$TARGET_PROFILE_DIR/$LIBNAME" --language python --out-dir python/src/payjoin/ if [[ $OS == "Darwin" ]]; then echo "Generating native binaries..." ensure_target aarch64-apple-darwin x86_64-apple-darwin - # This is a test script the actual release should not include the test utils feature - cargo build --profile dev --target aarch64-apple-darwin --features _test-utils & - cargo build --profile dev --target x86_64-apple-darwin --features _test-utils & - wait + cargo build --profile "$PAYJOIN_FFI_PROFILE" --target aarch64-apple-darwin "${FEATURE_ARGS[@]}" & + aarch64_pid=$! + cargo build --profile "$PAYJOIN_FFI_PROFILE" --target x86_64-apple-darwin "${FEATURE_ARGS[@]}" & + x86_64_pid=$! + # wait on each build so a failure cannot slip through to lipo, + # which would happily reuse a stale library from an earlier build. + wait "$aarch64_pid" + wait "$x86_64_pid" echo "Building macos fat library" lipo -create -output python/src/payjoin/$LIBNAME \ - ../target/aarch64-apple-darwin/debug/$LIBNAME \ - ../target/x86_64-apple-darwin/debug/$LIBNAME + "../target/aarch64-apple-darwin/$TARGET_PROFILE_DIR/$LIBNAME" \ + "../target/x86_64-apple-darwin/$TARGET_PROFILE_DIR/$LIBNAME" else echo "Generating native binaries..." ensure_target x86_64-unknown-linux-gnu - # This is a test script the actual release should not include the test utils feature - cargo build --profile dev --target x86_64-unknown-linux-gnu --features _test-utils + cargo build --profile "$PAYJOIN_FFI_PROFILE" --target x86_64-unknown-linux-gnu "${FEATURE_ARGS[@]}" echo "Copying payjoin_ffi binary" - cp ../target/x86_64-unknown-linux-gnu/debug/$LIBNAME python/src/payjoin/$LIBNAME + cp "../target/x86_64-unknown-linux-gnu/$TARGET_PROFILE_DIR/$LIBNAME" python/src/payjoin/$LIBNAME fi echo "All done!" diff --git a/payjoin-ffi/python/uv.lock b/payjoin-ffi/python/uv.lock index 89888e3ee..591efc5b4 100644 --- a/payjoin-ffi/python/uv.lock +++ b/payjoin-ffi/python/uv.lock @@ -16,22 +16,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/85/4f/d010eca6914703d8e6be222165d02c3e708ed909cdb2b7af3743667f302e/anyio-4.1.0-py3-none-any.whl", hash = "sha256:56a415fbc462291813a94528a779597226619c8e78af7de0507333f700011e5f", size = 83924, upload-time = "2023-11-22T23:23:52.595Z" }, ] -[[package]] -name = "build" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "os_name == 'nt'" }, - { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" }, - { name = "packaging" }, - { name = "pyproject-hooks" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/25/1c/23e33405a7c9eac261dff640926b8b5adaed6a6eb3e1767d441ed611d0c0/build-1.3.0.tar.gz", hash = "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397", size = 48544, upload-time = "2025-08-01T21:27:09.268Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4", size = 23382, upload-time = "2025-08-01T21:27:07.844Z" }, -] - [[package]] name = "certifi" version = "2025.8.3" @@ -41,15 +25,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, ] -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - [[package]] name = "exceptiongroup" version = "1.2.2" @@ -105,37 +80,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, ] -[[package]] -name = "importlib-metadata" -version = "8.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, -] - -[[package]] -name = "packaging" -version = "25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, -] - [[package]] name = "payjoin" source = { editable = "." } dependencies = [ - { name = "build" }, { name = "httpx" }, - { name = "semantic-version" }, - { name = "setuptools" }, - { name = "typing-extensions" }, - { name = "wheel" }, ] [package.dev-dependencies] @@ -145,14 +94,7 @@ dev = [ ] [package.metadata] -requires-dist = [ - { name = "build", specifier = "==1.3.0" }, - { name = "httpx", specifier = "==0.28.1" }, - { name = "semantic-version", specifier = "==2.9.0" }, - { name = "setuptools", specifier = "==83.0.0" }, - { name = "typing-extensions", specifier = "==4.0.1" }, - { name = "wheel", specifier = "==0.46.3" }, -] +requires-dist = [{ name = "httpx", specifier = ">=0.28.1,<1.0" }] [package.metadata.requires-dev] dev = [ @@ -169,33 +111,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, ] -[[package]] -name = "pyproject-hooks" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, -] - -[[package]] -name = "semantic-version" -version = "2.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/56/4aa487b46d09646eb1863faa7026551d8309ece2281794bf13b20f28ab94/semantic_version-2.9.0.tar.gz", hash = "sha256:abf54873553e5e07a6fd4d5f653b781f5ae41297a493666b59dcf214006a12b2", size = 51123, upload-time = "2022-02-06T18:45:20.132Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/ac/df31047966c4d0293e7bd16276ebc9f6654de36ad8e19061a09369380c0a/semantic_version-2.9.0-py2.py3-none-any.whl", hash = "sha256:db2504ab37902dd2c9876ece53567aa43a5b2a417fbe188097b2048fff46da3d", size = 15357, upload-time = "2022-02-06T18:45:17.996Z" }, -] - -[[package]] -name = "setuptools" -version = "83.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, -] - [[package]] name = "sniffio" version = "1.3.1" @@ -253,27 +168,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] -[[package]] -name = "typing-extensions" -version = "4.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/4a/60ba3706797b878016f16edc5fbaf1e222109e38d0fa4d7d9312cb53f8dd/typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", size = 22706, upload-time = "2021-12-01T01:45:39.576Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/e4/baf0031e39cf545f0c9edd5b1a2ea12609b7fcba2d58e118b11753d68cf0/typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b", size = 22816, upload-time = "2021-12-01T01:45:37.772Z" }, -] - -[[package]] -name = "wheel" -version = "0.46.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/89/24/a2eb353a6edac9a0303977c4cb048134959dd2a51b48a269dfc9dde00c8a/wheel-0.46.3.tar.gz", hash = "sha256:e3e79874b07d776c40bd6033f8ddf76a7dad46a7b8aa1b2787a83083519a1803", size = 60605, upload-time = "2026-01-22T12:39:49.136Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/22/b76d483683216dde3d67cba61fb2444be8d5be289bf628c13fc0fd90e5f9/wheel-0.46.3-py3-none-any.whl", hash = "sha256:4b399d56c9d9338230118d705d9737a2a468ccca63d5e813e2a4fc7815d8bc4d", size = 30557, upload-time = "2026-01-22T12:39:48.099Z" }, -] - [[package]] name = "yapf" version = "0.43.0" @@ -286,12 +180,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25 wheels = [ { url = "https://files.pythonhosted.org/packages/37/81/6acd6601f61e31cfb8729d3da6d5df966f80f374b78eff83760714487338/yapf-0.43.0-py3-none-any.whl", hash = "sha256:224faffbc39c428cb095818cf6ef5511fdab6f7430a10783fdfb292ccf2852ca", size = 256158, upload-time = "2024-11-14T00:11:39.37Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -]