diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ddb7c9ce..8715bf2d 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -7,14 +7,14 @@ on: branches: [ master, dev ] jobs: - build: + coverage: runs-on: ${{ matrix.platform }} strategy: max-parallel: 4 matrix: platform: - ubuntu-latest - python-version: [3.8] + python-version: [3.9] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b19ea603..9ab94725 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,8 +10,7 @@ on: branches: [ master, dev ] jobs: - build: - + lint: runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1e64154b..9880c996 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: matrix: platform: - ubuntu-latest - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: ['3.7', '3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 98d80a3a..bebf31fc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/psf/black - rev: 19.10b0 + rev: 22.1.0 hooks: - id: black args: @@ -8,12 +8,12 @@ repos: - --quiet files: ^((blinkpy|tests)/.+)?[^/]+\.py$ - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.3 + rev: 3.9.1 hooks: - id: flake8 additional_dependencies: - - flake8-docstrings==1.5.0 - - pydocstyle==5.1.1 + - flake8-docstrings==1.6.0 + - pydocstyle==6.0.0 files: ^(blinkpy|tests)/.+\.py$ - repo: https://github.com/Lucas-C/pre-commit-hooks-markup rev: v1.0.0 diff --git a/CHANGES.rst b/CHANGES.rst index 3678aa38..68ef3de3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,33 @@ Changelog A list of changes between each release +0.19.0 (2022-03-20) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Bugfixes:** + +- Debug log in prase download method fix (`@tieum `__) +- Fix issue with malformed thumbnails (`#550 `__) +- Fully support new thumbnail API (`@gdoermann `__) + +**New Features:** + +- Support for arm/disarm of Blink Mini cameras (`@mstratford `__) +- Add product_type to BlinkCamera class to report type of camera (`#553 `__) +- Remove python 3.6 support, add python 3.10 support (`#554 `__) + +**Other:** + +- Make code that determines need for unique class (Mini + Doorbells) generic (`#553 `__) +- Bump pre-commit to 2.17.0 +- Bump pytest-timeout to 2.1.0 +- Bump pygments to 2.11.2 +- Bump black to 22.1.0 +- Bump coverage to 6.3.2 +- Bump pytest to 7.1.1 +- Bump restructuredtext-lint to 1.4.0 + + 0.18.0 (2021-12-11) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Dockerfile b/Dockerfile index 02ccf085..1a40c5d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ VOLUME /media RUN python -m pip install --upgrade pip RUN pip3 install blinkpy -COPY app/ . +COPY blinkapp/ . -ENTRYPOINT ["python", "./app.py"] +ENTRYPOINT ["python", "./blinkapp.py"] CMD [] diff --git a/README.rst b/README.rst index 7fcd6d96..f52b8606 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ blinkpy |Build Status| |Coverage Status| |Docs| |PyPi Version| |Codestyle| ============================================================================================= -A Python library for the Blink Camera system (Python 3.6+) +A Python library for the Blink Camera system (Python 3.7+) Like the library? Consider buying me a cup of coffee! diff --git a/app/__init__.py b/app/__init__.py deleted file mode 100644 index 099ec593..00000000 --- a/app/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Python init file for app.py.""" diff --git a/blinkapp/__init__.py b/blinkapp/__init__.py new file mode 100644 index 00000000..15586a6f --- /dev/null +++ b/blinkapp/__init__.py @@ -0,0 +1 @@ +"""Python init file for blinkapp.py.""" diff --git a/app/app.py b/blinkapp/blinkapp.py similarity index 91% rename from app/app.py rename to blinkapp/blinkapp.py index 081bcdfc..af6c5018 100644 --- a/app/app.py +++ b/blinkapp/blinkapp.py @@ -1,4 +1,4 @@ -"""Script to run blinkpy as an app.""" +"""Script to run blinkpy as an blinkapp.""" from os import environ from datetime import datetime, timedelta from blinkpy.blinkpy import Blink @@ -29,7 +29,7 @@ def start(): def main(): - """Run the app.""" + """Run the blink app.""" blink = start() download_videos(blink) blink.save(CREDFILE) diff --git a/app/build.sh b/blinkapp/build.sh similarity index 100% rename from app/build.sh rename to blinkapp/build.sh diff --git a/app/run.sh b/blinkapp/run.sh similarity index 100% rename from app/run.sh rename to blinkapp/run.sh diff --git a/blinkpy/api.py b/blinkpy/api.py index 3df449a4..ac6dfe90 100644 --- a/blinkpy/api.py +++ b/blinkpy/api.py @@ -11,7 +11,10 @@ def request_login( - auth, url, login_data, is_retry=False, + auth, + url, + login_data, + is_retry=False, ): """ Login request. @@ -51,7 +54,11 @@ def request_verify(auth, blink, verify_key): url = f"{blink.urls.base_url}/api/v4/account/{blink.account_id}/client/{blink.client_id}/pin/verify" data = dumps({"pin": verify_key}) return auth.query( - url=url, headers=auth.header, data=data, json_resp=False, reqtype="post", + url=url, + headers=auth.header, + data=data, + json_resp=False, + reqtype="post", ) @@ -309,14 +316,21 @@ def http_get(blink, url, stream=False, json=True, is_retry=False, timeout=TIMEOU ) -def http_post(blink, url, is_retry=False, timeout=TIMEOUT): +def http_post(blink, url, is_retry=False, data=None, json=True, timeout=TIMEOUT): """ Perform an http post request. :param url: URL to perfom post request. :param is_retry: Is this part of a re-auth attempt? + :param data: str body for post request + :param json: Return json response? TRUE/False """ _LOGGER.debug("Making POST request to %s", url) return blink.auth.query( - url=url, headers=blink.auth.header, reqtype="post", is_retry=is_retry + url=url, + headers=blink.auth.header, + reqtype="post", + is_retry=is_retry, + json_resp=json, + data=data, ) diff --git a/blinkpy/auth.py b/blinkpy/auth.py index 3e78c134..a7e11f85 100644 --- a/blinkpy/auth.py +++ b/blinkpy/auth.py @@ -103,7 +103,12 @@ def login(self, login_url=LOGIN_ENDPOINT): """Attempt login to blink servers.""" self.validate_login() _LOGGER.info("Attempting login with %s", login_url) - response = api.request_login(self, login_url, self.data, is_retry=False,) + response = api.request_login( + self, + login_url, + self.data, + is_retry=False, + ) try: if response.status_code == 200: return response.json() @@ -193,7 +198,8 @@ def query( return self.validate_response(response, json_resp) except (exceptions.ConnectionError, exceptions.Timeout): _LOGGER.error( - "Connection error. Endpoint %s possibly down or throttled.", url, + "Connection error. Endpoint %s possibly down or throttled.", + url, ) except BlinkBadResponse: code = None diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index c3168751..41f3220e 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -385,7 +385,7 @@ def _parse_downloaded_items(self, result, camera, path, delay, debug): print( ( f"Camera: {camera_name}, Timestamp: {created_at}, " - "Address: {address}, Filename: {filename}" + f"Address: {address}, Filename: {filename}" ) ) if delay > 0: diff --git a/blinkpy/camera.py b/blinkpy/camera.py index 29ceec8e..1212f508 100644 --- a/blinkpy/camera.py +++ b/blinkpy/camera.py @@ -2,6 +2,8 @@ from shutil import copyfileobj import logging +from json import dumps +from requests.compat import urljoin from blinkpy import api from blinkpy.helpers.constants import TIMEOUT_MEDIA @@ -31,6 +33,7 @@ def __init__(self, sync): self._cached_image = None self._cached_video = None self.camera_type = "" + self.product_type = None @property def attributes(self): @@ -52,6 +55,7 @@ def attributes(self): "network_id": self.sync.network_id, "sync_module": self.sync.name, "last_record": self.last_record, + "type": self.product_type, } return attributes @@ -108,7 +112,11 @@ def get_media(self, media_type="image"): if media_type.lower() == "video": url = self.clip return api.http_get( - self.sync.blink, url=url, stream=True, json=False, timeout=TIMEOUT_MEDIA, + self.sync.blink, + url=url, + stream=True, + json=False, + timeout=TIMEOUT_MEDIA, ) def snap_picture(self): @@ -145,6 +153,7 @@ def extract_config_info(self, config): self.battery_state = config.get("battery_state", None) self.temperature = config.get("temperature", None) self.wifi_strength = config.get("wifi_strength", None) + self.product_type = config.get("type", None) def get_sensor_info(self): """Retrieve calibrated temperatue from special endpoint.""" @@ -161,14 +170,29 @@ def update_images(self, config, force_cache=False): """Update images for camera.""" new_thumbnail = None thumb_addr = None + thumb_string = None if config.get("thumbnail", False): thumb_addr = config["thumbnail"] + try: + # API update only returns the timestamp! + int(thumb_addr) + thumb_string = f"/api/v3/media/accounts/{self.sync.blink.account_id}/networks/{self.network_id}/{self.product_type}/{self.camera_id}/thumbnail/thumbnail.jpg?ts={thumb_addr}&ext=" + except ValueError: + # This is the old API and has the full url + thumb_string = f"{thumb_addr}.jpg" + # Check that new full api url has not been returned: + if thumb_addr.endswith("&ext="): + thumb_string = thumb_addr + except TypeError: + # Thumb address is None + pass + + if thumb_string is not None: + new_thumbnail = urljoin(self.sync.urls.base_url, thumb_string) + else: _LOGGER.warning("Could not find thumbnail for camera %s", self.name) - if thumb_addr is not None: - new_thumbnail = f"{self.sync.urls.base_url}{thumb_addr}.jpg" - try: self.motion_detected = self.sync.motion[self.name] except KeyError: @@ -252,9 +276,9 @@ def arm(self): @arm.setter def arm(self, value): """Set camera arm status.""" - _LOGGER.warning( - "Individual camera motion detection enable/disable for Blink Mini cameras is unsupported at this time." - ) + url = f"{self.sync.urls.base_url}/api/v1/accounts/{self.sync.blink.account_id}/networks/{self.network_id}/owls/{self.camera_id}/config" + data = dumps({"enabled": value}) + return api.http_post(self.sync.blink, url, json=False, data=data) def snap_picture(self): """Snap picture for a blink mini camera.""" diff --git a/blinkpy/helpers/constants.py b/blinkpy/helpers/constants.py index 7bb48508..e2326e1b 100644 --- a/blinkpy/helpers/constants.py +++ b/blinkpy/helpers/constants.py @@ -3,12 +3,12 @@ import os MAJOR_VERSION = 0 -MINOR_VERSION = 18 +MINOR_VERSION = 19 PATCH_VERSION = 0 __version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}.{PATCH_VERSION}" -REQUIRED_PYTHON_VER = (3, 6, 0) +REQUIRED_PYTHON_VER = (3, 7, 0) PROJECT_NAME = "blinkpy" PROJECT_PACKAGE_NAME = "blinkpy" @@ -31,10 +31,10 @@ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Topic :: Home Automation", ] diff --git a/blinkpy/sync_module.py b/blinkpy/sync_module.py index 2730de5a..0e37702a 100644 --- a/blinkpy/sync_module.py +++ b/blinkpy/sync_module.py @@ -37,6 +37,10 @@ def __init__(self, blink, network_name, network_id, camera_list): self.last_record = {} self.camera_list = camera_list self.available = False + self.type_key_map = { + "mini": "owls", + "lotus": "doorbell", + } @property def attributes(self): @@ -118,6 +122,10 @@ def sync_initialize(self): def update_cameras(self, camera_type=BlinkCamera): """Update cameras from server.""" + type_map = { + "mini": BlinkCameraMini, + "lotus": BlinkDoorbell, + } try: for camera_config in self.camera_list: if "name" not in camera_config: @@ -125,15 +133,12 @@ def update_cameras(self, camera_type=BlinkCamera): blink_camera_type = camera_config.get("type", "") name = camera_config["name"] self.motion[name] = False - owl_info = self.get_owl_info(name) - lotus_info = self.get_lotus_info(name) - if blink_camera_type == "mini": - camera_type = BlinkCameraMini - if blink_camera_type == "lotus": - camera_type = BlinkDoorbell + unique_info = self.get_unique_info(name) + if blink_camera_type in type_map.keys(): + camera_type = type_map[blink_camera_type] self.cameras[name] = camera_type(self) camera_info = self.get_camera_info( - camera_config["id"], owl_info=owl_info, lotus_info=lotus_info + camera_config["id"], unique_info=unique_info ) self.cameras[name].update(camera_info, force_cache=True, force=True) @@ -142,22 +147,14 @@ def update_cameras(self, camera_type=BlinkCamera): return False return True - def get_owl_info(self, name): - """Extract owl information.""" + def get_unique_info(self, name): + """Extract unique information for Minis and Doorbells.""" try: - for owl in self.blink.homescreen["owls"]: - if owl["name"] == name: - return owl - except (TypeError, KeyError): - pass - return None - - def get_lotus_info(self, name): - """Extract lotus information.""" - try: - for doorbell in self.blink.homescreen["doorbells"]: - if doorbell["name"] == name: - return doorbell + for camera_type in self.type_key_map: + type_key = self.type_key_map[camera_type] + for device in self.blink.homescreen[type_key]: + if device["name"] == name: + return device except (TypeError, KeyError): pass return None @@ -174,12 +171,9 @@ def get_events(self, **kwargs): def get_camera_info(self, camera_id, **kwargs): """Retrieve camera information.""" - owl = kwargs.get("owl_info", None) - if owl is not None: - return owl - lotus = kwargs.get("lotus_info", None) - if lotus is not None: - return lotus + unique = kwargs.get("unique_info", None) + if unique is not None: + return unique response = api.request_camera_info(self.blink, self.network_id, camera_id) try: return response["camera"][0] @@ -207,8 +201,7 @@ def refresh(self, force_cache=False): camera_id = self.cameras[camera_name].camera_id camera_info = self.get_camera_info( camera_id, - owl_info=self.get_owl_info(camera_name), - lotus_info=self.get_lotus_info(camera_name), + unique_info=self.get_unique_info(camera_name), ) self.cameras[camera_name].update(camera_info, force_cache=force_cache) self.available = True diff --git a/requirements_test.txt b/requirements_test.txt index c10bab75..415e26d4 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,14 +1,14 @@ -black==19.10b0 -coverage==5.5 +black==22.1.0 +coverage==6.3.2 flake8==3.9.1 flake8-docstrings==1.6.0 -pre-commit==2.15.0 +pre-commit==2.17.0 pylint==2.10.2 pydocstyle==6.0.0 -pytest==6.2.5 +pytest==7.1.1 pytest-cov==3.0.0 pytest-sugar==0.9.4 -pytest-timeout==1.4.2 -restructuredtext-lint==1.3.2 -pygments==2.10.0 +pytest-timeout==2.1.0 +restructuredtext-lint==1.4.0 +pygments==2.11.2 testtools>=2.4.0 diff --git a/tests/test_blink_functions.py b/tests/test_blink_functions.py index c4c7d7c6..2879bb74 100644 --- a/tests/test_blink_functions.py +++ b/tests/test_blink_functions.py @@ -1,7 +1,6 @@ """Tests camera and system functions.""" import unittest from unittest import mock -import logging import time from blinkpy import blinkpy @@ -53,8 +52,6 @@ def test_merge_cameras(self): def test_download_video_exit(self, mock_req): """Test we exit method when provided bad response.""" blink = blinkpy.Blink() - # pylint: disable=protected-access - blinkpy._LOGGER.setLevel(logging.DEBUG) blink.last_refresh = 0 mock_req.return_value = {} formatted_date = get_time(blink.last_refresh) @@ -63,16 +60,14 @@ def test_download_video_exit(self, mock_req): "DEBUG:blinkpy.blinkpy:Processing page 1", "INFO:blinkpy.blinkpy:No videos found on page 1. Exiting.", ] - with self.assertLogs() as dl_log: + with self.assertLogs(level="DEBUG") as dl_log: blink.download_videos("/tmp") - self.assertEqual(dl_log.output, expected_log) + self.assertListEqual(dl_log.output, expected_log) @mock.patch("blinkpy.blinkpy.api.request_videos") def test_parse_downloaded_items(self, mock_req): """Test ability to parse downloaded items list.""" blink = blinkpy.Blink() - # pylint: disable=protected-access - blinkpy._LOGGER.setLevel(logging.DEBUG) generic_entry = { "created_at": "1970", "device_name": "foo", @@ -88,9 +83,9 @@ def test_parse_downloaded_items(self, mock_req): "DEBUG:blinkpy.blinkpy:Processing page 1", "DEBUG:blinkpy.blinkpy:foo: /bar.mp4 is marked as deleted.", ] - with self.assertLogs() as dl_log: + with self.assertLogs(level="DEBUG") as dl_log: blink.download_videos("/tmp", stop=2, delay=0) - self.assertEqual(dl_log.output, expected_log) + self.assertListEqual(dl_log.output, expected_log) @mock.patch("blinkpy.blinkpy.api.request_videos") def test_parse_downloaded_throttle(self, mock_req): @@ -120,8 +115,6 @@ def test_parse_downloaded_throttle(self, mock_req): def test_parse_camera_not_in_list(self, mock_req): """Test ability to parse downloaded items list.""" blink = blinkpy.Blink() - # pylint: disable=protected-access - blinkpy._LOGGER.setLevel(logging.DEBUG) generic_entry = { "created_at": "1970", "device_name": "foo", @@ -137,9 +130,9 @@ def test_parse_camera_not_in_list(self, mock_req): "DEBUG:blinkpy.blinkpy:Processing page 1", "DEBUG:blinkpy.blinkpy:Skipping videos for foo.", ] - with self.assertLogs() as dl_log: + with self.assertLogs(level="DEBUG") as dl_log: blink.download_videos("/tmp", camera="bar", stop=2, delay=0) - self.assertEqual(dl_log.output, expected_log) + self.assertListEqual(dl_log.output, expected_log) @mock.patch("blinkpy.blinkpy.api.request_network_update") @mock.patch("blinkpy.auth.Auth.query") diff --git a/tests/test_cameras.py b/tests/test_cameras.py index e482a72a..c752f530 100644 --- a/tests/test_cameras.py +++ b/tests/test_cameras.py @@ -13,7 +13,6 @@ from blinkpy.sync_module import BlinkSyncModule from blinkpy.camera import BlinkCamera, BlinkCameraMini, BlinkDoorbell - CAMERA_CFG = { "camera": [ { @@ -87,6 +86,14 @@ def test_camera_update(self, mock_resp): self.assertEqual(self.camera.image_from_cache, "test") self.assertEqual(self.camera.video_from_cache, "foobar") + # Check that thumbnail without slash processed properly + mock_resp.side_effect = [None] + self.camera.update_images({"thumbnail": "thumb_no_slash"}) + self.assertEqual( + self.camera.thumbnail, + "https://rest-test.immedia-semi.com/thumb_no_slash.jpg", + ) + def test_no_thumbnails(self, mock_resp): """Tests that thumbnail is 'None' if none found.""" mock_resp.return_value = "foobar" @@ -194,3 +201,75 @@ def test_camera_stream(self, mock_resp): self.assertEqual(self.camera.get_liveview(), "rtsps://foo.bar") self.assertEqual(mini_camera.get_liveview(), "rtsps://foo.bar") self.assertEqual(doorbell_camera.get_liveview(), "rtsps://foo.bar") + + def test_different_thumb_api(self, mock_resp): + """Test that the correct url is created with new api.""" + thumb_endpoint = "https://rest-test.immedia-semi.com/api/v3/media/accounts/9999/networks/5678/test/1234/thumbnail/thumbnail.jpg?ts=1357924680&ext=" + config = { + "name": "new", + "id": 1234, + "network_id": 5678, + "serial": "12345678", + "enabled": False, + "battery_voltage": 90, + "battery_state": "ok", + "temperature": 68, + "wifi_strength": 4, + "thumbnail": 1357924680, + "type": "test", + } + mock_resp.side_effect = [ + {"temp": 71}, + "test", + ] + self.camera.sync.blink.account_id = 9999 + self.camera.update(config) + self.assertEqual(self.camera.thumbnail, thumb_endpoint) + + def test_thumb_return_none(self, mock_resp): + """Test that a 'None" thumbnail is doesn't break system.""" + config = { + "name": "new", + "id": 1234, + "network_id": 5678, + "serial": "12345678", + "enabled": False, + "battery_voltage": 90, + "battery_state": "ok", + "temperature": 68, + "wifi_strength": 4, + "thumbnail": None, + "type": "test", + } + mock_resp.side_effect = [ + {"temp": 71}, + "test", + ] + self.camera.update(config) + self.assertEqual(self.camera.thumbnail, None) + + def test_new_thumb_url_returned(self, mock_resp): + """Test that thumb handled properly if new url returned.""" + thumb_return = "/api/v3/media/accounts/9999/networks/5678/test/1234/thumbnail/thumbnail.jpg?ts=1357924680&ext=" + config = { + "name": "new", + "id": 1234, + "network_id": 5678, + "serial": "12345678", + "enabled": False, + "battery_voltage": 90, + "battery_state": "ok", + "temperature": 68, + "wifi_strength": 4, + "thumbnail": thumb_return, + "type": "test", + } + mock_resp.side_effect = [ + {"temp": 71}, + "test", + ] + self.camera.sync.blink.account_id = 9999 + self.camera.update(config) + self.assertEqual( + self.camera.thumbnail, f"https://rest-test.immedia-semi.com{thumb_return}" + ) diff --git a/tox.ini b/tox.ini index dcc71120..47a3ba0e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = build, py36, py37, py38, py39, lint +envlist = build, py37, py38, py39, py310, lint skip_missing_interpreters = True skipsdist = True @@ -7,40 +7,40 @@ skipsdist = True setenv = LANG=en_US.UTF-8 PYTHONPATH = {toxinidir} -commands = +commands = pytest --timeout=9 --durations=10 --cov=blinkpy --cov-report term-missing {posargs} -deps = +deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements_test.txt [testenv:cov] -setenv = +setenv = LANG=en_US.UTF-8 PYTHONPATH = {toxinidir} -commands = +commands = pytest --timeout=9 --durations=10 --cov=blinkpy --cov-report=xml {posargs} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements_test.txt [testenv:pylint] -deps = +deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements_test.txt basepython = python3 ignore_errors = True -commands = - pylint --rcfile={toxinidir}/pylintrc blinkpy tests app +commands = + pylint --rcfile={toxinidir}/pylintrc blinkpy tests blinkapp [testenv:lint] -deps = +deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements_test.txt basepython = python3 -commands = - flake8 blinkpy tests app - pydocstyle blinkpy tests app - black --check --diff blinkpy tests app +commands = + flake8 blinkpy tests blinkapp + pydocstyle blinkpy tests blinkapp + black --check --diff blinkpy tests blinkapp rst-lint README.rst CHANGES.rst CONTRIBUTING.rst [testenv:build] @@ -51,7 +51,7 @@ whitelist_externals = /bin/rm deps = -r{toxinidir}/requirements_test.txt -commands = +commands = /bin/rm -rf build dist python setup.py bdist_wheel /bin/sh -c "pip install --upgrade dist/*.whl"