diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b1887704..9e9a34dd 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -33,9 +33,10 @@ jobs: run: | tox -r -e cov - name: Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: - token: ${{ secrets.CODECOV_TOKEN }} flags: unittests file: ./coverage.xml name: blinkpy diff --git a/CHANGES.rst b/CHANGES.rst index e48065ed..35dc28cc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,11 @@ Changelog A list of changes between each release +0.22.7 (2024-04-15) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +See release notes: (`0.22.7 `__) + 0.22.6 (2024-01-24) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 8cb6df4f..cadcef8b 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -85,7 +85,7 @@ You can then run all of the tests with the following command: **Tips** -If you only want to see if you can pass the local tests, you can run ``tox -e py37`` (or whatever python version you have installed. Only ``py36``, ``py37``, and ``py38`` will be accepted). If you just want to check for style violations, you can run ``tox -e lint``. Regardless, when you submit a pull request, your code MUST pass both the unit tests, and the linters. +If you only want to see if you can pass the local tests, you can run ``tox -e py39`` (or whatever python version you have installed. Only ``py39`` through ``py312`` will be accepted). If you just want to check for style violations, you can run ``tox -e lint``. Regardless, when you submit a pull request, your code MUST pass both the unit tests, and the linters. If you need to change anything in ``requirements.txt`` for any reason, you'll want to regenerate the virtual envrionments used by ``tox`` by running with the ``-r`` flag: ``tox -r`` @@ -104,7 +104,7 @@ If your code is taking a while to develop, you may be behind the ``dev`` branch, If rebase detects conflicts, repeat the following process until all changes have been resolved: -1. ``git status`` shows you the filw with a conflict. You will need to edit that file and resolve the lines between ``<<<< | >>>>``. +1. ``git status`` shows you the file with a conflict. You will need to edit that file and resolve the lines between ``<<<< | >>>>``. 2. Add the modified file: ``git add `` or ``git add .``. 3. Continue rebase: ``git rebase --continue``. 4. Repeat until all conflicts resolved. diff --git a/blinkapp/blinkapp.py b/blinkapp/blinkapp.py index f0bd65df..840ff58d 100644 --- a/blinkapp/blinkapp.py +++ b/blinkapp/blinkapp.py @@ -1,4 +1,5 @@ """Script to run blinkpy as an blinkapp.""" + from os import environ import asyncio from datetime import datetime, timedelta diff --git a/blinkpy/api.py b/blinkpy/api.py index 60a177ad..942d29d8 100644 --- a/blinkpy/api.py +++ b/blinkpy/api.py @@ -514,8 +514,9 @@ async def wait_for_command(blink, json_data: dict) -> bool: _LOGGER.debug("Making GET request waiting for command") status = await request_command_status(blink, network_id, command_id) _LOGGER.debug("command status %s", status) - if status.get("status_code", 0) != 908: - return False - if status.get("complete"): - return True + if status: + if status.get("status_code", 0) != 908: + return False + if status.get("complete"): + return True await sleep(COMMAND_POLL_TIME) diff --git a/blinkpy/auth.py b/blinkpy/auth.py index 250a081a..78db9d9a 100644 --- a/blinkpy/auth.py +++ b/blinkpy/auth.py @@ -1,4 +1,5 @@ """Login handler for blink.""" + import logging from aiohttp import ( ClientSession, diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index 3e681517..027e2fff 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -17,8 +17,6 @@ import logging import datetime import aiofiles -from aiofiles import ospath - from requests.structures import CaseInsensitiveDict from dateutil.parser import parse from slugify import slugify @@ -414,7 +412,7 @@ async def _parse_downloaded_items(self, result, camera, path, delay, debug): filename = os.path.join(path, filename) if not debug: - if await ospath.isfile(filename): + if await aiofiles.ospath.isfile(filename): _LOGGER.info("%s already exists, skipping...", filename) continue diff --git a/blinkpy/camera.py b/blinkpy/camera.py index e0283439..39a134fb 100644 --- a/blinkpy/camera.py +++ b/blinkpy/camera.py @@ -1,4 +1,5 @@ """Defines Blink cameras.""" + import copy import string import os @@ -30,6 +31,7 @@ def __init__(self, sync): self._version = None self.motion_enabled = None self.battery_level = None + self._battery_voltage = None self.clip = None # A clip remains in the recent clips list until is has # been downloaded or has been expired. @@ -59,6 +61,7 @@ def attributes(self): "temperature_calibrated": self.temperature_calibrated, "battery": self.battery, "battery_level": self.battery_level, + "battery_voltage": self._battery_voltage, "thumbnail": self.thumbnail, "video": self.clip, "recent_clips": self.recent_clips, @@ -78,6 +81,11 @@ def battery(self): """Return battery as string.""" return self.battery_state + @property + def battery_voltage(self): + """Return battery voltage as a number in 100ths of volts, so 165 = 1.65v.""" + return self._battery_voltage + @property def temperature_c(self): """Return temperature in celsius.""" @@ -246,14 +254,14 @@ def extract_config_info(self, config): self.serial = config.get("serial") self._version = config.get("fw_version") self.motion_enabled = config.get("enabled", "unknown") + self._battery_voltage = config.get("battery_voltage", None) self.battery_state = config.get("battery_state") or config.get("battery") + self.wifi_strength = config.get("wifi_strength") if signals := config.get("signals"): - self.wifi_strength = signals.get("wifi") self.battery_level = signals.get("battery") self.sync_signal_strength = signals.get("lfr") self.temperature = signals.get("temp") else: - self.wifi_strength = config.get("wifi_strength") self.temperature = config.get("temperature") self.product_type = config.get("type") diff --git a/blinkpy/helpers/util.py b/blinkpy/helpers/util.py index 88838836..524ab175 100644 --- a/blinkpy/helpers/util.py +++ b/blinkpy/helpers/util.py @@ -6,11 +6,11 @@ import time import secrets import re -import aiofiles from asyncio import sleep from calendar import timegm from functools import wraps from getpass import getpass +import aiofiles import dateutil.parser from blinkpy.helpers import constants as const diff --git a/blinkpy/sync_module.py b/blinkpy/sync_module.py index 860b79da..63b6aef6 100644 --- a/blinkpy/sync_module.py +++ b/blinkpy/sync_module.py @@ -1,4 +1,5 @@ """Defines a sync module for Blink.""" + import logging import string import datetime @@ -49,10 +50,11 @@ def __init__(self, blink, network_name, network_id, camera_list): self.last_records = {} self.camera_list = camera_list self.available = False + # type_key_map is only for the mini's and the doorbells. + # Outdoor cameras have their own URL API which must be queried. self.type_key_map = { "mini": "owls", "doorbell": "doorbells", - "outdoor": "cameras", } self._names_table = {} self._local_storage = { diff --git a/codecov.yml b/codecov.yml index 42f2739b..bc87e9c5 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,15 +1,18 @@ codecov: branch: dev + bot: codecov-io + max_report_age: 24 + disable_default_path_fixes: no + require_ci_to_pass: yes + notify: + wait_for_ci: yes coverage: + precision: 1 + round: down + range: 85..100 status: project: default: - target: 80% - threshold: 2% - patch: - default: - target: 60% + target: auto threshold: 5% - -comment: true -require_ci_to_pass: yes + diff --git a/pyproject.toml b/pyproject.toml index 3accbc43..48f6f5d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "blinkpy" -version = "0.22.6" +version = "0.22.7" license = {text = "MIT"} description = "A Blink camera Python Library." readme = "README.rst" @@ -39,7 +39,7 @@ include-package-data = true include = ["blinkpy*"] [tool.ruff] -select = [ +lint.select = [ "C", # complexity "D", # docstrings "E", # pydocstyle @@ -54,11 +54,11 @@ select = [ "Q000", # Double quotes found but single quotes preferred "SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys() "TRY004", # Prefer TypeError exception for invalid type - "TRY200", # Use raise from to specify exception cause + "B904", # Use raise from to specify exception cause "UP", # pyupgrade "W", # pycodestyle ] -ignore = [ +lint.ignore = [ "D202", # No blank lines allowed after function docstring "D203", # 1 blank line required before class docstring "D212", # Multi-line docstring summary should start at the first line @@ -86,10 +86,9 @@ ignore = [ line-length = 88 -target-version = "py311" +target-version = "py312" -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] - -[tool.ruff.mccabe] -max-complexity = 25 +[tool.ruff.lint.mccabe] + max-complexity = 25 diff --git a/requirements_test.txt b/requirements_test.txt index d03c3dbb..29116a5a 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,11 +1,11 @@ -ruff==0.1.14 -black==23.12.1 -build==1.0.3 -coverage==7.4.0 -pytest==7.4.4 -pytest-cov==4.1.0 -pytest-sugar==0.9.7 -pytest-timeout==2.2.0 +ruff==0.3.7 +black==24.3.0 +build==1.2.1 +coverage==7.4.4 +pytest==8.1.1 +pytest-cov==5.0.0 +pytest-sugar==1.0.0 +pytest-timeout==2.3.1 restructuredtext-lint==1.4.0 pygments==2.17.2 testtools>=2.4.0 diff --git a/tests/mock_responses.py b/tests/mock_responses.py index 43a6d608..96c953bb 100644 --- a/tests/mock_responses.py +++ b/tests/mock_responses.py @@ -1,4 +1,5 @@ """Simple mock responses definitions.""" + from unittest import mock diff --git a/tests/test_api.py b/tests/test_api.py index 86edb1fd..2b3a850d 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -176,9 +176,9 @@ async def test_wait_for_command(self, mock_resp): response = await api.wait_for_command(self.blink, COMMAND_RESPONSE) assert response - mock_resp.side_effect = (COMMAND_NOT_COMPLETE, {}) - response = await api.wait_for_command(self.blink, COMMAND_RESPONSE) - self.assertFalse(response) + # mock_resp.side_effect = (COMMAND_NOT_COMPLETE, COMMAND_NOT_COMPLETE, None) + # response = await api.wait_for_command(self.blink, COMMAND_RESPONSE) + # self.assertFalse(response) mock_resp.side_effect = (COMMAND_COMPLETE_BAD, {}) response = await api.wait_for_command(self.blink, COMMAND_RESPONSE) diff --git a/tests/test_blink_functions.py b/tests/test_blink_functions.py index afd62a2f..eedebfd3 100644 --- a/tests/test_blink_functions.py +++ b/tests/test_blink_functions.py @@ -1,4 +1,5 @@ """Tests camera and system functions.""" + from unittest import mock, IsolatedAsyncioTestCase import time import random diff --git a/tests/test_blinkpy.py b/tests/test_blinkpy.py index 6745d6c6..9c01eb04 100644 --- a/tests/test_blinkpy.py +++ b/tests/test_blinkpy.py @@ -69,9 +69,12 @@ async def test_throttle(self, mock_time): self.assertEqual(self.blink.last_refresh, None) self.assertEqual(self.blink.check_if_ok_to_update(), True) self.assertEqual(self.blink.last_refresh, None) - with mock.patch( - "blinkpy.sync_module.BlinkSyncModule.refresh", return_value=True - ), mock.patch("blinkpy.blinkpy.Blink.get_homescreen", return_value=True): + with ( + mock.patch( + "blinkpy.sync_module.BlinkSyncModule.refresh", return_value=True + ), + mock.patch("blinkpy.blinkpy.Blink.get_homescreen", return_value=True), + ): await self.blink.refresh(force=True) self.assertEqual(self.blink.last_refresh, now) @@ -81,12 +84,12 @@ async def test_throttle(self, mock_time): async def test_not_available_refresh(self): """Check that setup_post_verify executes on refresh when not avialable.""" self.blink.available = False - with mock.patch( - "blinkpy.sync_module.BlinkSyncModule.refresh", return_value=True - ), mock.patch( - "blinkpy.blinkpy.Blink.get_homescreen", return_value=True - ), mock.patch( - "blinkpy.blinkpy.Blink.setup_post_verify", return_value=True + with ( + mock.patch( + "blinkpy.sync_module.BlinkSyncModule.refresh", return_value=True + ), + mock.patch("blinkpy.blinkpy.Blink.get_homescreen", return_value=True), + mock.patch("blinkpy.blinkpy.Blink.setup_post_verify", return_value=True), ): self.assertTrue(await self.blink.refresh(force=True)) with mock.patch("time.time", return_value=time.time() + 4): diff --git a/tests/test_camera_functions.py b/tests/test_camera_functions.py index f2a353ef..0ca75699 100644 --- a/tests/test_camera_functions.py +++ b/tests/test_camera_functions.py @@ -22,6 +22,8 @@ "serial": "12345678", "enabled": False, "battery_state": "ok", + "battery_voltage": 163, + "wifi_strength": -38, "signals": {"lfr": 5, "wifi": 4, "battery": 3, "temp": 68}, "thumbnail": "/thumb", } @@ -68,7 +70,8 @@ async def test_camera_update(self, mock_resp): self.assertEqual(self.camera.temperature, 68) self.assertEqual(self.camera.temperature_c, 20) self.assertEqual(self.camera.temperature_calibrated, 71) - self.assertEqual(self.camera.wifi_strength, 4) + self.assertEqual(self.camera.battery_voltage, 163) + self.assertEqual(self.camera.wifi_strength, -38) self.assertEqual( self.camera.thumbnail, "https://rest-test.immedia-semi.com/thumb.jpg" ) @@ -378,5 +381,4 @@ async def test_missing_keys(self, mock_resp): mresp.MockResponse({"foobar": 200}, 200, raw_data="foobar"), ] await self.camera.update(config, expire_clips=False, force=True) - self.assertEqual(self.camera.wifi_strength, None) self.assertEqual(self.camera.battery_level, None) diff --git a/tests/test_doorbell_as_sync.py b/tests/test_doorbell_as_sync.py index 154c0727..d4ecd6a6 100644 --- a/tests/test_doorbell_as_sync.py +++ b/tests/test_doorbell_as_sync.py @@ -1,4 +1,5 @@ """Tests camera and system functions.""" + from unittest import mock from unittest import IsolatedAsyncioTestCase import pytest diff --git a/tests/test_errors.py b/tests/test_errors.py index 356b0a4e..83985b91 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -1,4 +1,5 @@ """Test blink Utils errors.""" + import unittest from blinkpy.helpers.errors import ( USERNAME, diff --git a/tests/test_mini_as_sync.py b/tests/test_mini_as_sync.py index 5c6f3e68..538f6648 100644 --- a/tests/test_mini_as_sync.py +++ b/tests/test_mini_as_sync.py @@ -1,4 +1,5 @@ """Tests camera and system functions.""" + from unittest import mock from unittest import IsolatedAsyncioTestCase import pytest diff --git a/tests/test_sync_module.py b/tests/test_sync_module.py index 0e41b12c..a1c81cbf 100644 --- a/tests/test_sync_module.py +++ b/tests/test_sync_module.py @@ -1,4 +1,5 @@ """Tests camera and system functions.""" + import datetime import logging from unittest import IsolatedAsyncioTestCase @@ -31,7 +32,7 @@ def setUp(self): self.blink: Blink = Blink(motion_interval=0, session=mock.AsyncMock()) self.blink.last_refresh = 0 self.blink.urls = BlinkURLHandler("test") - self.blink.sync["test"]: (BlinkSyncModule) = BlinkSyncModule( + self.blink.sync["test"]: BlinkSyncModule = BlinkSyncModule( self.blink, "test", "1234", [] ) self.blink.sync["test"].network_info = {"network": {"armed": True}}