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
16 changes: 0 additions & 16 deletions .github/workflows/python-publish.yml

This file was deleted.

35 changes: 12 additions & 23 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
name: Release gate & publish

on:
workflow_call:
inputs:
python-version:
description: Python version to build/test/publish with
required: false
type: string
default: "3.13"
pypi-project-url:
description: PyPI project URL shown on the environment deployment
required: true
type: string
environment-name:
description: Protected GitHub environment gating the publish step
required: false
type: string
default: pypi
push:
tags:
- "v*.*.*"

env:
PYTHON_VERSION: "3.13"
PYPI_PROJECT_URL: https://pypi.org/p/teslemetry-stream
ENVIRONMENT_NAME: pypi

jobs:
lint:
Expand Down Expand Up @@ -75,8 +67,8 @@ jobs:
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: Set up Python ${{ inputs.python-version }}
run: uv python install ${{ inputs.python-version }}
- name: Set up Python ${{ env.PYTHON_VERSION }}
run: uv python install ${{ env.PYTHON_VERSION }}
- name: Build
run: uv build
- name: Check distribution
Expand All @@ -92,8 +84,8 @@ jobs:
needs: build
runs-on: ubuntu-latest
environment:
name: ${{ inputs.environment-name }}
url: ${{ inputs.pypi-project-url }}
name: ${{ env.ENVIRONMENT_NAME }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use a supported context for the environment name

On every matching release tag, GitHub must evaluate this job-level environment name, but env is not available here: GitHub's context-availability table permits only github, needs, strategy, matrix, vars, and inputs for jobs.<job_id>.environment. Consequently, the release workflow is rejected before the protected pypi environment or publish step can run; hardcode pypi here or store it in vars instead.

AGENTS.md reference: AGENTS.md:L17-L17

Useful? React with 👍 / 👎.

url: ${{ env.PYPI_PROJECT_URL }}
permissions:
id-token: write
steps:
Expand All @@ -104,9 +96,6 @@ jobs:
path: dist/
- name: Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
# Reusable workflow: PEP 740 attestations sign under the top-level caller's identity, which PyPI's publisher-identity check rejects with a 400.
attestations: false

github-release:
name: Upload distribution to GitHub Release
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ This file is the project's committed home for project-intrinsic agent knowledge:
- 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.
- `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.
- `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.
- 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.
- 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.
- `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.

## Maintaining this file
Expand Down
Loading