Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: CI
on:
workflow_dispatch:
push:
pull_request:

jobs:
mypy-test:
name: mypy test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.x"
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install mypy
- name: Run mypy test
run: mypy -p pyrympro
41 changes: 41 additions & 0 deletions .github/workflows/pr-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: PR Build

on:
pull_request:
types: [opened, synchronize, reopened]

concurrency:
group: pr-build-${{ github.event.pull_request.number }}
cancel-in-progress: true

permissions:
contents: read
actions: write

jobs:
build:
name: Build PR wheel
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.x"

- run: pip install build

- name: Build wheel
run: python -m build --wheel
env:
SETUPTOOLS_SCM_PRETEND_VERSION: 0.0.0.dev${{ github.event.pull_request.number }}

- name: Save PR metadata
run: |
echo "${{ github.event.pull_request.number }}" > dist/PR_NUMBER
echo "${{ github.event.pull_request.head.sha }}" > dist/PR_HEAD_SHA

- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: pr-wheel
path: dist/
30 changes: 30 additions & 0 deletions .github/workflows/pr-cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: PR Cleanup

on:
pull_request_target:
types: [closed]

permissions:
contents: write
issues: write

jobs:
cleanup:
name: Delete PR pre-release
runs-on: ubuntu-latest
env:
PR: ${{ github.event.pull_request.number }}
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
steps:
- name: Delete PR release and tag
run: gh release delete "pr-${PR}" --cleanup-tag --yes || true

- name: Delete PR comment
run: |
gh api "repos/${GH_REPO}/issues/${PR}/comments" \
--paginate \
--jq '.[] | select(.user.login == "github-actions[bot]") | select(.body | startswith("<!-- pr-test-build -->")) | .id' \
| while read -r ID; do
gh api "repos/${GH_REPO}/issues/comments/${ID}" -X DELETE
done
121 changes: 121 additions & 0 deletions .github/workflows/pr-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
name: PR Publish

on:
workflow_run:
workflows: ["PR Build"]
types: [completed]
workflow_dispatch:
inputs:
run_id:
description: 'PR Build run ID to publish (find it in the Actions URL)'
required: true

concurrency:
group: pr-publish-${{ github.event.workflow_run.pull_requests[0].number || github.event.inputs.run_id || github.run_id }}
cancel-in-progress: true

permissions:
contents: write
issues: write
pull-requests: write
actions: read

jobs:
publish:
name: Publish PR pre-release
runs-on: ubuntu-latest
if: >
github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success'
steps:
- name: Resolve run ID
id: run
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "id=${{ github.event.inputs.run_id }}" >> "$GITHUB_OUTPUT"
else
echo "id=${{ github.event.workflow_run.id }}" >> "$GITHUB_OUTPUT"
fi

- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
with:
name: pr-wheel
run-id: ${{ steps.run.outputs.id }}
github-token: ${{ github.token }}
path: dist/

- name: Read PR metadata
id: pr
run: |
echo "number=$(cat dist/PR_NUMBER)" >> "$GITHUB_OUTPUT"
echo "head_sha=$(cat dist/PR_HEAD_SHA)" >> "$GITHUB_OUTPUT"

- name: Validate wheel
id: wheel
run: |
PR="$(cat dist/PR_NUMBER)"
WHL_COUNT=$(ls dist/pyrympro-0.0.0.dev${PR}-*.whl 2>/dev/null | wc -l)
if [ "$WHL_COUNT" -ne 1 ]; then
echo "Expected exactly 1 wheel matching pyrympro-0.0.0.dev${PR}-*.whl, found ${WHL_COUNT}:"
ls dist/ || true
exit 1
fi
WHEEL_FILE=$(ls dist/pyrympro-0.0.0.dev${PR}-*.whl)
echo "basename=$(basename "$WHEEL_FILE")" >> "$GITHUB_OUTPUT"

- name: Delete existing PR release (if any)
run: gh release delete "pr-${{ steps.pr.outputs.number }}" --cleanup-tag --yes || true
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}

- name: Create GitHub pre-release
run: |
gh release create "pr-${{ steps.pr.outputs.number }}" "dist/${{ steps.wheel.outputs.basename }}" \
--prerelease \
--target "${{ steps.pr.outputs.head_sha }}" \
--repo "${{ github.repository }}" \
--title "PR #${{ steps.pr.outputs.number }} test build" \
--notes "Test build for PR #${{ steps.pr.outputs.number }}."
env:
GH_TOKEN: ${{ github.token }}

- name: Post PR comment (first build only)
run: |
PR="${{ steps.pr.outputs.number }}"
TAG="pr-${PR}"
REPO="${{ github.repository }}"
BASE="https://github.com/${REPO}/releases/download/${TAG}"
WHEEL_URL="${BASE}/${{ steps.wheel.outputs.basename }}"

EXISTING=$(gh api "repos/${REPO}/issues/${PR}/comments" \
--paginate \
--jq '.[] | select(.user.login == "github-actions[bot]") | select(.body | startswith("<!-- pr-test-build -->")) | .id' \
| head -1)

if [ -n "$EXISTING" ]; then
echo "Comment already exists, skipping."
exit 0
fi

BODY_FILE=$(mktemp)
printf '%s\n' \
'<!-- pr-test-build -->' \
'## Test build ready' \
'' \
'**pip:**' \
'```' \
"pip install ${WHEEL_URL}" \
'```' \
'' \
"**Home Assistant** — temporarily update the \`rympro\` integration's \`manifest.json\`:" \
'```json' \
'{' \
" \"requirements\": [\"pyrympro @ ${WHEEL_URL}\"]" \
'}' \
'```' \
'Revert to the pinned version after testing.' \
> "$BODY_FILE"
gh pr comment "$PR" --repo "$REPO" --body-file "$BODY_FILE"
env:
GH_TOKEN: ${{ github.token }}
49 changes: 49 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Release

on:
release:
types: [published]

permissions:
contents: read
actions: write # needed by actions/upload-artifact in the build job

jobs:
build:
name: Build distributions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0 # required for setuptools-scm to read git tags

- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.x"

- run: pip install build

- run: python -m build

- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: dist
path: dist/

publish:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/pyrympro
permissions:
id-token: write # OIDC for trusted publishing + PEP 740 attestations
actions: read # needed by actions/download-artifact
steps:
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
with:
name: dist
path: dist/

- uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ __pycache__/

build/
dist/
*.egg-info/
*.egg-info/
.mypy_cache/
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## Status
[![Build Status](https://github.com/OnFreund/pyrympro/actions/workflows/ci.yml/badge.svg)](https://github.com/OnFreund/pyrympro/actions/workflows/ci.yml)

# pyrympro

A python library to communitcate with [Read Your Meter Pro](https://rym-pro.com/).
Expand All @@ -24,4 +27,25 @@ await rym.login("<email>", "<password>", "<device_id>")
info = await rym.account_info()
meter_reads = await rym.last_read()
...
```
```

## Testing PRs

Every pull request automatically publishes a test build as a GitHub pre-release. You can find the install command in the PR comment posted by the bot, or on the [Releases page](https://github.com/OnFreund/pyrympro/releases) (pre-releases are tagged `pr-{number}`).

**pip:**
```
pip install https://github.com/OnFreund/pyrympro/releases/download/pr-42/pyrympro-0.0.0.dev42-py3-none-any.whl
```

**Home Assistant** — temporarily update the `rympro` integration's `manifest.json` to use the PEP 508 URL form so HA doesn't overwrite it on restart:
```json
{
"requirements": ["pyrympro @ https://github.com/OnFreund/pyrympro/releases/download/pr-42/pyrympro-0.0.0.dev42-py3-none-any.whl"]
}
```
Replace `42` with the actual PR number. Revert to the pinned version (e.g. `pyrympro==0.0.9`) after testing.

The install URL is stable for the lifetime of the PR — new commits to the same PR reuse the same tag and wheel name, so you don't need to update `manifest.json` if more commits are pushed.

The pre-release and comment are deleted automatically when the PR is merged or closed.
53 changes: 53 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
[build-system]
requires = ["setuptools>=61", "setuptools-scm>=8"]
build-backend = "setuptools.build_meta"

[project]
name = "pyrympro"
dynamic = ["version"]
description = "A python library to communicate with Read Your Meter Pro (https://rym-pro.com/)."
readme = "README.md"
license = { text = "MIT" }
authors = [{ name = "On Freund", email = "onfreund@gmail.com" }]
requires-python = ">=3.10"
dependencies = [
"aiohttp",
]
classifiers = [
"Development Status :: 4 - Beta",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
]

[project.urls]
Homepage = "https://github.com/OnFreund/pyrympro"

[tool.setuptools]
include-package-data = false

[tool.setuptools.packages.find]
include = ["pyrympro*"]

[tool.setuptools.package-data]
pyrympro = ["py.typed"]

[tool.setuptools_scm]
# version derived from git tags (e.g. v0.0.9 -> 0.0.9)

[tool.mypy]
python_version = "3.10"
disallow_any_decorated = true
disallow_any_generics = true
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
no_implicit_optional = true
show_error_codes = true
strict_equality = true
warn_no_return = true
warn_redundant_casts = true
warn_unreachable = true
warn_unused_configs = true
warn_unused_ignores = true
Empty file added pyrympro/py.typed
Empty file.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
aiohttp
Loading
Loading