Skip to content

Commit edcecff

Browse files
authored
ci(release): make release.yml the top-level publish workflow (#22)
Collapse the tag-triggered caller into release.yml directly and re-enable PEP 740 attestations. The reusable workflow_call split had signing identity mismatched against the PyPI trusted publisher (configured for release.yml + pypi environment), which is why attestations were disabled. Running release.yml directly restores the matching identity.
1 parent bd3c8cb commit edcecff

3 files changed

Lines changed: 13 additions & 40 deletions

File tree

‎.github/workflows/python-publish.yml‎

Lines changed: 0 additions & 16 deletions
This file was deleted.

‎.github/workflows/release.yml‎

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,14 @@
11
name: Release gate & publish
22

33
on:
4-
workflow_call:
5-
inputs:
6-
python-version:
7-
description: Python version to build/test/publish with
8-
required: false
9-
type: string
10-
default: "3.13"
11-
pypi-project-url:
12-
description: PyPI project URL shown on the environment deployment
13-
required: true
14-
type: string
15-
environment-name:
16-
description: Protected GitHub environment gating the publish step
17-
required: false
18-
type: string
19-
default: pypi
4+
push:
5+
tags:
6+
- "v*.*.*"
7+
8+
env:
9+
PYTHON_VERSION: "3.13"
10+
PYPI_PROJECT_URL: https://pypi.org/p/teslemetry-stream
11+
ENVIRONMENT_NAME: pypi
2012

2113
jobs:
2214
lint:
@@ -75,8 +67,8 @@ jobs:
7567
uses: astral-sh/setup-uv@v5
7668
with:
7769
enable-cache: true
78-
- name: Set up Python ${{ inputs.python-version }}
79-
run: uv python install ${{ inputs.python-version }}
70+
- name: Set up Python ${{ env.PYTHON_VERSION }}
71+
run: uv python install ${{ env.PYTHON_VERSION }}
8072
- name: Build
8173
run: uv build
8274
- name: Check distribution
@@ -92,8 +84,8 @@ jobs:
9284
needs: build
9385
runs-on: ubuntu-latest
9486
environment:
95-
name: ${{ inputs.environment-name }}
96-
url: ${{ inputs.pypi-project-url }}
87+
name: ${{ env.ENVIRONMENT_NAME }}
88+
url: ${{ env.PYPI_PROJECT_URL }}
9789
permissions:
9890
id-token: write
9991
steps:
@@ -104,9 +96,6 @@ jobs:
10496
path: dist/
10597
- name: Publish distribution to PyPI
10698
uses: pypa/gh-action-pypi-publish@release/v1
107-
with:
108-
# Reusable workflow: PEP 740 attestations sign under the top-level caller's identity, which PyPI's publisher-identity check rejects with a 400.
109-
attestations: false
11099

111100
github-release:
112101
name: Upload distribution to GitHub Release

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ This file is the project's committed home for project-intrinsic agent knowledge:
1414
- Energy site events (`teslemetry_stream/energysite.py`) are shaped differently from vehicle signals: `live_status`/`site_info` are flat top-level envelopes (`{createdAt, site_id, isCache?, live_status|site_info}`), not nested under `data`, and the payload is a full opaque document rather than a field delta - there is no per-field config to enable, the server auto-polls subscribed sites. Contract source: Teslemetry/api PR 310 (`src/routes/sse/index.ts`, `liveStatusSchema.ts`, `siteInfoSchema.ts`), flag-gated server-side as of this writing - `tests/test_energysite_events.py` fixtures mirror that PR's schemas.
1515
- `energy_totals` (Teslemetry/api PR 316, trimmed by PR 321) is shaped differently again: the site id rides the `id` field, not `site_id` - filter on `id` and `totals`, not `site_id`. It carries a compact cumulative `totals` object (`EnergyHistoryTotals` in `const.py`) instead of a document, fires only when the server's periodic `calendar_history` poll detects a change (silence is not staleness), and has no snapshot-on-connect delivery. As of PR 321 the wire payload is trimmed to `id`/`createdAt`/`totals` plus `isCache` only when true (`product_type`/`topic`/`url` were dropped as redundant with the event's own topic name and site id); `Key.PRODUCT_TYPE`/`Key.TOPIC`/`Key.URL` in `const.py` remain defined for other event kinds but are no longer part of the energy_totals filter.
1616
- `site_info` events no longer carry `tariff_content`/`tariff_content_v2` (Teslemetry/api PR 318); the V2 tariff is its own `tariff_content_v2` event/listener (`listen_TariffContentV2`), same envelope shape as `site_info`, with a `None` body meaning an explicit server-side removal rather than "not received yet". Both share the same silence-means-no-change contract - freshness lives in REST, never in event cadence. There is deliberately no library helper recombining `site_info` and `tariff_content_v2` into one document - that would only ever cover the V2 tariff (legacy V1 `tariff_content` has no SSE topic and stays REST-only by design), so it can't actually promise the whole REST-shaped document; a consumer wanting both tariffs together should use the REST site_info endpoint.
17-
- Releases (tag `v*.*.*`) go through `.github/workflows/release.yml`, a reusable `workflow_call` gate: `lint` + the full `test` python-version matrix (mirrors `ci.yml`) must pass on the exact release SHA before `build` (single Python, build+twine) runs, and only then do the `pypi` environment's protection rules (and its trusted-publishing OIDC) allow `publish-to-pypi`; `python-publish.yml` just supplies the tag trigger and calls it. The `pypi` GitHub environment itself (required reviewers, deployment branches) is admin-configured outside this repo's files.
17+
- Releases (tag `v*.*.*`) go through `.github/workflows/release.yml` directly - it's the sole top-level workflow, triggered on the tag push: `lint` + the full `test` python-version matrix (mirrors `ci.yml`) must pass on the exact release SHA before `build` (single Python, build+twine) runs, and only then do the `pypi` environment's protection rules (and its trusted-publishing OIDC) allow `publish-to-pypi`. It must stay a top-level workflow, not a `workflow_call` reusable one - PyPI's trusted publisher is configured for the `release.yml` + `pypi` environment identity, and a reusable-workflow caller signs PEP 740 attestations under the caller's identity instead, which that publisher check rejects. The `pypi` GitHub environment itself (required reviewers, deployment branches) is admin-configured outside this repo's files.
1818
- `TeslemetryStream(topics=...)` (Teslemetry/api PR 319) is an optional exact SSE wire-event allowlist sent as the connection's `topics` query param; `SseTopic` in `const.py` is the closed set the server recognizes (must stay in sync with the api's `SSE_TOPICS`), and `SSE_VEHICLE_TOPICS`/`SSE_ENERGY_TOPICS`/`SSE_ALL_TOPICS` are client-side presets - flat per-product-kind lists of exact wire names, deliberately not further split by whether a topic happens to have a connect-time snapshot server-side; that's upstream server behavior, not something this library encodes. Omitting `topics` (`None`) is legacy-all forever - every applicable event delivered unfiltered - and existing callers that never pass it are unaffected. An explicitly empty iterable is rejected with `ValueError` at construction time rather than silently falling back to legacy-all - "no topics" must not mean "all topics", mirroring the server's own 400 on an empty `topics` value. A bare `str`/`SseTopic` is accepted as a single topic rather than iterated character-by-character - `topics` type-checks `str | Iterable[str] | None` precisely because a lone string also satisfies `Iterable[str]`, the classic footgun. `tests/test_sse_topics.py` covers the tariff listener, its null-removal signal, the `topics` param's URL construction, the empty-iterable rejection, and the bare-string/bare-`SseTopic` case.
1919

2020
## Maintaining this file

0 commit comments

Comments
 (0)