Skip to content

Commit 11f8517

Browse files
authored
feat(python): POWER-5106 publish Python SDK to PyPI via trusted publishing (#140)
Adds a PyPI publish step to the Python release workflow using OIDC trusted publishing (pypa/gh-action-pypi-publish), mirroring the NuGet setup from POWER-5121 — no stored API token, and the GitHub Release wheel upload is kept for backward compatibility. Renames the distribution to heimdallpower-api-client to match the pending publisher under the heimdallpower PyPI org (the import package heimdall_api_client is unchanged). Also fixes the x-client-version header to resolve the installed distribution version via importlib.metadata instead of a hardcoded 0.0.0.
1 parent 84c892c commit 11f8517

6 files changed

Lines changed: 65 additions & 5 deletions

File tree

.github/workflows/python-publish.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ jobs:
1414

1515
permissions:
1616
contents: write # Required to upload release artifacts
17+
id-token: write # Enable GitHub OIDC token issuance for PyPI Trusted Publishing
1718

1819
steps:
1920
- name: Checkout repository
@@ -48,6 +49,11 @@ jobs:
4849
- name: Build package
4950
run: poetry build
5051

52+
- name: Publish to PyPI
53+
uses: pypa/gh-action-pypi-publish@release/v1
54+
with:
55+
packages-dir: python/dist
56+
5157
- name: Upload dist artifacts to GitHub Release
5258
uses: softprops/action-gh-release@v2
5359
with:

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ ci: add conventional commits PR title validation
5151
- Contributions should be made as pull requests into `main`.
5252
- Releases are managed using GitHub Releases.
5353
- Tags must follow the format: `v<MAJOR>.<MINOR>.<PATCH>` (e.g., `v1.2.3`)
54-
- A GitHub Release must be published for the package to be built and pushed to NuGet
54+
- A GitHub Release must be published for the packages to be built and published: the .NET package is pushed to [NuGet](https://www.nuget.org/) and the Python package to [PyPI](https://pypi.org/project/heimdallpower-api-client/) (both via trusted publishing / OIDC, no stored API tokens)
5555
- The `v` prefix is automatically stripped when packaging
5656

5757
---

python/README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,16 @@ poetry install
1818

1919
### Installing the SDK
2020

21-
The package can be downloaded and installed using the GitHub release artifacts:
21+
The package is published to [PyPI](https://pypi.org/project/heimdallpower-api-client/):
2222

2323
```bash
24-
pip install https://github.com/heimdallpower/api-sdk/releases/download/v1.2.3/heimdall_api_client-1.2.3-py3-none-any.whl
24+
pip install heimdallpower-api-client
25+
```
26+
27+
Alternatively, it can be downloaded and installed using the GitHub release artifacts:
28+
29+
```bash
30+
pip install https://github.com/heimdallpower/api-sdk/releases/download/v1.2.3/heimdallpower_api_client-1.2.3-py3-none-any.whl
2531
```
2632

2733
> Replace the version and filename with the latest from [Releases](https://github.com/heimdallpower/api-sdk/releases)

python/heimdall_api_client/client.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import logging
55
import time
66
from collections.abc import Callable
7+
from importlib.metadata import PackageNotFoundError, version
78
from typing import TYPE_CHECKING, TypeVar
89
from uuid import UUID
910

@@ -58,6 +59,12 @@
5859

5960
_MAX_RETRY_ATTEMPTS = 3
6061

62+
try:
63+
_SDK_VERSION = version("heimdallpower-api-client")
64+
except PackageNotFoundError:
65+
# Running from source (e.g. a repo checkout) rather than an installed distribution
66+
_SDK_VERSION = "0.0.0"
67+
6168

6269
class HeimdallApiClient:
6370
"""
@@ -144,7 +151,7 @@ def __init__(
144151

145152
default_metadata = {
146153
"x-client-name": "python-sdk",
147-
"x-client-version": "0.0.0",
154+
"x-client-version": _SDK_VERSION,
148155
}
149156

150157
# Ensure user values override the defaults

python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[project]
2-
name = "heimdall-api-client"
2+
name = "heimdallpower-api-client"
33
version = "0.1.0"
44
description = "Python SDK for interacting with the Heimdall Power External API"
55
authors = [
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""
2+
Unit tests for the default client metadata headers sent by HeimdallApiClient.
3+
4+
Covers:
5+
- x-client-version reflects the installed distribution version (not a hardcoded value)
6+
- Fallback to "0.0.0" when the distribution is not installed (running from source)
7+
- User-supplied client_metadata overrides the defaults
8+
"""
9+
10+
from importlib.metadata import PackageNotFoundError
11+
12+
from heimdall_api_client.client import _SDK_VERSION, HeimdallApiClient
13+
14+
15+
def _make_client(**kwargs) -> HeimdallApiClient:
16+
return HeimdallApiClient(client_id="fake-id", client_secret="fake-secret", **kwargs)
17+
18+
19+
class TestClientMetadata:
20+
def test_default_metadata_uses_sdk_version(self):
21+
client = _make_client()
22+
23+
assert client.client_metadata["x-client-name"] == "python-sdk"
24+
assert client.client_metadata["x-client-version"] == _SDK_VERSION
25+
26+
def test_sdk_version_matches_installed_distribution(self):
27+
from importlib.metadata import version
28+
29+
try:
30+
expected = version("heimdallpower-api-client")
31+
except PackageNotFoundError:
32+
expected = "0.0.0"
33+
34+
assert _SDK_VERSION == expected
35+
36+
def test_user_metadata_overrides_defaults(self):
37+
client = _make_client(client_metadata={"x-client-version": "9.9.9", "x-custom": "abc"})
38+
39+
assert client.client_metadata["x-client-version"] == "9.9.9"
40+
assert client.client_metadata["x-custom"] == "abc"
41+
assert client.client_metadata["x-client-name"] == "python-sdk"

0 commit comments

Comments
 (0)