From 8428cca6666faad4b3cf8e21aee9e75839b71964 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Wed, 16 Aug 2023 23:17:56 -0400 Subject: [PATCH 01/44] Update constants.py --- blinkpy/helpers/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blinkpy/helpers/constants.py b/blinkpy/helpers/constants.py index afd65231..cfc230c6 100644 --- a/blinkpy/helpers/constants.py +++ b/blinkpy/helpers/constants.py @@ -3,8 +3,8 @@ import os MAJOR_VERSION = 0 -MINOR_VERSION = 22 -PATCH_VERSION = 0 +MINOR_VERSION = 23 +PATCH_VERSION = "0.dev0" __version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}.{PATCH_VERSION}" From 8549e67c1674099e56087f56c7d0e6de492de791 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Thu, 17 Aug 2023 10:55:25 -0400 Subject: [PATCH 02/44] Cleanup readme, add breaking change warning --- README.rst | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index 347554a4..55d0a7a9 100644 --- a/README.rst +++ b/README.rst @@ -6,6 +6,9 @@ Like the library? Consider buying me a cup of coffee! `Buy me a Coffee! `__ +**BREAKING CHANGE WARNING:*** +As of ``0.22.0`` the library uses asyncio which will break any user scripts used prior to this version. Please see the updated examples below and the ``blinkapp.py`` or ``blinksync.py`` examples in the ``blinkapp/`` directory for examples on how to migrate. + **Disclaimer:** Published under the MIT license - See LICENSE file for more details. @@ -47,11 +50,17 @@ Quick Start The simplest way to use this package from a terminal is to call ``await Blink.start()`` which will prompt for your Blink username and password and then log you in. In addition, http requests are throttled internally via use of the ``Blink.refresh_rate`` variable, which can be set at initialization and defaults to 30 seconds. .. code:: python - + + import asyncio + from aiohttp import ClientSession from blinkpy.blinkpy import Blink - blink = Blink() - await blink.start() + async def start(): + blink = Blink(session=ClientSession()) + await blink.start() + return blink + + blink = asyncio.run(start()) This flow will prompt you for your username and password. Once entered, if you likely will need to send a 2FA key to the blink servers (this pin is sent to your email address). When you receive this pin, enter at the prompt and the Blink library will proceed with setup. @@ -62,14 +71,20 @@ In some cases, having an interactive command-line session is not desired. In th .. code:: python + import asyncio + from aiohttp import ClientSession from blinkpy.blinkpy import Blink from blinkpy.auth import Auth - blink = Blink() - # Can set no_prompt when initializing auth handler - auth = Auth({"username": , "password": }, no_prompt=True) - blink.auth = auth - await blink.start() + async def start(): + blink = Blink(session=ClientSession()) + # Can set no_prompt when initializing auth handler + auth = Auth({"username": , "password": }, no_prompt=True) + blink.auth = auth + await blink.start() + return blink + + blink = asyncio.run(start()) Since you will not be prompted for any 2FA pin, you must call the ``blink.auth.send_auth_key`` function. There are two required parameters: the ``blink`` object as well as the ``key`` you received from Blink for 2FA: @@ -86,14 +101,20 @@ Other use cases may involved loading credentials from a file. This file must be .. code:: python + import asyncio + from aiohttp import ClientSession from blinkpy.blinkpy import Blink from blinkpy.auth import Auth from blinkpy.helpers.util import json_load - blink = Blink() - auth = Auth(await json_load("")) - blink.auth = auth - await blink.start() + async def start(): + blink = Blink() + auth = Auth(await json_load("")) + blink.auth = auth + await blink.start() + return blink + + blink = asyncio.run(start()) Saving credentials From 31ce2d471db5d8d45142f13ae8a115da6732cb2d Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Thu, 17 Aug 2023 10:57:07 -0400 Subject: [PATCH 03/44] Typo --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 55d0a7a9..4d074f7c 100644 --- a/README.rst +++ b/README.rst @@ -6,7 +6,7 @@ Like the library? Consider buying me a cup of coffee! `Buy me a Coffee! `__ -**BREAKING CHANGE WARNING:*** +**BREAKING CHANGE WARNING:** As of ``0.22.0`` the library uses asyncio which will break any user scripts used prior to this version. Please see the updated examples below and the ``blinkapp.py`` or ``blinksync.py`` examples in the ``blinkapp/`` directory for examples on how to migrate. **Disclaimer:** From 655c77fa797b5731afaf2c444f0014043c72053e Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Thu, 17 Aug 2023 11:37:51 -0400 Subject: [PATCH 04/44] Updates for ruff --- blinkpy/camera.py | 53 +++++++++++++++++++++++++++++------- blinkpy/helpers/constants.py | 49 +++++---------------------------- blinkpy/helpers/util.py | 6 +++- blinkpy/sync_module.py | 30 ++++++++++++++------ 4 files changed, 76 insertions(+), 62 deletions(-) diff --git a/blinkpy/camera.py b/blinkpy/camera.py index 80d61d38..fd004477 100644 --- a/blinkpy/camera.py +++ b/blinkpy/camera.py @@ -258,7 +258,13 @@ async def update_images(self, config, force_cache=False, expire_clips=True): 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=" + thumb_string = ( + "/api/v3/media/accounts/" + f"{self.sync.blink.account_id}/networks/" + f"{self.network_id}/{self.product_type}/" + f"{self.camera_id}/thumbnail/" + f"thumbnail.jpg?ts={thumb_addr}&ext=" + ) except ValueError: # This is the old API and has the full url thumb_string = f"{thumb_addr}.jpg" @@ -305,7 +311,8 @@ def timest(record): f"Found {len(self.recent_clips)} recent clips for {self.name}" ) _LOGGER.debug( - f"Most recent clip for {self.name} was created at {self.last_record}: {self.clip}" + f"Most recent clip for {self.name} was created at " + f"{self.last_record}: {self.clip}" ) except (KeyError, IndexError): ex = traceback.format_exc() @@ -351,7 +358,8 @@ async def expire_recent_clips(self, delta=datetime.timedelta(hours=1)): self.recent_clips = copy.deepcopy(to_keep) if len(self.recent_clips) > 0: _LOGGER.info( - f"'{self.name}' has {len(self.recent_clips)} clips available for download" + f"'{self.name}' has {len(self.recent_clips)} " + "clips available for download" ) for clip in self.recent_clips: url = clip["clip"] @@ -435,7 +443,8 @@ async def save_recent_clips( _LOGGER.info(f"No recent clips to save for '{self.name}'.") else: _LOGGER.info( - f"Saved {num_saved} of {len(recent)} recent clips from '{self.name}' to directory {output_dir}" + f"Saved {num_saved} of {len(recent)} recent clips from " + f"'{self.name}' to directory {output_dir}" ) @@ -454,13 +463,21 @@ def arm(self): async def async_arm(self, value): """Set camera arm status.""" - url = f"{self.sync.urls.base_url}/api/v1/accounts/{self.sync.blink.account_id}/networks/{self.network_id}/owls/{self.camera_id}/config" + url = ( + f"{self.sync.urls.base_url}/api/v1/accounts/" + f"{self.sync.blink.account_id}/networks/" + f"{self.network_id}/owls/{self.camera_id}/config" + ) data = dumps({"enabled": value}) return await api.http_post(self.sync.blink, url, json=False, data=data) async def snap_picture(self): """Snap picture for a blink mini camera.""" - url = f"{self.sync.urls.base_url}/api/v1/accounts/{self.sync.blink.account_id}/networks/{self.network_id}/owls/{self.camera_id}/thumbnail" + url = ( + f"{self.sync.urls.base_url}/api/v1/accounts/" + f"{self.sync.blink.account_id}/networks/" + f"{self.network_id}/owls/{self.camera_id}/thumbnail" + ) return await api.http_post(self.sync.blink, url) async def get_sensor_info(self): @@ -468,7 +485,11 @@ async def get_sensor_info(self): async def get_liveview(self): """Get liveview link.""" - url = f"{self.sync.urls.base_url}/api/v1/accounts/{self.sync.blink.account_id}/networks/{self.network_id}/owls/{self.camera_id}/liveview" + url = ( + f"{self.sync.urls.base_url}/api/v1/accounts/" + f"{self.sync.blink.account_id}/networks/" + f"{self.network_id}/owls/{self.camera_id}/liveview" + ) response = await api.http_post(self.sync.blink, url) server = response["server"] server_split = server.split(":") @@ -492,7 +513,11 @@ def arm(self): async def async_arm(self, value): """Set camera arm status.""" - url = f"{self.sync.urls.base_url}/api/v1/accounts/{self.sync.blink.account_id}/networks/{self.sync.network_id}/doorbells/{self.camera_id}" + url = ( + f"{self.sync.urls.base_url}/api/v1/accounts/" + f"{self.sync.blink.account_id}/networks/" + f"{self.sync.network_id}/doorbells/{self.camera_id}" + ) if value: url = f"{url}/enable" else: @@ -501,7 +526,11 @@ async def async_arm(self, value): async def snap_picture(self): """Snap picture for a blink doorbell camera.""" - url = f"{self.sync.urls.base_url}/api/v1/accounts/{self.sync.blink.account_id}/networks/{self.sync.network_id}/doorbells/{self.camera_id}/thumbnail" + url = ( + f"{self.sync.urls.base_url}/api/v1/accounts/" + f"{self.sync.blink.account_id}/networks/" + f"{self.sync.network_id}/doorbells/{self.camera_id}/thumbnail" + ) return await api.http_post(self.sync.blink, url) async def get_sensor_info(self): @@ -509,7 +538,11 @@ async def get_sensor_info(self): async def get_liveview(self): """Get liveview link.""" - url = f"{self.sync.urls.base_url}/api/v1/accounts/{self.sync.blink.account_id}/networks/{self.sync.network_id}/doorbells/{self.camera_id}/liveview" + url = ( + f"{self.sync.urls.base_url}/api/v1/accounts/" + f"{self.sync.blink.account_id}/networks/" + f"{self.sync.network_id}/doorbells/{self.camera_id}/liveview" + ) response = await api.http_post(self.sync.blink, url) server = response["server"] link = server.replace("immis://", "rtsps://") diff --git a/blinkpy/helpers/constants.py b/blinkpy/helpers/constants.py index cfc230c6..b208f56c 100644 --- a/blinkpy/helpers/constants.py +++ b/blinkpy/helpers/constants.py @@ -1,47 +1,8 @@ """Generates constants for use in blinkpy.""" -import os +import importlib.metadata -MAJOR_VERSION = 0 -MINOR_VERSION = 23 -PATCH_VERSION = "0.dev0" - -__version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}.{PATCH_VERSION}" - -REQUIRED_PYTHON_VER = (3, 8, 0) - -PROJECT_NAME = "blinkpy" -PROJECT_PACKAGE_NAME = "blinkpy" -PROJECT_LICENSE = "MIT" -PROJECT_AUTHOR = "Kevin Fronczak" -PROJECT_COPYRIGHT = f" 2017, {PROJECT_AUTHOR}" -PROJECT_URL = "https://github.com/fronzbot/blinkpy" -PROJECT_EMAIL = "kfronczak@gmail.com" -PROJECT_DESCRIPTION = "A Blink camera Python library " "running on Python 3." -PROJECT_LONG_DESCRIPTION = ( - "blinkpy is an open-source " - "unofficial API for the Blink Camera " - "system with the intention for easy " - "integration into various home " - "automation platforms." -) -if os.path.exists("README.rst"): - PROJECT_LONG_DESCRIPTION = open("README.rst").read() -PROJECT_CLASSIFIERS = [ - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Topic :: Home Automation", -] - -PROJECT_GITHUB_USERNAME = "fronzbot" -PROJECT_GITHUB_REPOSITORY = "blinkpy" - -PYPI_URL = f"https://pypi.python.org/pypi/{PROJECT_PACKAGE_NAME}" +__version__ = importlib.metadata.version("blinkpy") """ URLS @@ -59,7 +20,11 @@ """ OTHER """ -DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36" +DEFAULT_USER_AGENT = ( + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/71.0.3578.98 Safari/537.36" +) DEVICE_ID = "Blinkpy" TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S%z" DEFAULT_MOTION_INTERVAL = 1 diff --git a/blinkpy/helpers/util.py b/blinkpy/helpers/util.py index 749a0cb3..2f1c16cd 100644 --- a/blinkpy/helpers/util.py +++ b/blinkpy/helpers/util.py @@ -40,7 +40,11 @@ async def json_save(data, file_name): def gen_uid(size, uid_format=False): """Create a random sring.""" if uid_format: - token = f"BlinkCamera_{secrets.token_hex(4)}-{secrets.token_hex(2)}-{secrets.token_hex(2)}-{secrets.token_hex(2)}-{secrets.token_hex(6)}" + token = ( + f"BlinkCamera_{secrets.token_hex(4)}-" + f"{secrets.token_hex(2)}-{secrets.token_hex(2)}-" + f"{secrets.token_hex(2)}-{secrets.token_hex(6)}" + ) else: token = secrets.token_hex(size) return token diff --git a/blinkpy/sync_module.py b/blinkpy/sync_module.py index cc801dc3..48810764 100644 --- a/blinkpy/sync_module.py +++ b/blinkpy/sync_module.py @@ -283,7 +283,8 @@ async def check_new_videos(self): # No need to check for motion. ex = traceback.format_exc() _LOGGER.error( - f"Error calculating interval (last_refresh={self.blink.last_refresh}): {ex}" + "Error calculating interval " + f"(last_refresh={self.blink.last_refresh}): {ex}" ) trace = "".join(traceback.format_stack()) _LOGGER.debug(f"\n{trace}") @@ -347,12 +348,14 @@ async def check_new_videos(self): iso_timestamp = item.created_at.isoformat() _LOGGER.debug( - f"Checking '{item.name}': clip_time={iso_timestamp}, manifest_read={last_manifest_read}" + f"Checking '{item.name}': clip_time={iso_timestamp}, " + f"manifest_read={last_manifest_read}" ) # Exit the loop once there are no new videos in the list. if not self.check_new_video_time(iso_timestamp, last_manifest_read): _LOGGER.info( - f"No new local storage videos since last manifest read at {last_read_local}." + "No new local storage videos since last manifest " + f"read at {last_read_local}." ) break _LOGGER.debug(f"Found new item in local storage manifest: {item}") @@ -446,7 +449,8 @@ async def update_local_storage_manifest(self): num_added = len(self._local_storage["manifest"]) - num_stored if num_added > 0: _LOGGER.info( - f"Found {num_added} new clip(s) in local storage manifest id={manifest_id}" + f"Found {num_added} new clip(s) in local storage " + f"manifest id={manifest_id}" ) except (TypeError, KeyError): ex = traceback.format_exc() @@ -463,7 +467,8 @@ async def poll_local_storage_manifest( self, manifest_request_id=None, max_retries=4 ): """Poll for local storage manifest.""" - # The sync module may be busy processing another request (like saving a new clip). + # The sync module may be busy processing another request + # (like saving a new clip). # Poll the endpoint until it is ready, backing off each retry. response = None for retry in range(max_retries): @@ -659,7 +664,9 @@ def size(self): return self._size def url(self, manifest_id=None): - """Build the URL new each time since the media item is cached, and the manifest is possibly rebuilt each refresh. + """ + Build the URL new each time since the media item is cached, + and the manifest is possibly rebuilt each refresh. :param manifest_id: ID of new manifest (if it changed) :return: URL for clip retrieval @@ -716,7 +723,10 @@ async def download_video(self, blink, file_name, max_retries=4) -> bool: return False async def download_video_delete(self, blink, file_name, max_retries=4) -> bool: - """Initiate upload of media item from the sync module to Blink cloud servers then download to local filesystem and delete from sync.""" + """ + Initiate upload of media item from the sync module to + Blink cloud servers then download to local filesystem and delete from sync. + """ if await self.prepare_download(blink): if await self.download_video(blink, file_name): if await self.delete_video(blink): @@ -726,8 +736,10 @@ async def download_video_delete(self, blink, file_name, max_retries=4) -> bool: def __repr__(self): """Create string representation.""" return ( - f"LocalStorageMediaItem(id={self._id}, camera_name={self._camera_name}, created_at={self._created_at}" - + f", size={self._size}, manifest_id={self._manifest_id}, url_template={self._url_template})" + f"LocalStorageMediaItem(id={self._id}, camera_name={self._camera_name}, " + f"created_at={self._created_at}" + + f", size={self._size}, manifest_id={self._manifest_id}, " + f"url_template={self._url_template})" ) def __str__(self): From aa869cc66b217ddeae7ac92c63fd352608151784 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Thu, 17 Aug 2023 12:02:39 -0400 Subject: [PATCH 05/44] Migrate to ruff + pyproject.toml --- .github/workflows/lint.yml | 2 +- .github/workflows/publish.yml | 8 +-- .github/workflows/tests.yml | 4 +- .pre-commit-config.yaml | 22 -------- blinkpy/api.py | 60 +++++++++++++++------ blinkpy/blinkpy.py | 13 +++-- blinkpy/camera.py | 6 ++- blinkpy/sync_module.py | 17 +++--- pyproject.toml | 95 ++++++++++++++++++++++++++++++++++ requirements_test.txt | 7 +-- setup.cfg | 18 ------- setup.py | 44 ---------------- tests/test_blink_functions.py | 12 ++--- tests/test_camera_functions.py | 11 ++-- tests/test_cameras.py | 5 +- tests/test_sync_module.py | 2 +- tox.ini | 16 ++---- 17 files changed, 191 insertions(+), 151 deletions(-) delete mode 100644 .pre-commit-config.yaml create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9ab94725..58510477 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 473af5d3..517d64fa 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,17 +15,17 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: - python-version: '3.8' + python-version: '3.9' - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine + pip install twine build - name: Build and publish env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | - python setup.py bdist_wheel + python -m build twine upload dist/* diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d00bfa76..bb094e03 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: matrix: platform: - ubuntu-latest - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v3 @@ -28,6 +28,6 @@ jobs: pip install -r requirements.txt pip install -r requirements_test.txt pip install tox - - name: Test + - name: Tests run: | tox -r diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index bebf31fc..00000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,22 +0,0 @@ -repos: - - repo: https://github.com/psf/black - rev: 22.1.0 - hooks: - - id: black - args: - - --safe - - --quiet - files: ^((blinkpy|tests)/.+)?[^/]+\.py$ - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.1 - hooks: - - id: flake8 - additional_dependencies: - - 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 - hooks: - - id: rst-linter - files: /.+\.rst$ diff --git a/blinkpy/api.py b/blinkpy/api.py index 0fa5bb3f..c8ed9155 100644 --- a/blinkpy/api.py +++ b/blinkpy/api.py @@ -57,7 +57,10 @@ async def request_login( async def request_verify(auth, blink, verify_key): """Send verification key to blink servers.""" - url = f"{blink.urls.base_url}/api/v4/account/{blink.account_id}/client/{blink.client_id}/pin/verify" + url = ( + f"{blink.urls.base_url}/api/v4/account/{blink.account_id}" + f"/client/{blink.client_id}/pin/verify" + ) data = dumps({"pin": verify_key}) return await auth.query( url=url, @@ -70,7 +73,10 @@ async def request_verify(auth, blink, verify_key): async def request_logout(blink): """Logout of blink servers.""" - url = f"{blink.urls.base_url}/api/v4/account/{blink.account_id}/client/{blink.client_id}/logout" + url = ( + f"{blink.urls.base_url}/api/v4/account/{blink.account_id}" + f"/client/{blink.client_id}/logout" + ) return await http_post(blink, url=url) @@ -127,7 +133,10 @@ async def request_system_arm(blink, network): :param blink: Blink instance. :param network: Sync module network id. """ - url = f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}/networks/{network}/state/arm" + url = ( + f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}" + f"/networks/{network}/state/arm" + ) return await http_post(blink, url) @@ -139,7 +148,10 @@ async def request_system_disarm(blink, network): :param blink: Blink instance. :param network: Sync module network id. """ - url = f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}/networks/{network}/state/disarm" + url = ( + f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}" + f"/networks/{network}/state/disarm" + ) return await http_post(blink, url) @@ -216,7 +228,10 @@ async def request_videos(blink, time=None, page=0): :param page: Page number to get videos from. """ timestamp = get_time(time) - url = f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}/media/changed?since={timestamp}&page={page}" + url = ( + f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}" + f"/media/changed?since={timestamp}&page={page}" + ) return await http_get(blink, url) @@ -261,7 +276,10 @@ async def request_camera_liveview(blink, network, camera_id): :param network: Sync module network id. :param camera_id: Camera ID of camera to request liveview from. """ - url = f"{blink.urls.base_url}/api/v5/accounts/{blink.account_id}/networks/{network}/cameras/{camera_id}/liveview" + url = ( + f"{blink.urls.base_url}/api/v5/accounts/{blink.account_id}" + f"/networks/{network}/cameras/{camera_id}/liveview" + ) return await http_post(blink, url) @@ -303,15 +321,19 @@ async def request_motion_detection_disable(blink, network, camera_id): async def request_local_storage_manifest(blink, network, sync_id): - """Request creation of an updated manifest of video clips stored in sync module local storage. + """Update local manifest. + + Request creation of an updated manifest of video clips stored in + sync module local storage. :param blink: Blink instance. :param network: Sync module network id. :param sync_id: ID of sync module. """ url = ( - f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}/networks/{network}/sync_modules/{sync_id}" - + "/local_storage/manifest/request" + f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}" + f"/networks/{network}/sync_modules/{sync_id}" + f"/local_storage/manifest/request" ) return await http_post(blink, url) @@ -322,11 +344,13 @@ async def get_local_storage_manifest(blink, network, sync_id, manifest_request_i :param blink: Blink instance. :param network: Sync module network id. :param sync_id: ID of sync module. - :param manifest_request_id: Request ID of local storage manifest (requested creation of new manifest). + :param manifest_request_id: Request ID of local storage manifest \ + (requested creation of new manifest). """ url = ( - f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}/networks/{network}/sync_modules/{sync_id}" - + f"/local_storage/manifest/request/{manifest_request_id}" + f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}" + f"/networks/{network}/sync_modules/{sync_id}" + f"/local_storage/manifest/request/{manifest_request_id}" ) return await http_get(blink, url) @@ -337,7 +361,7 @@ async def request_local_storage_clip(blink, network, sync_id, manifest_id, clip_ :param blink: Blink instance. :param network: Sync module network id. :param sync_id: ID of sync module. - :param manifest_id: ID of local storage manifest (returned in the manifest response). + :param manifest_id: ID of local storage manifest (returned in manifest response). :param clip_id: ID of the clip. """ url = blink.urls.base_url + string.Template( @@ -361,7 +385,10 @@ async def request_get_config(blink, network, camera_id, product_type="owl"): :param product_type: Camera product type "owl" or "catalina" """ if product_type == "owl": - url = f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}/networks/{network}/owls/{camera_id}/config" + url = ( + f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}" + f"/networks/{network}/owls/{camera_id}/config" + ) elif product_type == "catalina": url = f"{blink.urls.base_url}/network/{network}/camera/{camera_id}/config" else: @@ -386,7 +413,10 @@ async def request_update_config( :param data: string w/JSON dict of parameters/values to update """ if product_type == "owl": - url = f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}/networks/{network}/owls/{camera_id}/update" + url = ( + f"{blink.urls.base_url}/api/v1/accounts/" + f"{blink.account_id}/networks/{network}/owls/{camera_id}/update" + ) elif product_type == "catalina": url = f"{blink.urls.base_url}/network/{network}/camera/{camera_id}/update" else: diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index 8f81037d..8bfa4566 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ blinkpy is an unofficial api for the Blink security camera system. @@ -58,7 +57,9 @@ def __init__( Defaults to last refresh time. Useful for preventing motion_detected property from de-asserting too quickly. - :param no_owls: Disable searching for owl entries (blink mini cameras only known entity). Prevents an uneccessary API call if you don't have these in your network. + :param no_owls: Disable searching for owl entries (blink mini cameras \ + only known entity). Prevents an uneccessary API call \ + if you don't have these in your network. """ self.auth = Auth(session=session) self.account_id = None @@ -128,7 +129,7 @@ async def start(self): self.last_refresh = int(time.time() - self.refresh_rate * 1.05) _LOGGER.debug( f"Initialized last_refresh to {self.last_refresh} == " - + f"{datetime.datetime.fromtimestamp(self.last_refresh)}" + f"{datetime.datetime.fromtimestamp(self.last_refresh)}" ) return await self.setup_post_verify() @@ -421,10 +422,8 @@ async def _parse_downloaded_items(self, result, camera, path, delay, debug): _LOGGER.info("Downloaded video to %s", filename) else: print( - ( - f"Camera: {camera_name}, Timestamp: {created_at}, " - f"Address: {address}, Filename: {filename}" - ) + f"Camera: {camera_name}, Timestamp: {created_at}, " + f"Address: {address}, Filename: {filename}" ) if delay > 0: time.sleep(delay) diff --git a/blinkpy/camera.py b/blinkpy/camera.py index fd004477..eeb850e2 100644 --- a/blinkpy/camera.py +++ b/blinkpy/camera.py @@ -30,7 +30,8 @@ def __init__(self, sync): self.motion_enabled = 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. + # A clip remains in the recent clips list until is has + # been downloaded or has been expired. self.recent_clips = [] self.temperature = None self.temperature_calibrated = None @@ -203,7 +204,8 @@ async def snap_picture(self): async def set_motion_detect(self, enable): """Set motion detection.""" _LOGGER.warning( - "Method is deprecated as of v0.16.0 and will be removed in a future version. Please use the BlinkCamera.arm property instead." + "Method is deprecated as of v0.16.0 and will be removed in " + "a future version. Please use the BlinkCamera.arm property instead." ) if enable: return await api.request_motion_detection_enable( diff --git a/blinkpy/sync_module.py b/blinkpy/sync_module.py index 48810764..f7d47b4d 100644 --- a/blinkpy/sync_module.py +++ b/blinkpy/sync_module.py @@ -38,7 +38,8 @@ def __init__(self, blink, network_name, network_id, camera_list): self.cameras = CaseInsensitiveDict({}) self.motion_interval = blink.motion_interval self.motion = {} - # A dictionary where keys are the camera names, and values are lists of recent clips. + # A dictionary where keys are the camera names, and + # values are lists of recent clips. self.last_records = {} self.camera_list = camera_list self.available = False @@ -46,7 +47,7 @@ def __init__(self, blink, network_name, network_id, camera_list): "mini": "owls", "doorbell": "doorbells", } - self._names_table = dict() + self._names_table = {} self._local_storage = { "enabled": False, "compatible": False, @@ -294,7 +295,7 @@ async def check_new_videos(self): resp = await api.request_videos(self.blink, time=interval, page=1) last_record = {} - for camera in self.cameras.keys(): + for camera in self.cameras: # Initialize the list if doesn't exist yet. if camera not in self.last_records: self.last_records[camera] = [] @@ -377,7 +378,7 @@ async def check_new_videos(self): _LOGGER.debug(f"Updated last_manifest_read to {last_manifest_read}") _LOGGER.debug(f"Last clip time was {last_clip_time}") # We want to keep the last record when no new motion was detected. - for camera in self.cameras.keys(): + for camera in self.cameras: # Check if there are no new records, indicating motion. if len(self.last_records[camera]) == 0: # If no new records, check if we had a previous last record. @@ -664,8 +665,9 @@ def size(self): return self._size def url(self, manifest_id=None): - """ - Build the URL new each time since the media item is cached, + """Build the URL. + + Builds the url new each time since the media item is cached, and the manifest is possibly rebuilt each refresh. :param manifest_id: ID of new manifest (if it changed) @@ -723,7 +725,8 @@ async def download_video(self, blink, file_name, max_retries=4) -> bool: return False async def download_video_delete(self, blink, file_name, max_retries=4) -> bool: - """ + """Delete local videos. + Initiate upload of media item from the sync module to Blink cloud servers then download to local filesystem and delete from sync. """ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..09d88fdc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,95 @@ +[build-system] +requires = ["setuptools~=68.0", "wheel~=0.40.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "blinkpy" +version = "0.23.0.dev0" +license = {text = "MIT"} +description = "A Blink camera Python Library." +readme = "README.rst" +authors = [ + {name = "Kevin Fronczak", email = "kfronczak@gmail.com"}, +] +classifiers = [ + "Intended Audience :: Developers", + "License :: OSI Apporved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Home Automation", +] +requires-python = ">=3.8.0" +dynamic = ["dependencies"] + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + +[project.urls] +"Source Code" = "https://github.com/fronzbot/blinkpy" +"Bug Reports" = "https://github.com/fronzbot/blinkpy/issues" + +[tool.setuptools] +platforms = ["any"] +include-package-data = true + +[tool.setuptools.packages.find] +include = ["blinkpy*"] + +[tool.ruff] +select = [ + "C", # complexity + "D", # docstrings + "E", # pydocstyle + "F", # pyflakes/autoflake + "G", # flake8-logging-format + "N815", # Varible {name} in class scope should not be mixedCase + "PGH004", # Use specific rule codes when using noqa + "PLC", # pylint + "PLE", # pylint + "PLR", # pylint + "PLW", # pylint + "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 + "UP", # pyupgrade + "W", # pycodestyle +] +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 + "D213", # Multi-line docstring summary should start at the second line + "D406", # Section name should end with a newline + "D407", # Section name underlining + "E731", # do not assign a lambda expression, use a def + "G004", # I don't care if logging uses an f string + "PLC1901", # Lots of false positives + # False positives https://github.com/astral-sh/ruff/issues/5386 + "PLC0208", # Use a sequence type instead of a `set` when iterating over values + "PLR0911", # Too many return statements ({returns} > {max_returns}) + "PLR0912", # Too many branches ({branches} > {max_branches}) + "PLR0913", # Too many arguments to function call ({c_args} > {max_args}) + "PLR0915", # Too many statements ({statements} > {max_statements}) + "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable + "PLW2901", # Outer {outer_kind} variable {name} overwritten by inner {inner_kind} target + "UP006", # keep type annotation style as is + "UP007", # keep type annotation style as is + "UP015", # Unnecessary open mode parameters + # Ignored due to performance: https://github.com/charliermarsh/ruff/issues/2923 + "UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)` +] + +line-length = 88 + +target-version = "py39" + +[tool.ruff.per-file-ignores] + + +[tool.ruff.mccabe] +max-complexity = 25 diff --git a/requirements_test.txt b/requirements_test.txt index 0106e919..17d0c943 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,10 +1,7 @@ +ruff==0.0.278 black==23.7.0 +build==0.10.0 coverage==7.3.0 -flake8==6.1.0 -pre-commit==3.0.4 -flake8-docstrings==1.7.0 -pylint==2.17.5 -pydocstyle==6.3.0 pytest==7.4.0 pytest-cov==4.1.0 pytest-sugar==0.9.7 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 72b7313c..00000000 --- a/setup.cfg +++ /dev/null @@ -1,18 +0,0 @@ -[flake8] -exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build -doctests = True -# To work with Black -max-line-length = 88 - -# E501: line too long -# W503: Line break occurred before a binary operator -# E203: Whitespace before ':' -# D202 No blank lines allowed after function docstring -# W504 line break after binary operator - -ignore = - E501, - W503, - E203, - D202, - W504, diff --git a/setup.py b/setup.py deleted file mode 100644 index fc0b76f9..00000000 --- a/setup.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Setup file for blinkpy.""" -# -*- coding: utf-8 -*- -from os.path import abspath, dirname -from setuptools import setup, find_packages -from blinkpy.helpers.constants import ( - __version__, - PROJECT_PACKAGE_NAME, - PROJECT_LICENSE, - PROJECT_URL, - PROJECT_EMAIL, - PROJECT_DESCRIPTION, - PROJECT_CLASSIFIERS, - PROJECT_AUTHOR, -) - -PROJECT_VERSION = __version__ - -THIS_DIR = abspath(dirname(__file__)) - -with open(f"{THIS_DIR}/requirements.txt") as req_file: - REQUIRES = [line.rstrip() for line in req_file] - -PACKAGES = find_packages(exclude=["tests*", "docs"]) - -with open("{}/README.rst".format(THIS_DIR), encoding="utf-8") as readme_file: - LONG_DESCRIPTION = readme_file.read() - -setup( - name=PROJECT_PACKAGE_NAME, - version=PROJECT_VERSION, - description=PROJECT_DESCRIPTION, - long_description=LONG_DESCRIPTION, - author=PROJECT_AUTHOR, - author_email=PROJECT_EMAIL, - license=PROJECT_LICENSE, - url=PROJECT_URL, - platforms="any", - py_modules=["blinkpy"], - packages=PACKAGES, - include_package_data=True, - install_requires=REQUIRES, - test_suite="tests", - classifiers=PROJECT_CLASSIFIERS, -) diff --git a/tests/test_blink_functions.py b/tests/test_blink_functions.py index 7b890b84..bb3326ab 100644 --- a/tests/test_blink_functions.py +++ b/tests/test_blink_functions.py @@ -62,7 +62,7 @@ async def test_download_video_exit(self, mock_req): mock_req.return_value = {} formatted_date = get_time(blink.last_refresh) expected_log = [ - "INFO:blinkpy.blinkpy:Retrieving videos since {}".format(formatted_date), + f"INFO:blinkpy.blinkpy:Retrieving videos since {formatted_date}", "DEBUG:blinkpy.blinkpy:Processing page 1", "INFO:blinkpy.blinkpy:No videos found on page 1. Exiting.", ] @@ -85,7 +85,7 @@ async def test_parse_downloaded_items(self, mock_req): blink.last_refresh = 0 formatted_date = get_time(blink.last_refresh) expected_log = [ - "INFO:blinkpy.blinkpy:Retrieving videos since {}".format(formatted_date), + f"INFO:blinkpy.blinkpy:Retrieving videos since {formatted_date}", "DEBUG:blinkpy.blinkpy:Processing page 1", "DEBUG:blinkpy.blinkpy:foo: /bar.mp4 is marked as deleted.", ] @@ -164,7 +164,7 @@ async def test_download_videos_deleted(self, mock_req): self.blink.last_refresh = 0 formatted_date = get_time(self.blink.last_refresh) expected_log = [ - "INFO:blinkpy.blinkpy:Retrieving videos since {}".format(formatted_date), + f"INFO:blinkpy.blinkpy:Retrieving videos since {formatted_date}", "DEBUG:blinkpy.blinkpy:Processing page 1", "DEBUG:blinkpy.blinkpy:foo: /bar.mp4 is marked as deleted.", ] @@ -214,7 +214,7 @@ async def test_download_videos_file_exists(self, mock_isfile, mock_req): self.blink.last_refresh = 0 formatted_date = get_time(self.blink.last_refresh) expected_log = [ - "INFO:blinkpy.blinkpy:Retrieving videos since {}".format(formatted_date), + f"INFO:blinkpy.blinkpy:Retrieving videos since {formatted_date}", "DEBUG:blinkpy.blinkpy:Processing page 1", "INFO:blinkpy.blinkpy:/tmp/foo-1970.mp4 already exists, skipping...", ] @@ -236,7 +236,7 @@ async def test_parse_camera_not_in_list(self, mock_req): self.blink.last_refresh = 0 formatted_date = get_time(self.blink.last_refresh) expected_log = [ - "INFO:blinkpy.blinkpy:Retrieving videos since {}".format(formatted_date), + f"INFO:blinkpy.blinkpy:Retrieving videos since {formatted_date}", "DEBUG:blinkpy.blinkpy:Processing page 1", "DEBUG:blinkpy.blinkpy:Skipping videos for foo.", ] @@ -255,7 +255,7 @@ async def test_parse_malformed_entry(self, mock_req): result = [generic_entry] mock_req.return_value = {"media": result} expected_log = [ - "INFO:blinkpy.blinkpy:Retrieving videos since {}".format(formatted_date), + f"INFO:blinkpy.blinkpy:Retrieving videos since {formatted_date}", "DEBUG:blinkpy.blinkpy:Processing page 1", "INFO:blinkpy.blinkpy:Missing clip information, skipping...", ] diff --git a/tests/test_camera_functions.py b/tests/test_camera_functions.py index 5231674d..f90a9582 100644 --- a/tests/test_camera_functions.py +++ b/tests/test_camera_functions.py @@ -158,7 +158,11 @@ async def test_no_video_clips(self, mock_resp): self.assertEqual(self.camera.video_from_cache, None) async def test_recent_video_clips(self, mock_resp): - """Tests that the last records in the sync module are added to the camera recent clips list.""" + """Test recent video clips. + + Tests that the last records in the sync module are added + to the camera recent clips list. + """ config = { "name": "new", "id": 1234, @@ -311,7 +315,7 @@ async def test_image_to_file_error(self, mock_open, mock_resp): self.camera.thumbnail = "/thumbnail" with self.assertLogs(level="DEBUG") as dl_log: await self.camera.image_to_file("my_path") - self.assertEquals( + self.assertEqual( dl_log.output[2], "ERROR:blinkpy.camera:Cannot write image to file, response 400", ) @@ -366,6 +370,7 @@ async def test_save_recent_clips(self, mock_clip, mock_open, mock_resp): await self.camera.save_recent_clips() self.assertEqual( dl_log.output[4], - f"INFO:blinkpy.camera:Saved 2 of 2 recent clips from '{self.camera.name}' to directory /tmp/", + "INFO:blinkpy.camera:Saved 2 of 2 recent clips from " + f"'{self.camera.name}' to directory /tmp/", ) assert mock_open.call_count == 2 diff --git a/tests/test_cameras.py b/tests/test_cameras.py index 660c9648..8689237f 100644 --- a/tests/test_cameras.py +++ b/tests/test_cameras.py @@ -173,7 +173,10 @@ async def test_thumb_return_none(self, mock_resp): async 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=" + thumb_return = ( + "/api/v3/media/accounts/9999/networks/5678/" + "test/1234/thumbnail/thumbnail.jpg?ts=1357924680&ext=" + ) config = { "name": "new", "id": 1234, diff --git a/tests/test_sync_module.py b/tests/test_sync_module.py index e07b83d2..8d01ee11 100644 --- a/tests/test_sync_module.py +++ b/tests/test_sync_module.py @@ -527,7 +527,7 @@ async def test_local_storage_media_item(self, mock_resp): {"network_id": 123456}, ] - self.assertEquals( + self.assertEqual( await item.prepare_download(blink, max_retries=1), {"network_id": 123456} ) diff --git a/tox.ini b/tox.ini index 0e1020fc..1f2ee3cc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = build, py37, py38, py39, py310, lint +envlist = build, py38, py39, py310, py311, py312, lint skip_missing_interpreters = True skipsdist = True @@ -23,23 +23,13 @@ deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements_test.txt -[testenv:pylint] -deps = - -r{toxinidir}/requirements.txt - -r{toxinidir}/requirements_test.txt -basepython = python3 -ignore_errors = True -commands = - pylint --rcfile={toxinidir}/pylintrc blinkpy tests blinkapp - [testenv:lint] deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements_test.txt basepython = python3 commands = - flake8 blinkpy tests blinkapp - pydocstyle blinkpy tests blinkapp + ruff check blinkpy tests blinkapp black --check --diff blinkpy tests blinkapp rst-lint README.rst CHANGES.rst CONTRIBUTING.rst @@ -53,6 +43,6 @@ deps = -r{toxinidir}/requirements_test.txt commands = /bin/rm -rf build dist - python setup.py bdist_wheel + python -m build /bin/sh -c "pip install --upgrade dist/*.whl" py.test tests From 3e0baa192e4b1cb48c17785e2eda967207a5b28d Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Thu, 17 Aug 2023 12:07:14 -0400 Subject: [PATCH 06/44] Fix workflow version --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 8715bf2d..9557bdc7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From 6d9fd3aff74001bea8e6850750e4dfa06c182d53 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Thu, 17 Aug 2023 12:18:49 -0400 Subject: [PATCH 07/44] Trying to fix github workflows --- .github/workflows/coverage.yml | 6 +++--- .github/workflows/tests.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 9557bdc7..5b036714 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -10,14 +10,14 @@ jobs: coverage: runs-on: ${{ matrix.platform }} strategy: - max-parallel: 4 + max-parallel: 1 matrix: platform: - ubuntu-latest - python-version: [3.9] + python-version: ['3.9'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bb094e03..372ba5ff 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: matrix: platform: - ubuntu-latest - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12.0-rc.1'] steps: - uses: actions/checkout@v3 From c69c52e28163a9bd5e675d21bb0d6d3905583857 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Thu, 17 Aug 2023 12:28:02 -0400 Subject: [PATCH 08/44] Remove 3.12 because aiohttp doesn't support it yet --- .github/workflows/tests.yml | 2 +- pyproject.toml | 1 - tox.ini | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 372ba5ff..4b8812e3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: matrix: platform: - ubuntu-latest - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12.0-rc.1'] + python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 diff --git a/pyproject.toml b/pyproject.toml index 09d88fdc..37a774d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,6 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", "Topic :: Home Automation", ] requires-python = ">=3.8.0" diff --git a/tox.ini b/tox.ini index 1f2ee3cc..dc228048 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = build, py38, py39, py310, py311, py312, lint +envlist = build, py38, py39, py310, py311, lint skip_missing_interpreters = True skipsdist = True @@ -18,6 +18,7 @@ setenv = LANG=en_US.UTF-8 PYTHONPATH = {toxinidir} commands = + pip install -e . pytest --timeout=9 --durations=10 --cov=blinkpy --cov-report=xml {posargs} deps = -r{toxinidir}/requirements.txt From a3edf0f0ebfc9d8ac15d521fc0a89fece0e61976 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Aug 2023 18:39:20 +0000 Subject: [PATCH 09/44] Bump ruff from 0.0.278 to 0.0.284 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.278 to 0.0.284. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.0.278...v0.0.284) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 17d0c943..62592d81 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -ruff==0.0.278 +ruff==0.0.284 black==23.7.0 build==0.10.0 coverage==7.3.0 From 027473814b8566a444a23d63bc79df5b9f6c4c7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Aug 2023 18:04:10 +0000 Subject: [PATCH 10/44] Bump ruff from 0.0.284 to 0.0.285 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.284 to 0.0.285. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.0.284...v0.0.285) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 62592d81..2602eadd 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -ruff==0.0.284 +ruff==0.0.285 black==23.7.0 build==0.10.0 coverage==7.3.0 From 84818c5c8881b4e9a69edd437e88d910cbf00504 Mon Sep 17 00:00:00 2001 From: cocasema Date: Fri, 18 Aug 2023 13:40:13 -0700 Subject: [PATCH 11/44] Fix night vision toggling for older devices (owl) Fixes: #755 --- blinkpy/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blinkpy/api.py b/blinkpy/api.py index c8ed9155..74a68dfc 100644 --- a/blinkpy/api.py +++ b/blinkpy/api.py @@ -415,7 +415,7 @@ async def request_update_config( if product_type == "owl": url = ( f"{blink.urls.base_url}/api/v1/accounts/" - f"{blink.account_id}/networks/{network}/owls/{camera_id}/update" + f"{blink.account_id}/networks/{network}/owls/{camera_id}/config" ) elif product_type == "catalina": url = f"{blink.urls.base_url}/network/{network}/camera/{camera_id}/update" From ea8fa961cdfd6fd973e216009f065c15dab55404 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Sep 2023 18:54:52 +0000 Subject: [PATCH 12/44] Bump ruff from 0.0.285 to 0.0.287 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.285 to 0.0.287. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.0.285...v0.0.287) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 2602eadd..674833a7 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -ruff==0.0.285 +ruff==0.0.287 black==23.7.0 build==0.10.0 coverage==7.3.0 From 58d2ee3d31060487e0807039d8ab7532b613620d Mon Sep 17 00:00:00 2001 From: mkmer Date: Tue, 12 Sep 2023 07:36:50 -0400 Subject: [PATCH 13/44] Update blinkapp.py Add missing await for saving CREDFILE --- blinkapp/blinkapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blinkapp/blinkapp.py b/blinkapp/blinkapp.py index 69d300e0..17c254e5 100644 --- a/blinkapp/blinkapp.py +++ b/blinkapp/blinkapp.py @@ -34,7 +34,7 @@ async def main(): session = ClientSession() blink = await start(session) await download_videos(blink) - blink.save(CREDFILE) + await blink.save(CREDFILE) await session.close() From 62bf6c478cac82540688a6434396c971a74b5bba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 18:03:57 +0000 Subject: [PATCH 14/44] Bump ruff from 0.0.287 to 0.0.289 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.287 to 0.0.289. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.0.287...v0.0.289) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 674833a7..0f390f32 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -ruff==0.0.287 +ruff==0.0.289 black==23.7.0 build==0.10.0 coverage==7.3.0 From 363cad21334f8d99154c46b26a3125b8c780ef30 Mon Sep 17 00:00:00 2001 From: mkmer Date: Fri, 15 Sep 2023 19:32:33 +0000 Subject: [PATCH 15/44] add check command to PUT --- blinkpy/api.py | 59 ++++++++++++++++++++++++++++++++------- blinkpy/camera.py | 20 ++++++++++--- blinkpy/sync_module.py | 14 +++------- tests/mock_responses.py | 4 +++ tests/test_api.py | 43 +++++++++++++++++++++++----- tests/test_sync_module.py | 42 ++++++++++++++++++---------- 6 files changed, 136 insertions(+), 46 deletions(-) diff --git a/blinkpy/api.py b/blinkpy/api.py index 74a68dfc..4d01c08f 100644 --- a/blinkpy/api.py +++ b/blinkpy/api.py @@ -3,6 +3,7 @@ import logging import string from json import dumps +from asyncio import sleep from blinkpy.helpers.util import ( get_time, Throttle, @@ -13,6 +14,7 @@ _LOGGER = logging.getLogger(__name__) MIN_THROTTLE_TIME = 5 +COMMAND_POLL_TIME = 1 async def request_login( @@ -94,7 +96,9 @@ async def request_network_update(blink, network): :param network: Sync module network id. """ url = f"{blink.urls.base_url}/network/{network}/update" - return await http_post(blink, url) + response = await http_post(blink, url) + await wait_for_command(blink, response) + return response async def request_user(blink): @@ -137,7 +141,9 @@ async def request_system_arm(blink, network): f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}" f"/networks/{network}/state/arm" ) - return await http_post(blink, url) + response = await http_post(blink, url) + await wait_for_command(blink, response) + return response @Throttle(seconds=MIN_THROTTLE_TIME) @@ -152,7 +158,9 @@ async def request_system_disarm(blink, network): f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}" f"/networks/{network}/state/disarm" ) - return await http_post(blink, url) + response = await http_post(blink, url) + await wait_for_command(blink, response) + return response async def request_command_status(blink, network, command_id): @@ -196,7 +204,9 @@ async def request_new_image(blink, network, camera_id): :param camera_id: Camera ID of camera to request new image from. """ url = f"{blink.urls.base_url}/network/{network}/camera/{camera_id}/thumbnail" - return await http_post(blink, url) + response = await http_post(blink, url) + await wait_for_command(blink, response) + return response @Throttle(seconds=MIN_THROTTLE_TIME) @@ -209,7 +219,9 @@ async def request_new_video(blink, network, camera_id): :param camera_id: Camera ID of camera to request new video from. """ url = f"{blink.urls.base_url}/network/{network}/camera/{camera_id}/clip" - return await http_post(blink, url) + response = await http_post(blink, url) + await wait_for_command(blink, response) + return response @Throttle(seconds=MIN_THROTTLE_TIME) @@ -280,7 +292,9 @@ async def request_camera_liveview(blink, network, camera_id): f"{blink.urls.base_url}/api/v5/accounts/{blink.account_id}" f"/networks/{network}/cameras/{camera_id}/liveview" ) - return await http_post(blink, url) + response = await http_post(blink, url) + await wait_for_command(blink, response) + return response async def request_camera_sensors(blink, network, camera_id): @@ -305,7 +319,9 @@ async def request_motion_detection_enable(blink, network, camera_id): :param camera_id: Camera ID of camera to enable. """ url = f"{blink.urls.base_url}/network/{network}/camera/{camera_id}/enable" - return await http_post(blink, url) + response = await http_post(blink, url) + await wait_for_command(blink, response) + return response @Throttle(seconds=MIN_THROTTLE_TIME) @@ -317,7 +333,9 @@ async def request_motion_detection_disable(blink, network, camera_id): :param camera_id: Camera ID of camera to disable. """ url = f"{blink.urls.base_url}/network/{network}/camera/{camera_id}/disable" - return await http_post(blink, url) + response = await http_post(blink, url) + await wait_for_command(blink, response) + return response async def request_local_storage_manifest(blink, network, sync_id): @@ -335,7 +353,9 @@ async def request_local_storage_manifest(blink, network, sync_id): f"/networks/{network}/sync_modules/{sync_id}" f"/local_storage/manifest/request" ) - return await http_post(blink, url) + response = await http_post(blink, url) + await wait_for_command(blink, response) + return response async def get_local_storage_manifest(blink, network, sync_id, manifest_request_id): @@ -373,7 +393,9 @@ async def request_local_storage_clip(blink, network, sync_id, manifest_id, clip_ manifest_id=manifest_id, clip_id=clip_id, ) - return await http_post(blink, url) + response = await http_post(blink, url) + await wait_for_command(blink, response) + return response async def request_get_config(blink, network, camera_id, product_type="owl"): @@ -467,3 +489,20 @@ async def http_post(blink, url, is_retry=False, data=None, json=True, timeout=TI json_resp=json, data=data, ) + + +async def wait_for_command(blink, json_data: dict) -> bool: + """Wait for command to complete.""" + _LOGGER.debug("Command Wait %s", json_data) + network_id = json_data.get("network_id") + command_id = json_data.get("id") + if command_id and network_id: + while True: + _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("complete"): + return True + if status.get("status_code", 0) != 908: + return False + await sleep(COMMAND_POLL_TIME) diff --git a/blinkpy/camera.py b/blinkpy/camera.py index eeb850e2..e3af62e9 100644 --- a/blinkpy/camera.py +++ b/blinkpy/camera.py @@ -471,7 +471,9 @@ async def async_arm(self, value): f"{self.network_id}/owls/{self.camera_id}/config" ) data = dumps({"enabled": value}) - return await api.http_post(self.sync.blink, url, json=False, data=data) + response = await api.http_post(self.sync.blink, url, data=data) + await api.wait_for_command(self.sync.blink, response) + return response async def snap_picture(self): """Snap picture for a blink mini camera.""" @@ -480,7 +482,9 @@ async def snap_picture(self): f"{self.sync.blink.account_id}/networks/" f"{self.network_id}/owls/{self.camera_id}/thumbnail" ) - return await api.http_post(self.sync.blink, url) + response = await api.http_post(self.sync.blink, url) + await api.wait_for_command(self.sync.blink, response) + return response async def get_sensor_info(self): """Get sensor info for blink mini camera.""" @@ -493,6 +497,7 @@ async def get_liveview(self): f"{self.network_id}/owls/{self.camera_id}/liveview" ) response = await api.http_post(self.sync.blink, url) + await api.wait_for_command(self.sync.blink, response) server = response["server"] server_split = server.split(":") server_split[0] = "rtsps:" @@ -524,7 +529,10 @@ async def async_arm(self, value): url = f"{url}/enable" else: url = f"{url}/disable" - return await api.http_post(self.sync.blink, url) + + response = await api.http_post(self.sync.blink, url) + await api.wait_for_command(self.sync.blink, response) + return response async def snap_picture(self): """Snap picture for a blink doorbell camera.""" @@ -533,7 +541,10 @@ async def snap_picture(self): f"{self.sync.blink.account_id}/networks/" f"{self.sync.network_id}/doorbells/{self.camera_id}/thumbnail" ) - return await api.http_post(self.sync.blink, url) + + response = await api.http_post(self.sync.blink, url) + await api.wait_for_command(self.sync.blink, response) + return response async def get_sensor_info(self): """Get sensor info for blink doorbell camera.""" @@ -546,6 +557,7 @@ async def get_liveview(self): f"{self.sync.network_id}/doorbells/{self.camera_id}/liveview" ) response = await api.http_post(self.sync.blink, url) + await api.wait_for_command(self.sync.blink, response) server = response["server"] link = server.replace("immis://", "rtsps://") return link diff --git a/blinkpy/sync_module.py b/blinkpy/sync_module.py index f7d47b4d..ea1fcfb8 100644 --- a/blinkpy/sync_module.py +++ b/blinkpy/sync_module.py @@ -679,17 +679,11 @@ def url(self, manifest_id=None): async def prepare_download(self, blink, max_retries=4): """Initiate upload of media item from the sync module to Blink cloud servers.""" + if max_retries == 0: + return None url = blink.urls.base_url + self.url() - response = None - for retry in range(max_retries): - response = await api.http_post(blink, url) - if "id" in response: - break - seconds = backoff_seconds(retry=retry, default_time=3) - _LOGGER.debug( - "[retry=%d] Retrying in %d seconds: %s", retry + 1, seconds, url - ) - await asyncio.sleep(seconds) + response = await api.http_post(blink, url) + await api.wait_for_command(blink, response) return response async def delete_video(self, blink, max_retries=4) -> bool: diff --git a/tests/mock_responses.py b/tests/mock_responses.py index 2cbfe9f6..2e249d21 100644 --- a/tests/mock_responses.py +++ b/tests/mock_responses.py @@ -17,3 +17,7 @@ def __init__(self, json_data, status_code, headers={}, raw_data=None): async def json(self): """Return json data from get_request.""" return self.json_data + + def get(self, name): + """Return field for json.""" + return self.json_data[name] diff --git a/tests/test_api.py b/tests/test_api.py index 8a37d0dd..197c0885 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -7,6 +7,10 @@ from blinkpy.auth import Auth import tests.mock_responses as mresp +COMMAND_RESPONSE = {"network_id": "12345", "id": "54321"} +COMMAND_COMPLETE = {"complete": True, "status_code": 908} +COMMAND_NOT_COMPLETE = {"complete": False, "status_code": 908} + @mock.patch("blinkpy.auth.Auth.query") class TestAPI(IsolatedAsyncioTestCase): @@ -57,7 +61,7 @@ async def test_request_network_status(self, mock_resp): async def test_request_command_status(self, mock_resp): """Test command_status.""" - mock_resp.return_value = {"command": "done"} + mock_resp.side_effect = ({"command": "done"}, COMMAND_COMPLETE) self.assertEqual( await api.request_command_status(self.blink, "network", "command"), {"command": "done"}, @@ -65,13 +69,19 @@ async def test_request_command_status(self, mock_resp): async def test_request_new_image(self, mock_resp): """Test api request new image.""" - mock_resp.return_value = mresp.MockResponse({}, 200) + mock_resp.side_effect = ( + mresp.MockResponse(COMMAND_RESPONSE, 200), + COMMAND_COMPLETE, + ) response = await api.request_new_image(self.blink, "network", "camera") self.assertEqual(response.status, 200) async def test_request_new_video(self, mock_resp): """Test api request new Video.""" - mock_resp.return_value = mresp.MockResponse({}, 200) + mock_resp.side_effect = ( + mresp.MockResponse(COMMAND_RESPONSE, 200), + COMMAND_COMPLETE, + ) response = await api.request_new_video(self.blink, "network", "camera") self.assertEqual(response.status, 200) @@ -97,7 +107,10 @@ async def test_request_camera_usage(self, mock_resp): async def test_request_motion_detection_enable(self, mock_resp): """Test Motion detect enable.""" - mock_resp.return_value = mresp.MockResponse({}, 200) + mock_resp.side_effect = ( + mresp.MockResponse(COMMAND_RESPONSE, 200), + COMMAND_COMPLETE, + ) response = await api.request_motion_detection_enable( self.blink, "network", "camera" ) @@ -105,7 +118,10 @@ async def test_request_motion_detection_enable(self, mock_resp): async def test_request_motion_detection_disable(self, mock_resp): """Test Motion detect enable.""" - mock_resp.return_value = mresp.MockResponse({}, 200) + mock_resp.side_effect = ( + mresp.MockResponse(COMMAND_RESPONSE, 200), + COMMAND_COMPLETE, + ) response = await api.request_motion_detection_disable( self.blink, "network", "camera" ) @@ -113,7 +129,10 @@ async def test_request_motion_detection_disable(self, mock_resp): async def test_request_local_storage_clip(self, mock_resp): """Test Motion detect enable.""" - mock_resp.return_value = mresp.MockResponse({}, 200) + mock_resp.side_effect = ( + mresp.MockResponse(COMMAND_RESPONSE, 200), + COMMAND_COMPLETE, + ) response = await api.request_local_storage_clip( self.blink, "network", "sync_id", "manifest_id", "clip_id" ) @@ -135,7 +154,7 @@ async def test_request_get_config(self, mock_resp): async def test_request_update_config(self, mock_resp): """Test Motion detect enable.""" - mock_resp.return_value = mresp.MockResponse({}, 200) + mock_resp.return_value = mresp.MockResponse(COMMAND_RESPONSE, 200) response = await api.request_update_config( self.blink, "network", "camera_id", "owl" ) @@ -149,3 +168,13 @@ async def test_request_update_config(self, mock_resp): self.blink, "network", "camera_id", "other_camera" ) ) + + async def test_wait_for_command(self, mock_resp): + """Test Motion detect enable.""" + mock_resp.side_effect = (COMMAND_NOT_COMPLETE, COMMAND_COMPLETE) + 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) diff --git a/tests/test_sync_module.py b/tests/test_sync_module.py index 8d01ee11..b67eee1d 100644 --- a/tests/test_sync_module.py +++ b/tests/test_sync_module.py @@ -14,6 +14,7 @@ from blinkpy.camera import BlinkCamera from tests.test_blink_functions import MockCamera import tests.mock_responses as mresp +from .test_api import COMMAND_RESPONSE, COMMAND_COMPLETE @mock.patch("blinkpy.auth.Auth.query") @@ -123,12 +124,12 @@ async def test_get_network_info(self, mock_resp) -> None: async def test_get_network_info_failure(self, mock_resp) -> None: """Test failed network retrieval.""" - mock_resp.return_value = {} + mock_resp.side_effect = (COMMAND_RESPONSE, COMMAND_COMPLETE) self.blink.sync["test"].available = True self.assertFalse(await self.blink.sync["test"].get_network_info()) self.assertFalse(self.blink.sync["test"].available) self.blink.sync["test"].available = True - mock_resp.return_value = None + mock_resp.side_effect = None self.assertFalse(await self.blink.sync["test"].get_network_info()) self.assertFalse(self.blink.sync["test"].available) @@ -238,7 +239,8 @@ async def test_update_local_storage_manifest(self, mock_resp) -> None: test_sync._local_storage["status"] = True test_sync.sync_id = 1234 mock_resp.side_effect = [ - {"id": 387372591, "network_id": 123456}, + COMMAND_RESPONSE, + COMMAND_COMPLETE, { "version": "1.0", "manifest_id": "4321", @@ -305,7 +307,8 @@ async def test_check_new_videos_with_local_storage(self, mock_resp) -> None: datetime.datetime.utcnow() - datetime.timedelta(seconds=60) ).isoformat() mock_resp.side_effect = [ - {"id": 387372591, "network_id": 123456}, + COMMAND_RESPONSE, + COMMAND_COMPLETE, { "version": "1.0", "manifest_id": "4321", @@ -325,11 +328,15 @@ async def test_check_new_videos_with_local_storage(self, mock_resp) -> None: ], }, {"media": []}, - {"id": 489371591, "network_id": 123456}, - {"id": 489371592, "network_id": 123456}, + COMMAND_RESPONSE, + COMMAND_COMPLETE, + COMMAND_RESPONSE, + COMMAND_COMPLETE, {"media": []}, - {"id": 489371592, "network_id": 123456}, - {"id": 489371592, "network_id": 123456}, + COMMAND_RESPONSE, + COMMAND_COMPLETE, + COMMAND_RESPONSE, + COMMAND_COMPLETE, ] test_sync._names_table[to_alphanumeric("Front_Door")] = "Front_Door" @@ -362,7 +369,7 @@ async def test_check_no_missing_id_with_update_local_storage_manifest( test_sync.cameras["Back Door"] = MockCamera(self.blink.sync) test_sync.cameras["Front_Door"] = MockCamera(self.blink.sync) mock_poll.return_value = [ - {"network_id": 123456}, + {"network_id": "12345", "id": "54321"}, ] test_sync._names_table[to_alphanumeric("Front_Door")] = "Front_Door" test_sync._names_table[to_alphanumeric("Back Door")] = "Back Door" @@ -385,7 +392,8 @@ async def test_check_missing_manifest_id_with_update_local_storage_manifest( ).isoformat() mock_resp.side_effect = [ - {"id": 387372591, "network_id": 123456}, + COMMAND_RESPONSE, + COMMAND_COMPLETE, { "version": "1.0", "clips": [ @@ -426,7 +434,8 @@ async def test_check_malformed_clips_with_update_local_storage_manifest( ).isoformat() mock_resp.side_effect = [ - {"id": 489371591, "network_id": 123456}, + COMMAND_RESPONSE, + COMMAND_COMPLETE, { "version": "1.0", "manifest_id": "4321", @@ -461,7 +470,8 @@ async def test_check_poll_local_storage_manifest_retry(self, mock_resp) -> None: test_sync.cameras["Front_Door"] = MockCamera(self.blink.sync) mock_resp.side_effect = [ - {"network_id": 123456}, + COMMAND_RESPONSE, + COMMAND_COMPLETE, ] test_sync._names_table[to_alphanumeric("Front_Door")] = "Front_Door" test_sync._names_table[to_alphanumeric("Back Door")] = "Back Door" @@ -469,7 +479,7 @@ async def test_check_poll_local_storage_manifest_retry(self, mock_resp) -> None: response = await test_sync.poll_local_storage_manifest(max_retries=1) self.assertEqual( response, - {"network_id": 123456}, + {"network_id": "12345", "id": "54321"}, ) async def test_sync_owl_init(self, mock_resp): @@ -524,11 +534,13 @@ async def test_local_storage_media_item(self, mock_resp): self.assertFalse(item == item2) mock_resp.side_effect = [ - {"network_id": 123456}, + COMMAND_RESPONSE, + COMMAND_COMPLETE, ] self.assertEqual( - await item.prepare_download(blink, max_retries=1), {"network_id": 123456} + await item.prepare_download(blink, max_retries=1), + {"network_id": "12345", "id": "54321"}, ) with mock.patch("blinkpy.api.http_post", return_value=""): From 5e87d663ed47adcc67cdc956fa901d2292b0a531 Mon Sep 17 00:00:00 2001 From: mkmer Date: Sun, 17 Sep 2023 14:49:01 +0000 Subject: [PATCH 16/44] Fix retry test in sync module --- tests/test_sync_module.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_sync_module.py b/tests/test_sync_module.py index b67eee1d..fb6f8a88 100644 --- a/tests/test_sync_module.py +++ b/tests/test_sync_module.py @@ -460,7 +460,7 @@ async def test_check_malformed_clips_with_update_local_storage_manifest( self.assertIsNone(await test_sync.update_local_storage_manifest()) async def test_check_poll_local_storage_manifest_retry(self, mock_resp) -> None: - """Test checking poll local storage manifest retry.""" + """Test checking poll local storage manifest retry logic.""" self.blink.account_id = 10111213 test_sync = self.blink.sync["test"] test_sync._local_storage["status"] = True @@ -470,16 +470,17 @@ async def test_check_poll_local_storage_manifest_retry(self, mock_resp) -> None: test_sync.cameras["Front_Door"] = MockCamera(self.blink.sync) mock_resp.side_effect = [ + {"bad": "stuff"}, # bad command response, fall back to retry logic COMMAND_RESPONSE, COMMAND_COMPLETE, ] test_sync._names_table[to_alphanumeric("Front_Door")] = "Front_Door" test_sync._names_table[to_alphanumeric("Back Door")] = "Back Door" - response = await test_sync.poll_local_storage_manifest(max_retries=1) + response = await test_sync.poll_local_storage_manifest(max_retries=2) self.assertEqual( response, - {"network_id": "12345", "id": "54321"}, + COMMAND_RESPONSE, ) async def test_sync_owl_init(self, mock_resp): From 8ea388ee95868d17fd271fa408e1a092581e0e5f Mon Sep 17 00:00:00 2001 From: mkmer Date: Mon, 18 Sep 2023 11:27:50 +0000 Subject: [PATCH 17/44] Clean up: Replace with COMMAND_RESPONSE --- tests/test_sync_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_sync_module.py b/tests/test_sync_module.py index fb6f8a88..a0f1a03f 100644 --- a/tests/test_sync_module.py +++ b/tests/test_sync_module.py @@ -369,7 +369,7 @@ async def test_check_no_missing_id_with_update_local_storage_manifest( test_sync.cameras["Back Door"] = MockCamera(self.blink.sync) test_sync.cameras["Front_Door"] = MockCamera(self.blink.sync) mock_poll.return_value = [ - {"network_id": "12345", "id": "54321"}, + COMMAND_RESPONSE, ] test_sync._names_table[to_alphanumeric("Front_Door")] = "Front_Door" test_sync._names_table[to_alphanumeric("Back Door")] = "Back Door" @@ -541,7 +541,7 @@ async def test_local_storage_media_item(self, mock_resp): self.assertEqual( await item.prepare_download(blink, max_retries=1), - {"network_id": "12345", "id": "54321"}, + COMMAND_RESPONSE, ) with mock.patch("blinkpy.api.http_post", return_value=""): From 1c29b669e5a174ab457d8d70c2a9b95a208e52d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 12:53:38 +0000 Subject: [PATCH 18/44] Bump black from 23.7.0 to 23.9.1 Bumps [black](https://github.com/psf/black) from 23.7.0 to 23.9.1. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/23.7.0...23.9.1) --- updated-dependencies: - dependency-name: black dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 0f390f32..1cabfd4e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,5 +1,5 @@ ruff==0.0.289 -black==23.7.0 +black==23.9.1 build==0.10.0 coverage==7.3.0 pytest==7.4.0 From 02f45a1e229ad558c99db9f50a3d49ebc03af0e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:10:45 +0000 Subject: [PATCH 19/44] Bump coverage from 7.3.0 to 7.3.1 Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.3.0 to 7.3.1. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/7.3.0...7.3.1) --- updated-dependencies: - dependency-name: coverage dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 1cabfd4e..677a3989 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,7 @@ ruff==0.0.289 black==23.9.1 build==0.10.0 -coverage==7.3.0 +coverage==7.3.1 pytest==7.4.0 pytest-cov==4.1.0 pytest-sugar==0.9.7 From 458896021e1b9d57ae113dd35e0f706c16078abb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:55:26 +0000 Subject: [PATCH 20/44] Bump build from 0.10.0 to 1.0.3 Bumps [build](https://github.com/pypa/build) from 0.10.0 to 1.0.3. - [Release notes](https://github.com/pypa/build/releases) - [Changelog](https://github.com/pypa/build/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pypa/build/compare/0.10.0...1.0.3) --- updated-dependencies: - dependency-name: build dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 677a3989..77dbbe64 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ ruff==0.0.289 black==23.9.1 -build==0.10.0 +build==1.0.3 coverage==7.3.1 pytest==7.4.0 pytest-cov==4.1.0 From 6b6f39576450e4345e3790deb55180c9c53a9ac9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 13:55:27 +0000 Subject: [PATCH 21/44] Bump pytest from 7.4.0 to 7.4.2 Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.0 to 7.4.2. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.4.0...7.4.2) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 677a3989..af688e4c 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -2,7 +2,7 @@ ruff==0.0.289 black==23.9.1 build==0.10.0 coverage==7.3.1 -pytest==7.4.0 +pytest==7.4.2 pytest-cov==4.1.0 pytest-sugar==0.9.7 pytest-timeout==2.1.0 From 7c43fab7a21b73f34698d84e8251b113429b5f7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 19:23:06 +0000 Subject: [PATCH 22/44] Bump ruff from 0.0.289 to 0.0.290 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.289 to 0.0.290. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.0.289...v0.0.290) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 77dbbe64..be96501e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -ruff==0.0.289 +ruff==0.0.290 black==23.9.1 build==1.0.3 coverage==7.3.1 From f55f62a60cabc526644b85d9e7ed1666df9e7c62 Mon Sep 17 00:00:00 2001 From: mkmer Date: Tue, 19 Sep 2023 12:45:25 +0000 Subject: [PATCH 23/44] Fix "complete" with "bad" code --- .vscode/settings.json | 4 ++++ blinkpy/api.py | 4 ++-- tests/test_api.py | 5 +++++ 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..e137fadb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/blinkpy/api.py b/blinkpy/api.py index 4d01c08f..38ea917c 100644 --- a/blinkpy/api.py +++ b/blinkpy/api.py @@ -501,8 +501,8 @@ 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("complete"): - return True if status.get("status_code", 0) != 908: return False + if status.get("complete"): + return True await sleep(COMMAND_POLL_TIME) diff --git a/tests/test_api.py b/tests/test_api.py index 197c0885..1c6e02d7 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -9,6 +9,7 @@ COMMAND_RESPONSE = {"network_id": "12345", "id": "54321"} COMMAND_COMPLETE = {"complete": True, "status_code": 908} +COMMAND_COMPLETE_BAD = {"complete": True, "status_code": 999} COMMAND_NOT_COMPLETE = {"complete": False, "status_code": 908} @@ -178,3 +179,7 @@ async def test_wait_for_command(self, mock_resp): 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_COMPLETE_BAD, {}) + response = await api.wait_for_command(self.blink, COMMAND_RESPONSE) + self.assertFalse(response) From 9cb116954098c7331be00bd4d065acb91e9755bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 18:40:15 +0000 Subject: [PATCH 24/44] Bump ruff from 0.0.290 to 0.0.292 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.290 to 0.0.292. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.0.290...v0.0.292) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index c7bc4cee..e3989ebc 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -ruff==0.0.290 +ruff==0.0.292 black==23.9.1 build==1.0.3 coverage==7.3.1 From dc309d61a2c2b980de26c27423fc53ed5ff9cc1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 18:07:37 +0000 Subject: [PATCH 25/44] Bump pytest-timeout from 2.1.0 to 2.2.0 Bumps [pytest-timeout](https://github.com/pytest-dev/pytest-timeout) from 2.1.0 to 2.2.0. - [Commits](https://github.com/pytest-dev/pytest-timeout/compare/2.1.0...2.2.0) --- updated-dependencies: - dependency-name: pytest-timeout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index c7bc4cee..a10af4e3 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ coverage==7.3.1 pytest==7.4.2 pytest-cov==4.1.0 pytest-sugar==0.9.7 -pytest-timeout==2.1.0 +pytest-timeout==2.2.0 restructuredtext-lint==1.4.0 pygments==2.16.1 testtools>=2.4.0 From 2040c0fcf2fc022867a242fa979460d3aaf4db62 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 10 Oct 2023 11:35:55 -0400 Subject: [PATCH 26/44] Create stale.yml --- .github/workflows/stale.yml | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..01f06f7d --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,44 @@ +name: Stale +on: + schedule: + - cron: "0 12 * * *" + workflow_dispatch: + +jobs: + stale: + if: github.repository_owner == 'fronzbot' + runs-on: ubuntu-latest + - name: stale-issues + uses: actions/stale@v8.0.0 + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 60 + days-before-close: 7 + days-before-pr-stale: -1 + days-before-pr-close: -1 + remove-stale-when-updated: true + stale-issue-label: "stale" + exempt-issue-labels: "no-stale,help-wanted,priority" + stale-issue-message: > + There hasn't been any activity on this issue recently. + Please make sure to update to the latest blinkpy version and + check if that solves the issue. Let us know if that works for you by + adding a comment 👍 + + This issue has now been marked as stale and will be closed if no + further activity occurs. Thank you for your contributions. + - name: stale-prs + uses: actions/stale@v8.0.0 + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 90 + days-before-close: 7 + days-before-issue-stale: -1 + days-before-issue-close: -1 + remove-stale-when-updated: true + stale-issue-label: "stale" + exempt-issue-labels: "no-stale" + stale-pr-message: > + There hasn't been any activity on this pull request recently. This + pull request has been automatically marked as stale because of that + and will be closed if no further activity occurs within 7 days. + + Thank you for your contributions. From 826c4e68dc557a05779e12e84c54c1ade079ce58 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 10 Oct 2023 11:37:21 -0400 Subject: [PATCH 27/44] Update stale.yml --- .github/workflows/stale.yml | 45 +++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 01f06f7d..f1979fc7 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,17 +8,18 @@ jobs: stale: if: github.repository_owner == 'fronzbot' runs-on: ubuntu-latest - - name: stale-issues - uses: actions/stale@v8.0.0 - repo-token: ${{ secrets.GITHUB_TOKEN }} - days-before-stale: 60 - days-before-close: 7 - days-before-pr-stale: -1 - days-before-pr-close: -1 - remove-stale-when-updated: true - stale-issue-label: "stale" - exempt-issue-labels: "no-stale,help-wanted,priority" - stale-issue-message: > + steps: + - name: stale-issues + uses: actions/stale@v8.0.0 + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 60 + days-before-close: 7 + days-before-pr-stale: -1 + days-before-pr-close: -1 + remove-stale-when-updated: true + stale-issue-label: "stale" + exempt-issue-labels: "no-stale,help-wanted,priority" + stale-issue-message: > There hasn't been any activity on this issue recently. Please make sure to update to the latest blinkpy version and check if that solves the issue. Let us know if that works for you by @@ -26,17 +27,17 @@ jobs: This issue has now been marked as stale and will be closed if no further activity occurs. Thank you for your contributions. - - name: stale-prs - uses: actions/stale@v8.0.0 - repo-token: ${{ secrets.GITHUB_TOKEN }} - days-before-stale: 90 - days-before-close: 7 - days-before-issue-stale: -1 - days-before-issue-close: -1 - remove-stale-when-updated: true - stale-issue-label: "stale" - exempt-issue-labels: "no-stale" - stale-pr-message: > + - name: stale-prs + uses: actions/stale@v8.0.0 + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 90 + days-before-close: 7 + days-before-issue-stale: -1 + days-before-issue-close: -1 + remove-stale-when-updated: true + stale-issue-label: "stale" + exempt-issue-labels: "no-stale" + stale-pr-message: > There hasn't been any activity on this pull request recently. This pull request has been automatically marked as stale because of that and will be closed if no further activity occurs within 7 days. From c6254fd5b867010041dc0e1cb31f86e6bb634395 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 10 Oct 2023 11:38:56 -0400 Subject: [PATCH 28/44] Update stale.yml --- .github/workflows/stale.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index f1979fc7..94e54daa 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,6 +8,9 @@ jobs: stale: if: github.repository_owner == 'fronzbot' runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write steps: - name: stale-issues uses: actions/stale@v8.0.0 From 5f7d98572821288679ce91567666240710814767 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 10 Oct 2023 11:42:00 -0400 Subject: [PATCH 29/44] Update stale.yml --- .github/workflows/stale.yml | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 94e54daa..cf37c959 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,19 +1,26 @@ -name: Stale +# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. +# +# You can adjust the behavior by modifying this file. +# For more information, see: +# https://github.com/actions/stale +name: Mark stale issues and pull requests + on: schedule: - - cron: "0 12 * * *" - workflow_dispatch: - + - cron: '20 12 * * *' + jobs: stale: - if: github.repository_owner == 'fronzbot' + runs-on: ubuntu-latest permissions: issues: write pull-requests: write + steps: - - name: stale-issues - uses: actions/stale@v8.0.0 + - name: stale-issues + uses: actions/stale@v5 + with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 60 days-before-close: 7 @@ -30,10 +37,11 @@ jobs: This issue has now been marked as stale and will be closed if no further activity occurs. Thank you for your contributions. - - name: stale-prs - uses: actions/stale@v8.0.0 + - name: stale-pulls + uses: actions/stale@v5 + with: repo-token: ${{ secrets.GITHUB_TOKEN }} - days-before-stale: 90 + days-before-stale: 60 days-before-close: 7 days-before-issue-stale: -1 days-before-issue-close: -1 From 09ed23e1199786cb1b603a5c06c02decfebeb49e Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 10 Oct 2023 11:42:30 -0400 Subject: [PATCH 30/44] Update stale.yml --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index cf37c959..5a013f32 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -3,7 +3,7 @@ # You can adjust the behavior by modifying this file. # For more information, see: # https://github.com/actions/stale -name: Mark stale issues and pull requests +name: Stale on: schedule: From 965c62431a339b3467bfbf58004fde323a2d8c90 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 10 Oct 2023 11:54:56 -0400 Subject: [PATCH 31/44] Update stale.yml --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 5a013f32..6821deca 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -21,7 +21,7 @@ jobs: - name: stale-issues uses: actions/stale@v5 with: - repo-token: ${{ secrets.GITHUB_TOKEN }} + #repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 60 days-before-close: 7 days-before-pr-stale: -1 From e200782cc63d52d901b14680f40bf3239e0ed7d7 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 10 Oct 2023 11:55:24 -0400 Subject: [PATCH 32/44] Update stale.yml --- .github/workflows/stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 6821deca..1c2567ec 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,7 +7,7 @@ name: Stale on: schedule: - - cron: '20 12 * * *' + - cron: '0 * * * *' jobs: stale: @@ -21,7 +21,7 @@ jobs: - name: stale-issues uses: actions/stale@v5 with: - #repo-token: ${{ secrets.GITHUB_TOKEN }} + repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 60 days-before-close: 7 days-before-pr-stale: -1 From 7238352d662c0dee5f9df3e37f25bf0ad1f1ff56 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 10 Oct 2023 12:05:40 -0400 Subject: [PATCH 33/44] Update stale.yml --- .github/workflows/stale.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 1c2567ec..e8e02f92 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,7 +7,9 @@ name: Stale on: schedule: - - cron: '0 * * * *' + - cron: '13 7 * * *' + issues: + types: [opened, edited] jobs: stale: From dbf3275e55e8d5676211b47962247830ae87da2d Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 10 Oct 2023 12:05:53 -0400 Subject: [PATCH 34/44] Update stale.yml --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index e8e02f92..3b87145a 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,7 +7,7 @@ name: Stale on: schedule: - - cron: '13 7 * * *' + - cron: '13 7 * * *' issues: types: [opened, edited] From d10c95d0dfb9aaa949bdbcb311ad7f63365a5564 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 10 Oct 2023 12:08:04 -0400 Subject: [PATCH 35/44] Update stale.yml --- .github/workflows/stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 3b87145a..93fd4d50 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,7 +7,7 @@ name: Stale on: schedule: - - cron: '13 7 * * *' + - cron: '13 * * * *' issues: types: [opened, edited] @@ -43,7 +43,7 @@ jobs: uses: actions/stale@v5 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - days-before-stale: 60 + days-before-stale: 90 days-before-close: 7 days-before-issue-stale: -1 days-before-issue-close: -1 From b4757c7a57fa5b269d8699291861c0f23de76e6e Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 10 Oct 2023 12:09:39 -0400 Subject: [PATCH 36/44] Delete .github/stale.yml --- .github/stale.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index dbe57159..00000000 --- a/.github/stale.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 30 - -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 - -# Issues with these labels will never be considered stale -exemptLabels: - - Priority - - bug - - help wanted - - feature request - -# Set to true to ignore issues in a project (defaults to false) -exemptProjects: true - -# Set to true to ignore issues in a milestone (defaults to false) -exemptMilestones: true - -# Label to use when marking an issue as stale -staleLabel: stale - -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs within seven days. Thank you - for your contributions. - -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: > - This issue is now being closed due to inactivity. From 592932b39e4d6b9d866c6b79b739877c6f4e3a55 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 10 Oct 2023 12:10:28 -0400 Subject: [PATCH 37/44] Update stale.yml --- .github/workflows/stale.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 93fd4d50..623c1217 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,12 +8,9 @@ name: Stale on: schedule: - cron: '13 * * * *' - issues: - types: [opened, edited] jobs: stale: - runs-on: ubuntu-latest permissions: issues: write From 2e3b92292f4996e12c8b9dc7f57f5e032732dc22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Oct 2023 16:16:05 +0000 Subject: [PATCH 38/44] Bump coverage from 7.3.1 to 7.3.2 Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.3.1 to 7.3.2. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/7.3.1...7.3.2) --- updated-dependencies: - dependency-name: coverage dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index e3989ebc..c9aac5ca 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,7 @@ ruff==0.0.292 black==23.9.1 build==1.0.3 -coverage==7.3.1 +coverage==7.3.2 pytest==7.4.2 pytest-cov==4.1.0 pytest-sugar==0.9.7 From c078cb876c2ce257e823ca41b4acbe6aaddcf281 Mon Sep 17 00:00:00 2001 From: mkmer Date: Wed, 11 Oct 2023 11:38:21 +0000 Subject: [PATCH 39/44] Add session to auth call --- blinkapp/blinkapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blinkapp/blinkapp.py b/blinkapp/blinkapp.py index 69d300e0..5d4ab37d 100644 --- a/blinkapp/blinkapp.py +++ b/blinkapp/blinkapp.py @@ -24,7 +24,7 @@ async def download_videos(blink, save_dir="/media"): async def start(session: ClientSession): """Startup blink app.""" blink = Blink(session=session) - blink.auth = Auth(await json_load(CREDFILE)) + blink.auth = Auth(await json_load(CREDFILE), session=session) await blink.start() return blink From 93ce7c38aca0208e5c8b4f1aac4c7561d59f0950 Mon Sep 17 00:00:00 2001 From: mkmer Date: Wed, 11 Oct 2023 14:51:47 +0000 Subject: [PATCH 40/44] use session for Auth --- blinkapp/blinkapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blinkapp/blinkapp.py b/blinkapp/blinkapp.py index 17c254e5..f0bd65df 100644 --- a/blinkapp/blinkapp.py +++ b/blinkapp/blinkapp.py @@ -24,7 +24,7 @@ async def download_videos(blink, save_dir="/media"): async def start(session: ClientSession): """Startup blink app.""" blink = Blink(session=session) - blink.auth = Auth(await json_load(CREDFILE)) + blink.auth = Auth(await json_load(CREDFILE), session=session) await blink.start() return blink From 2f962eb0a70853b95603ad4873aef05c2748b5b5 Mon Sep 17 00:00:00 2001 From: mkmer Date: Wed, 11 Oct 2023 15:11:30 +0000 Subject: [PATCH 41/44] Add max retry to wait_for_command --- blinkpy/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blinkpy/api.py b/blinkpy/api.py index 38ea917c..576568c0 100644 --- a/blinkpy/api.py +++ b/blinkpy/api.py @@ -15,6 +15,7 @@ MIN_THROTTLE_TIME = 5 COMMAND_POLL_TIME = 1 +MAX_RETRY = 120 async def request_login( @@ -497,7 +498,7 @@ async def wait_for_command(blink, json_data: dict) -> bool: network_id = json_data.get("network_id") command_id = json_data.get("id") if command_id and network_id: - while True: + for _ in range(0, MAX_RETRY): _LOGGER.debug("Making GET request waiting for command") status = await request_command_status(blink, network_id, command_id) _LOGGER.debug("command status %s", status) From 12a092939864fc5e3764de57ea1298c588576bef Mon Sep 17 00:00:00 2001 From: mkmer Date: Wed, 11 Oct 2023 17:49:17 +0000 Subject: [PATCH 42/44] remove vscode folder --- .gitignore | 1 + .vscode/settings.json | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 1b042b35..e06b56ba 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ Pipfile Pipfile.lock blink.json blinktest.py +.vscode/* diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index e137fadb..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true -} \ No newline at end of file From 789871362eba6d6e699ce447a01670c19b875fe0 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 13 Oct 2023 11:06:51 -0400 Subject: [PATCH 43/44] version bump --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 37a774d1..f085a1cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "blinkpy" -version = "0.23.0.dev0" +version = "0.22.1" license = {text = "MIT"} description = "A Blink camera Python Library." readme = "README.rst" From 16e136a1837226cf8616059dee16361f715578eb Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 13 Oct 2023 11:13:50 -0400 Subject: [PATCH 44/44] Update CHANGES.rst --- CHANGES.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b709589e..520406fe 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,28 @@ Changelog A list of changes between each release +0.22.1 (2023-10-13) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Bugfixes** + +- Fix night vision toggling for older devices (owl) (`@cocasema #756 `__) +- Add missing await to blinkapp.py (`@mkmer #768 `__) +- Add check command to POST commands (`@mkmer #772 `__) +- Fix blinkapp session call (`@mkmer #783 `__) + +**Other Changes** + +- Cleanup readme, add breaking change warning +- Migrate to puproject.toml + ruff +- Bump ruff to 0.0.292 +- Bump black to 23.9.1 +- Bump coverage to 7.3.2 +- Bump build to 1.0.3 +- Bump pytest to 7.4.2 +- Bump pytest-timeout to 2.2.0 +- Fix 'stale' github action + 0.22.0 (2023-08-16) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~