From 7468a8a064a424976abf1ac7c0147cfea6433a7e Mon Sep 17 00:00:00 2001 From: Justin Jeffery <34625666+jjeff07@users.noreply.github.com> Date: Tue, 9 Jun 2026 11:57:05 -0400 Subject: [PATCH] fix: Response statuses were not raised. (#2) * fix: Response statuses were not raised. * style: Fix format. --- .github/workflows/release.yml | 128 ++++++++++++++++++++++++++++++++ src/libreclient/_base_client.py | 2 + src/libreclient/_route_types.py | 0 tests/unit/test_client.py | 50 +++++++++++++ 4 files changed, 180 insertions(+) create mode 100644 .github/workflows/release.yml delete mode 100644 src/libreclient/_route_types.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ef20995 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,128 @@ +name: Release + +on: + workflow_dispatch: + inputs: + bump: + description: "Version bump type" + required: true + type: choice + options: + - patch + - minor + - major + +permissions: + contents: write + id-token: write + +jobs: + release: + name: Bump, Changelog, Build & Publish + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/project/libreclient/ + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + + - name: Install dependencies + run: uv sync + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # --- Version Bump --- + - name: Get current version + id: current + run: echo "version=$(uv version --short)" >> "$GITHUB_OUTPUT" + + - name: Bump version + id: bump + run: | + uv version --bump ${{ inputs.bump }} + echo "version=$(uv version --short)" >> "$GITHUB_OUTPUT" + echo "Bumped version: ${{ steps.current.outputs.version }} → $(uv version --short)" + + # --- Changelog --- + - name: Generate changelog entry + run: | + NEW_VERSION="${{ steps.bump.outputs.version }}" + DATE=$(date +%Y-%m-%d) + PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + + # Get commit messages since last tag (or all if no tags) + if [ -n "$PREV_TAG" ]; then + COMMITS=$(git log "${PREV_TAG}..HEAD" --pretty=format:"- %s" --no-merges) + else + COMMITS=$(git log --pretty=format:"- %s" --no-merges) + fi + + # Build the new changelog entry + ENTRY="## [${NEW_VERSION}] - ${DATE}\n\n### Changed\n\n${COMMITS}" + + # Insert after [Unreleased] section + sed -i "/^## \[Unreleased\]/a\\\\n${ENTRY}" CHANGELOG.md + + # Update comparison links at bottom + if [ -n "$PREV_TAG" ]; then + sed -i "s|\[Unreleased\]:.*|\[Unreleased\]: https://github.com/jjeff07/libreclient/compare/v${NEW_VERSION}...HEAD|" CHANGELOG.md + sed -i "/^\[Unreleased\]/a [${NEW_VERSION}]: https://github.com/jjeff07/libreclient/compare/${PREV_TAG}...v${NEW_VERSION}" CHANGELOG.md + else + sed -i "s|\[Unreleased\]:.*|\[Unreleased\]: https://github.com/jjeff07/libreclient/compare/v${NEW_VERSION}...HEAD|" CHANGELOG.md + echo "[${NEW_VERSION}]: https://github.com/jjeff07/libreclient/releases/tag/v${NEW_VERSION}" >> CHANGELOG.md + fi + + # --- Generate Type Stubs --- + - name: Generate .pyi stubs + run: uv run python generate_stubs.py + + # --- Build --- + - name: Build package + run: uv build + + # --- Commit & Tag --- + - name: Commit version bump and changelog + run: | + git add pyproject.toml uv.lock CHANGELOG.md + git commit -m "release: v${{ steps.bump.outputs.version }}" + git tag "v${{ steps.bump.outputs.version }}" + git push origin HEAD --tags + + # --- Publish --- + - name: Publish to TestPyPI + continue-on-error: true + run: uv publish --index-url https://test.pypi.org/legacy/ + + - name: Publish to PyPI + run: uv publish + + # --- GitHub Release --- + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.bump.outputs.version }} + name: v${{ steps.bump.outputs.version }} + body: | + ## Changes + + See [CHANGELOG.md](https://github.com/jjeff07/libreclient/blob/main/CHANGELOG.md) for details. + files: dist/* + + # --- Sync dev branch --- + - name: Fast-forward dev to main + run: | + git fetch origin dev + git checkout dev + git merge --ff-only origin/main + git push origin dev diff --git a/src/libreclient/_base_client.py b/src/libreclient/_base_client.py index 20ee746..19a1437 100644 --- a/src/libreclient/_base_client.py +++ b/src/libreclient/_base_client.py @@ -30,6 +30,7 @@ async def _request( response = await self._request_raw( method, self._api_url(path), **kwargs ) + response.raise_for_status() return await _json_from_response(response) async def _request_bytes( @@ -38,6 +39,7 @@ async def _request_bytes( response = await self._request_raw( method, self._api_url(path), **kwargs ) + response.raise_for_status() payload = response.content if inspect.isawaitable(payload): payload = await payload diff --git a/src/libreclient/_route_types.py b/src/libreclient/_route_types.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 7df702a..8a389d9 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -1,5 +1,8 @@ import asyncio +import pytest +from niquests import HTTPError + from libreclient.client import LibreClientAsync, LibreClientSync from libreclient.config import LibreConfig @@ -59,3 +62,50 @@ async def close(self): # noqa: S7503 asyncio.run(client.close()) assert state["closed"] is True + + +def test_sync_client_raises_for_http_error(monkeypatch) -> None: + """Client should raise HTTPError on non-2xx responses.""" + client = LibreClientSync(url="https://nms.example.com", token="x") + + class FakeResponse: + status_code = 404 + + def raise_for_status(self): + raise HTTPError("404 Not Found", response=self) + + def json(self): + return {"status": "error", "message": "Not Found"} + + monkeypatch.setattr( + client._session, "request", lambda *a, **kw: FakeResponse() + ) + + with pytest.raises(HTTPError): + client.system.ping() + + client.close() + + +def test_async_client_raises_for_http_error(monkeypatch) -> None: + """Async client should raise HTTPError on non-2xx responses.""" + client = LibreClientAsync(url="https://nms.example.com", token="x") + + class FakeResponse: + status_code = 500 + + def raise_for_status(self): + raise HTTPError("500 Internal Server Error", response=self) + + def json(self): + return {"status": "error", "message": "Server Error"} + + async def fake_request(*a, **kw): + return FakeResponse() + + monkeypatch.setattr(client._session, "request", fake_request) + + with pytest.raises(HTTPError): + asyncio.run(client.system.ping()) + + asyncio.run(client.close())