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
38 changes: 9 additions & 29 deletions .github/workflows/ci-test.yml
Original file line number Diff line number Diff line change
@@ -1,38 +1,18 @@
name: Python application
name: CI

on:
push:
branches: [ "master" ]
branches: [master]
pull_request:
branches: [ "master" ]
branches: [master]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10"]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: chartboost/ruff-action@v1
with:
args: 'check'
- uses: chartboost/ruff-action@v1
with:
args: 'format'
- name: Set up Python ${{matrix.python-version}}
uses: actions/setup-python@v3
with:
python-version: ${{matrix.python-version}}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements_tests.txt ]; then pip install -r requirements_tests.txt; fi
- name: Test with pytest
run: |
pytest
jobs:
tests:
uses: ./.github/workflows/tests.yml
45 changes: 45 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Publish to PyPI

on:
push:
tags: ['v[0-9]*.[0-9]*.[0-9]*'] # bumpversion tags, e.g. v2.6.0

permissions:
contents: read

jobs:
tests:
uses: ./.github/workflows/tests.yml

build:
needs: tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Build sdist + wheel
run: |
python -m pip install --upgrade pip build
python -m build
- name: Check artifacts
run: |
python -m pip install twine
twine check dist/*
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

publish:
needs: build
runs-on: ubuntu-latest
permissions:
id-token: write # required for OIDC Trusted Publishing
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@v1.14.0
42 changes: 42 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Tests

on:
workflow_call:

permissions:
contents: read

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
with:
version: 0.3.0
args: check
- uses: astral-sh/ruff-action@v3
with:
version: 0.3.0
args: format --check

test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ['3.9', '3.10', '3.11']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: requirements_tests.txt
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .
pip install -r requirements_tests.txt
- name: Test with pytest
run: pytest
16 changes: 0 additions & 16 deletions .travis.yml

This file was deleted.

12 changes: 8 additions & 4 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ Make sure all your changes are committed (including an entry in HISTORY.rst).
Then run::

$ bump2version patch # possible: major / minor / patch
$ git push
$ git push --tags

Travis will then deploy to PyPI if tests pass.
$ git push --follow-tags

bump2version bumps the version in setup.py, blebox_uniapi/__init__.py and
setup.cfg, commits the change and creates a matching git tag (e.g. v2.6.0).
Pushing the tag triggers the "Publish to PyPI" GitHub Actions workflow
(.github/workflows/release.yml), which first runs lint + tests, then builds and
uploads to PyPI via Trusted Publishing (OIDC) from the blebox/blebox_uniapi repo.
The publish step only runs if the tests pass.
1 change: 0 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
include AUTHORS.rst
include CONTRIBUTING.rst
include HISTORY.rst
include LICENSE
Expand Down
2 changes: 0 additions & 2 deletions blebox_uniapi/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ class UnsupportedBoxVersion(BoxError):
pass



class BadFieldExceedsMax(BoxError):
def __init__(self, dev_name: str, field: str, value: int, max_value: int):
self._dev_name = dev_name
Expand Down Expand Up @@ -103,7 +102,6 @@ def __str__(self) -> str:
return f"{self._dev_name}.{self._field} is {self._value} which is not a rgbw string"



# misc errors


Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"Natural Language :: English",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
],
description="Python API for accessing BleBox smart home devices",
install_requires=requirements,
Expand Down
12 changes: 10 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ def __call__(self, url, **kwargs):
response = _json.dumps(data).encode("utf-8")
status = 200
return AiohttpClientMockResponse(
"GET", url, status, response, headers={"content-type": "application/json"}
"GET",
url,
status,
response,
headers={"content-type": "application/json"},
)

mock.get = AsyncMock(side_effect=EffectWhenGet(mock))
Expand Down Expand Up @@ -126,7 +130,11 @@ def __call__(self, url, **kwargs):
response = _json.dumps(data).encode("utf-8")
status = 200
return AiohttpClientMockResponse(
"POST", url, status, response, headers={"content-type": "application/json"}
"POST",
url,
status,
response,
headers={"content-type": "application/json"},
)

mock.post = AsyncMock(side_effect=EffectWhenPost(mock))
Expand Down
12 changes: 9 additions & 3 deletions tests/test_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,9 @@ async def test_async_ota_update_calls_session(mock_session, sample_data, config)
mock_session.async_api_get.assert_called_once_with("/api/ota/update")


async def test_async_ota_check_updates_firmware_versions(mock_session, sample_data, config):
async def test_async_ota_check_updates_firmware_versions(
mock_session, sample_data, config
):
box = Box(mock_session, sample_data, config, None)
mock_session.async_api_get_ota = AsyncMock(return_value=None)
mock_session.async_api_get = AsyncMock(
Expand All @@ -211,7 +213,9 @@ async def test_async_ota_check_unwraps_device_key(mock_session, sample_data, con
assert box.available_firmware_version == "3.0"


async def test_async_ota_check_raises_on_none_info_response(mock_session, sample_data, config):
async def test_async_ota_check_raises_on_none_info_response(
mock_session, sample_data, config
):
box = Box(mock_session, sample_data, config, None)
mock_session.async_api_get_ota = AsyncMock(return_value=None)
mock_session.async_api_get = AsyncMock(return_value=None)
Expand All @@ -220,7 +224,9 @@ async def test_async_ota_check_raises_on_none_info_response(mock_session, sample
await box.async_ota_check()


async def test_async_ota_check_returns_silently_when_no_available_fv(mock_session, sample_data, config):
async def test_async_ota_check_returns_silently_when_no_available_fv(
mock_session, sample_data, config
):
box = Box(mock_session, sample_data, config, None)
mock_session.async_api_get_ota = AsyncMock(return_value=None)
mock_session.async_api_get = AsyncMock(return_value={"fv": "1.0"})
Expand Down
8 changes: 6 additions & 2 deletions tests/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,15 @@ async def test_session_api_get_non_json_returns_none(logger, client):
assert result is None


async def test_session_api_get_unicode_decode_error_raises_connection_error(logger, client):
async def test_session_api_get_unicode_decode_error_raises_connection_error(
logger, client
):
response = Mock(spec_set=aiohttp.ClientResponse)
response.status = 200
response.content_type = "application/json"
response.json = AsyncMock(side_effect=UnicodeDecodeError("utf-8", b"", 0, 1, "invalid"))
response.json = AsyncMock(
side_effect=UnicodeDecodeError("utf-8", b"", 0, 1, "invalid")
)
client.get = AsyncMock(return_value=response)
api_session = Session("127.0.0.4", "88", 2, client, None, logger)
with pytest.raises(error.ConnectionError, match="Invalid response encoding"):
Expand Down
8 changes: 6 additions & 2 deletions tests/test_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ async def test_calls_ota_check_on_product(self, update, mock_product):
await update.async_update()
mock_product.async_ota_check.assert_called_once()

async def test_propagates_connection_error_from_ota_check(self, update, mock_product):
async def test_propagates_connection_error_from_ota_check(
self, update, mock_product
):
mock_product.async_ota_check = AsyncMock(
side_effect=error.ConnectionError("connection refused")
)
Expand All @@ -58,7 +60,9 @@ async def test_calls_ota_update_on_product(self, update, mock_product):
await update.async_install()
mock_product.async_ota_update.assert_called_once()

async def test_propagates_connection_error_from_ota_update(self, update, mock_product):
async def test_propagates_connection_error_from_ota_update(
self, update, mock_product
):
mock_product.async_ota_update = AsyncMock(
side_effect=error.ConnectionError("connection refused")
)
Expand Down
Loading