From ea2b22dbf0753f18515e692a97e834f065707e88 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Tue, 20 Nov 2018 21:46:38 -0500 Subject: [PATCH 01/17] Dev version bump --- 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 67b6f5a6..c0e1538f 100644 --- a/blinkpy/helpers/constants.py +++ b/blinkpy/helpers/constants.py @@ -3,8 +3,8 @@ import os MAJOR_VERSION = 0 -MINOR_VERSION = 10 -PATCH_VERSION = 3 +MINOR_VERSION = 11 +PATCH_VERSION = '0.dev' __version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION) From e705fbd40361bc19cc675d80374ed384544ffc3d Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Wed, 21 Nov 2018 09:36:36 -0500 Subject: [PATCH 02/17] Update README.rst --- README.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index b87eb09a..7c06f44b 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,6 @@ blinkpy |Build Status| |Coverage Status| |Docs| |PyPi Version| |Python Version| ================================================================================ A Python library for the Blink Camera system -Only compatible with Python 3+ Disclaimer: ~~~~~~~~~~~~~~~ @@ -32,12 +31,16 @@ To install the current development version, perform the following steps. Note t $ pip3 install --upgrade dist/*.whl +If you'd like to contribute to this library, please read the `contributing instructions `__. + +For more information on how to use this library, please `read the docs `__. + Purpose =========== -This library was built with the intention of allowing easy communication with Blink camera systems, specifically so I can add a module into homeassistant https://home-assistant.io +This library was built with the intention of allowing easy communication with Blink camera systems, specifically to support the Blink component in `homeassistant Date: Wed, 21 Nov 2018 09:37:27 -0500 Subject: [PATCH 03/17] Bad link format --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 7c06f44b..3f729e34 100644 --- a/README.rst +++ b/README.rst @@ -37,7 +37,7 @@ For more information on how to use this library, please `read the docs `__. Quick Start ============= From db29bae1403088bb67b485326a4a97bc44c4688e Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Wed, 21 Nov 2018 09:38:23 -0500 Subject: [PATCH 04/17] Broke out blink home assistant links --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 3f729e34..b6641b7f 100644 --- a/README.rst +++ b/README.rst @@ -37,7 +37,7 @@ For more information on how to use this library, please `read the docs `__. +This library was built with the intention of allowing easy communication with Blink camera systems, specifically to support the `Blink component `__ in `homeassistant `__. Quick Start ============= From c53b2bd777d6b10b602b1c5ce7f98261ae0801dc Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Wed, 21 Nov 2018 10:02:12 -0500 Subject: [PATCH 05/17] Improve contribution instructions --- CONTRIBUTING.md | 88 ++++++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 81eab1a2..6ac21bdc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,41 +4,55 @@ Everyone is welcome to contribute to blinkpy! The process to get started is desc ## Fork the Repository -You can do this right in gituhb: just click the 'fork' button at the top right. +You can do this right in github: just click the 'fork' button at the top right. + +## Start Developing + +1. Setup Local Repository + ```shell + $ git clone https://github.com//blinkpy.git + $ cd blinkpy + $ git remote add upstream https://github.com/fronzbot/blinkpy.git + ``` + +2. Create a Local Branch + + First, you will want to create a new branch to hold your changes: + ``git checkout -b `` + + +3. Make changes + + Now you can make changes to your code. It is worthwhile to test your code as you progress (see the **Testing** section) + +4. Commit Your Changes + + To commit changes to your branch, simply add the files you want and the commit them to the branch. After that, you can push to your fork on GitHub: + ```shell + $ git add . + $ git commit -m "Put your commit text here. Please be concise, but descriptive." + $ git push origin HEAD + ``` + +5. Submit your pull request on GitHub + + - On GitHub, navigate to the [blinkpy](https://github.com/fronzbot/blinkpy) repository. + - In the "Branch" menu, choose the branch that contains your commits (from your fork). + - To the right of the Branch menu, click New pull request. + - The base branch dropdown menu should read `dev`. Use the compare branch drop-down menu to choose the branch you made your changes in. + - Type a title and complete the provided description for your pull request. + - Click Create pull request. + - More detailed instructions can be found here: [Creating a Pull Request](https://help.github.com/articles/creating-a-pull-request/) + +6. Prior to merge approval + + Finally, the `blinkpy` repository uses continuous integration tools to run tests prior to merging. If there are any problems, you will see a red 'X' next to your pull request. To see what's wrong, you can find your pull request [here](https://travis- ci.org/fronzbot/blinkpy/pull_requests) and click on the failing test to see the logs. Those logs will indicate, as best as they can, what is causing that test to fail. -## Setup Local Repository - -```shell -$ git clone https://github.com//blinkpy.git -$ cd blinkpy -$ git remote add upstream https://github.com/fronzbot/blinkpy.git -``` - -## Create a Local Branch - -First, you will want to create a new branch to hold your changes: -``git checkout -b `` -Next, you need to make sure you pull from the 'dev' branch: -``git pull origin dev`` - -## Make changes - -Now you can make changes to your code. It is worthwhile to test your code as you progress (see the **Testing** section) - -## Commit Your Changes - -To commit changes to your branch, simply add the files you want and the commit them to the branch. After that, you can push to your fork on GitHub: - -```shell -$ git add . -$ git commit -m "Put your commit text here. Please be concise, but descriptive." -$ git push origin HEAD -``` ## Testing It is important to test the code to make sure your changes don't break anything major and that they pass PEP8 style conventions. -FIrst, you need to locally install ``tox`` +First, you need to locally install ``tox`` ```shell $ pip3 install tox @@ -52,11 +66,11 @@ $ tox ### Tips -If you only want to see if you can pass the local tests, you can run ``tox -e py34``. If you just want to check for style violations, you can run ``tox -e lint``. Regardless, when you submit a pull request, your code MUST pass both the unit tests, and the linters. +If you only want to see if you can pass the local tests, you can run `tox -e py35` (or whatever python version you have installed. Only `py35`, `py36`, and `py37` will be accepted). If you just want to check for style violations, you can run `tox -e lint`. Regardless, when you submit a pull request, your code MUST pass both the unit tests, and the linters. -If you need to change anything in ``requirements.txt`` for any reason, you'll want to regenerate the virtual envrionments used by ``tox`` by running with the ``-r`` flag: ``tox -r`` +If you need to change anything in `requirements.txt` for any reason, you'll want to regenerate the virtual envrionments used by `tox` by running with the `-r` flag: `tox -r` -Please do not locally disable any linter warnings within the ``blinkpy.py`` module itself (it's ok to do this in any of the ``test_*.py`` files) +If you want to run a single test (perhaps you only changed a small thing in one file) you can run `tox -e py35 -- tests/.py -x`. This will run the test `.py` and stop testing upon the first failure, making it easier to figure out why a particular test might be failing. The test structure mimics the library structure, so if you changed something in `sync_module.py`, the associated test file would be in `test_sync_module.py` (ie. the filename is prepended with `test_`. # Catching Up With Reality @@ -73,11 +87,3 @@ If rebase detects conflicts, repeat the following process until all changes have 2. Add the modified file: ``git add `` or ``git add .``. 3. Continue rebase: ``git rebase --continue``. 4. Repeat until all conflicts resolved. - -# Creating a Pull Request - -Please follow these steps to create a pull request against the ``dev`` branch: [Creating a Pull Request](https://help.github.com/articles/creating-a-pull-request/) - -# Monitor Build Status - -Once you create your PR, you can monitor the status of your build [here](https://travis-ci.org/fronzbot/blinkpy), Your code will be tested to ensure it passes and won't cause any problems after merging. \ No newline at end of file From a1fd05735e1c7f50b943d31afab209b9c7d6d160 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 23 Nov 2018 14:22:54 -0500 Subject: [PATCH 06/17] Initial support for multiple sync modules --- blinkpy/blinkpy.py | 30 ++++++++++++++++-------------- blinkpy/sync_module.py | 2 +- tests/test_blinkpy.py | 17 ++++++++++++++++- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index 5663f548..c3f1ef0f 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -21,7 +21,7 @@ create_session, BlinkURLHandler, BlinkAuthenticationException) from blinkpy.helpers.constants import ( - BLINK_URL, LOGIN_URL, LOGIN_BACKUP_URL, PROJECT_URL) + BLINK_URL, LOGIN_URL, LOGIN_BACKUP_URL) REFRESH_RATE = 30 @@ -46,10 +46,10 @@ def __init__(self, username=None, password=None, self._token = None self._auth_header = None self._host = None - self.network_id = None self.account_id = None + self.network_ids = [] self.urls = None - self.sync = None + self.sync = {} self.region = None self.region_id = None self.last_refresh = None @@ -76,8 +76,10 @@ def start(self): self.get_auth_token() self.get_ids() - self.sync = BlinkSyncModule(self) - self.sync.start() + for network_id in self.network_ids: + sync_module = BlinkSyncModule(self) + sync_module.start() + self.sync[network_id] = sync_module def login(self): """Prompt user for username and password.""" @@ -138,16 +140,15 @@ def get_ids(self): all_networks = [] for network, status in self.networks.items(): if status['onboarded']: - all_networks.append(network) - self.network_id = all_networks.pop(0) + all_networks.append('{}'.format(network)) + + # For the first onboarded network we find, grab the account id for resp in response['networks']: - if str(resp['id']) == self.network_id: + if str(resp['id']) in all_networks: self.account_id = resp['account_id'] - if all_networks: - _LOGGER.warning(("More than one onboarded network. " - "Platform may not work as intended. " - "If you experience problems, please " - "open an issue on %s"), PROJECT_URL) + break + + self.network_ids = all_networks def refresh(self, force_cache=False): """ @@ -157,7 +158,8 @@ def refresh(self, force_cache=False): """ if self.check_if_ok_to_update() or force_cache: _LOGGER.debug("Attempting refresh of cameras.") - self.sync.refresh(force_cache=force_cache) + for sync_module in self.sync: + sync_module.refresh(force_cache=force_cache) def check_if_ok_to_update(self): """Check if it is ok to perform an http request.""" diff --git a/blinkpy/sync_module.py b/blinkpy/sync_module.py index ce4611cf..efb0cf91 100644 --- a/blinkpy/sync_module.py +++ b/blinkpy/sync_module.py @@ -21,7 +21,7 @@ def __init__(self, blink): """ self.blink = blink self._auth_header = blink.auth_header - self.network_id = blink.network_id + self.network_id = None self.region = blink.region self.region_id = blink.region_id self.name = 'sync' diff --git a/tests/test_blinkpy.py b/tests/test_blinkpy.py index 444581c2..f12c8704 100644 --- a/tests/test_blinkpy.py +++ b/tests/test_blinkpy.py @@ -114,9 +114,24 @@ def test_multiple_networks(self, mock_net, mock_sess): '5678': {'onboarded': True}, '1234': {'onboarded': False}} self.blink.get_ids() - self.assertEqual(self.blink.network_id, '5678') + self.assertTrue('5678' in self.blink.network_ids) self.assertEqual(self.blink.account_id, 2222) + @mock.patch('blinkpy.api.request_networks') + def test_multiple_onboarded_networks(self, mock_net, mock_sess): + """Check that we handle multiple networks appropriately.""" + mock_net.return_value = { + 'networks': [{'id': 0000, 'account_id': 2222}, + {'id': 5678, 'account_id': 1111}] + } + self.blink.networks = {'0000': {'onboarded': False}, + '5678': {'onboarded': True}, + '1234': {'onboarded': True}} + self.blink.get_ids() + self.assertTrue('5678' in self.blink.network_ids) + self.assertTrue('1234' in self.blink.network_ids) + self.assertEqual(self.blink.account_id, 1111) + @mock.patch('blinkpy.blinkpy.time.time') def test_throttle(self, mock_time, mock_sess): """Check throttling functionality.""" From ed25745deb9dfe0c2762a68ad807f7f6781eb5c0 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 23 Nov 2018 16:36:17 -0500 Subject: [PATCH 07/17] Added case insesnitive dict, changed sync module name to network name (to mimic app) --- blinkpy/blinkpy.py | 14 +++++++++----- blinkpy/sync_module.py | 7 +++---- tests/test_blinkpy.py | 19 ++++++++++++------- tests/test_cameras.py | 6 +++--- tests/test_sync_module.py | 33 ++++++++++++++++----------------- 5 files changed, 43 insertions(+), 36 deletions(-) diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index c3f1ef0f..34b5d387 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -14,6 +14,7 @@ import time import getpass import logging +from requests.structures import CaseInsensitiveDict import blinkpy.helpers.errors as ERROR from blinkpy import api from blinkpy.sync_module import BlinkSyncModule @@ -49,7 +50,7 @@ def __init__(self, username=None, password=None, self.account_id = None self.network_ids = [] self.urls = None - self.sync = {} + self.sync = CaseInsensitiveDict({}) self.region = None self.region_id = None self.last_refresh = None @@ -75,11 +76,11 @@ def start(self): else: self.get_auth_token() - self.get_ids() - for network_id in self.network_ids: - sync_module = BlinkSyncModule(self) + networks = self.get_ids() + for network_name, network_id in networks.items(): + sync_module = BlinkSyncModule(self, network_name, network_id) sync_module.start() - self.sync[network_id] = sync_module + self.sync[network_name] = sync_module def login(self): """Prompt user for username and password.""" @@ -138,9 +139,11 @@ def get_ids(self): # Look for only onboarded network, flag warning if multiple # since it's unexpected all_networks = [] + network_dict = {} for network, status in self.networks.items(): if status['onboarded']: all_networks.append('{}'.format(network)) + network_dict[status['name']] = network # For the first onboarded network we find, grab the account id for resp in response['networks']: @@ -149,6 +152,7 @@ def get_ids(self): break self.network_ids = all_networks + return network_dict def refresh(self, force_cache=False): """ diff --git a/blinkpy/sync_module.py b/blinkpy/sync_module.py index efb0cf91..b59c7a49 100644 --- a/blinkpy/sync_module.py +++ b/blinkpy/sync_module.py @@ -13,7 +13,7 @@ class BlinkSyncModule(): """Class to initialize sync module.""" - def __init__(self, blink): + def __init__(self, blink, network_name, network_id): """ Initialize Blink sync module. @@ -21,10 +21,10 @@ def __init__(self, blink): """ self.blink = blink self._auth_header = blink.auth_header - self.network_id = None + self.network_id = network_id self.region = blink.region self.region_id = blink.region_id - self.name = 'sync' + self.name = network_name self.serial = None self.status = None self.sync_id = None @@ -79,7 +79,6 @@ def start(self): """Initialize the system.""" response = api.request_syncmodule(self.blink, self.network_id) self.summary = response['syncmodule'] - self.name = self.summary['name'] self.sync_id = self.summary['id'] self.network_id = self.summary['network_id'] self.serial = self.summary['serial'] diff --git a/tests/test_blinkpy.py b/tests/test_blinkpy.py index f12c8704..6676fbde 100644 --- a/tests/test_blinkpy.py +++ b/tests/test_blinkpy.py @@ -30,7 +30,7 @@ def setUp(self): self.blink_no_cred = Blink() self.blink = Blink(username=USERNAME, password=PASSWORD) - self.blink.sync = BlinkSyncModule(self.blink) + self.blink.sync['test'] = BlinkSyncModule(self.blink, 'test', '1234') self.blink.urls = BlinkURLHandler('test') self.blink.session = create_session() @@ -110,9 +110,9 @@ def test_multiple_networks(self, mock_net, mock_sess): 'networks': [{'id': 1234, 'account_id': 1111}, {'id': 5678, 'account_id': 2222}] } - self.blink.networks = {'0000': {'onboarded': False}, - '5678': {'onboarded': True}, - '1234': {'onboarded': False}} + self.blink.networks = {'0000': {'onboarded': False, 'name': 'foo'}, + '5678': {'onboarded': True, 'name': 'bar'}, + '1234': {'onboarded': False, 'name': 'test'}} self.blink.get_ids() self.assertTrue('5678' in self.blink.network_ids) self.assertEqual(self.blink.account_id, 2222) @@ -124,9 +124,9 @@ def test_multiple_onboarded_networks(self, mock_net, mock_sess): 'networks': [{'id': 0000, 'account_id': 2222}, {'id': 5678, 'account_id': 1111}] } - self.blink.networks = {'0000': {'onboarded': False}, - '5678': {'onboarded': True}, - '1234': {'onboarded': True}} + self.blink.networks = {'0000': {'onboarded': False, 'name': 'foo'}, + '5678': {'onboarded': True, 'name': 'bar'}, + '1234': {'onboarded': True, 'name': 'test'}} self.blink.get_ids() self.assertTrue('5678' in self.blink.network_ids) self.assertTrue('1234' in self.blink.network_ids) @@ -143,3 +143,8 @@ def test_throttle(self, mock_time, mock_sess): self.assertEqual(result, True) self.assertEqual(self.blink.check_if_ok_to_update(), False) self.assertEqual(self.blink.last_refresh, now) + + def test_sync_case_insensitive_dict(self, mock_sess): + """Check that we can access sync modules ignoring case.""" + self.assertEqual(self.blink.sync['test'].name, 'test') + self.assertEqual(self.blink.sync['TEST'].name, 'test') diff --git a/tests/test_cameras.py b/tests/test_cameras.py index 4f7bbbcc..d3ffccb5 100644 --- a/tests/test_cameras.py +++ b/tests/test_cameras.py @@ -46,10 +46,10 @@ def setUp(self): self.blink._auth_header = header self.blink.session = create_session() self.blink.urls = BlinkURLHandler('test') - self.blink.sync = BlinkSyncModule(self.blink) - self.camera = BlinkCamera(self.blink.sync) + self.blink.sync['test'] = BlinkSyncModule(self.blink, 'test', 1234) + self.camera = BlinkCamera(self.blink.sync['test']) self.camera.name = 'foobar' - self.blink.sync.cameras['foobar'] = self.camera + self.blink.sync['test'].cameras['foobar'] = self.camera def tearDown(self): """Clean up after test.""" diff --git a/tests/test_sync_module.py b/tests/test_sync_module.py index 07d654fa..7c623d1b 100644 --- a/tests/test_sync_module.py +++ b/tests/test_sync_module.py @@ -24,7 +24,7 @@ def setUp(self): 'TOKEN_AUTH': 'foobar123' } self.blink.urls = blinkpy.BlinkURLHandler('test') - self.blink.sync = BlinkSyncModule(self.blink) + self.blink.sync['test'] = BlinkSyncModule(self.blink, 'test', '1234') self.camera = BlinkCamera(self.blink.sync) def tearDown(self): @@ -35,12 +35,12 @@ def tearDown(self): def test_get_events(self, mock_resp): """Test get events function.""" mock_resp.return_value = {'event': True} - self.assertEqual(self.blink.sync.get_events(), True) + self.assertEqual(self.blink.sync['test'].get_events(), True) def test_get_camera_info(self, mock_resp): """Test get camera info function.""" mock_resp.return_value = {'devicestatus': True} - self.assertEqual(self.blink.sync.get_camera_info(), True) + self.assertEqual(self.blink.sync['test'].get_camera_info(), True) def test_get_videos_one_page(self, mock_resp): """Test video access.""" @@ -54,13 +54,13 @@ def test_get_videos_one_page(self, mock_resp): expected_videos = {'foobar': [ {'clip': '/test/clip_1900_01_01_12_00_00AM.mp4', 'thumb': '/test/thumb'}]} - expected_records = {'foobar': ['1900_01_01_12_00_00AM']} + expected_recs = {'foobar': ['1900_01_01_12_00_00AM']} expected_clips = {'foobar': { '1900_01_01_12_00_00AM': '/test/clip_1900_01_01_12_00_00AM.mp4'}} - self.blink.sync.get_videos(start_page=0, end_page=0) - self.assertEqual(self.blink.sync.videos, expected_videos) - self.assertEqual(self.blink.sync.record_dates, expected_records) - self.assertEqual(self.blink.sync.all_clips, expected_clips) + self.blink.sync['test'].get_videos(start_page=0, end_page=0) + self.assertEqual(self.blink.sync['test'].videos, expected_videos) + self.assertEqual(self.blink.sync['test'].record_dates, expected_recs) + self.assertEqual(self.blink.sync['test'].all_clips, expected_clips) def test_get_videos_multi_page(self, mock_resp): """Test video access with multiple pages.""" @@ -71,17 +71,16 @@ def test_get_videos_multi_page(self, mock_resp): 'thumbnail': '/foobar' } ] - self.blink.sync.get_videos() + self.blink.sync['test'].get_videos() self.assertEqual(mock_resp.call_count, 2) mock_resp.reset_mock() - self.blink.sync.get_videos(start_page=0, end_page=9) + self.blink.sync['test'].get_videos(start_page=0, end_page=9) self.assertEqual(mock_resp.call_count, 10) def test_sync_start(self, mock_resp): """Test sync start function.""" mock_resp.side_effect = [ {'syncmodule': { - 'name': 'test', 'id': 1234, 'network_id': 5678, 'serial': '12345678', @@ -93,9 +92,9 @@ def test_sync_start(self, mock_resp): None, None ] - self.blink.sync.start() - self.assertEqual(self.blink.sync.name, 'test') - self.assertEqual(self.blink.sync.sync_id, 1234) - self.assertEqual(self.blink.sync.network_id, 5678) - self.assertEqual(self.blink.sync.serial, '12345678') - self.assertEqual(self.blink.sync.status, 'foobar') + self.blink.sync['test'].start() + self.assertEqual(self.blink.sync['test'].name, 'test') + self.assertEqual(self.blink.sync['test'].sync_id, 1234) + self.assertEqual(self.blink.sync['test'].network_id, 5678) + self.assertEqual(self.blink.sync['test'].serial, '12345678') + self.assertEqual(self.blink.sync['test'].status, 'foobar') From 84f987d29c8e8931409fdf9ac9541854f8a1606b Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 23 Nov 2018 16:46:20 -0500 Subject: [PATCH 08/17] Updated to reflect multi-sync unit support --- README.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index b6641b7f..8a7029fc 100644 --- a/README.rst +++ b/README.rst @@ -51,7 +51,7 @@ The simplest way to use this package from a terminal is to call ``Blink.start()` If you would like to log in without setting up the cameras or system, you can simply call the ``Blink.login()`` function which will prompt for a username and password and then authenticate with the server. This is useful if you want to avoid use of the ``start()`` function which simply acts as a wrapper for more targeted API methods. -Cameras are instantiated as individual ``BlinkCamera`` classes within a ``BlinkSyncModule`` instance. Note: currently the API only supports one sync module, but multiple sync modules are planned to be supported in the future. +Cameras are instantiated as individual ``BlinkCamera`` classes within a ``BlinkSyncModule`` instance. All of your sync modules are stored within the ``Blink.sync`` dictionary and can be accessed using the name of the sync module as the key (this is the name of your sync module in the Blink App). The below code will display cameras and their available attributes: @@ -62,7 +62,8 @@ The below code will display cameras and their available attributes: blink = blinkpy.Blink(username='YOUR USER NAME', password='YOUR PASSWORD') blink.start() - for name, camera in blink.sync.cameras.items(): + for sync_name, sync in blink.sync.items(): + for name, camera in blink.sync[sync_name].cameras.items(): print(name) # Name of the camera print(camera.attributes) # Print available attributes of camera @@ -70,7 +71,7 @@ The most recent images and videos can be accessed as a bytes-object via internal .. code:: python - camera = blink.sync.camera['SOME CAMERA NAME'] + camera = blink.sync['SYNC NAME'].camera['SOME CAMERA NAME'] blink.refresh(force_cache=True) # force a cache update USE WITH CAUTION camera.image_from_cache.raw # bytes-like image object (jpg) camera.video_from_cache.raw # bytes-like video object (mp4) @@ -79,7 +80,7 @@ The ``blinkpy`` api also allows for saving images and videos to a file and snapp .. code:: python - camera = blink.sync.camera['SOME CAMERA NAME'] + camera = blink.sync['SYNC NAME'].camera['SOME CAMERA NAME'] camera.snap_picture() # Take a new picture with the camera blink.refresh() # Get new information from server camera.image_to_file('/local/path/for/image.jpg') From 5a8af75a5bdfc1f43b19cb2ee737795ee52f545b Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 23 Nov 2018 17:47:44 -0500 Subject: [PATCH 09/17] Added cameras dict to blink to keep track of ALL cameras --- README.rst | 11 +++++------ blinkpy/blinkpy.py | 11 ++++++++++- blinkpy/helpers/util.py | 9 +++++++++ tests/test_blink_functions.py | 12 ++++++++++++ 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 8a7029fc..9b6a31a1 100644 --- a/README.rst +++ b/README.rst @@ -62,16 +62,15 @@ The below code will display cameras and their available attributes: blink = blinkpy.Blink(username='YOUR USER NAME', password='YOUR PASSWORD') blink.start() - for sync_name, sync in blink.sync.items(): - for name, camera in blink.sync[sync_name].cameras.items(): - print(name) # Name of the camera - print(camera.attributes) # Print available attributes of camera + for name, camera in blink.cameras.items(): + print(name) # Name of the camera + print(camera.attributes) # Print available attributes of camera The most recent images and videos can be accessed as a bytes-object via internal variables. These can be updated with calls to ``Blink.refresh()`` but will only make a request if motion has been detected or other changes have been found. This can be overridden with the ``force_cache`` flag, but this should be used for debugging only since it overrides the internal request throttling. .. code:: python - camera = blink.sync['SYNC NAME'].camera['SOME CAMERA NAME'] + camera = blink.cameras['SOME CAMERA NAME'] blink.refresh(force_cache=True) # force a cache update USE WITH CAUTION camera.image_from_cache.raw # bytes-like image object (jpg) camera.video_from_cache.raw # bytes-like video object (mp4) @@ -80,7 +79,7 @@ The ``blinkpy`` api also allows for saving images and videos to a file and snapp .. code:: python - camera = blink.sync['SYNC NAME'].camera['SOME CAMERA NAME'] + camera = blink.cameras['SOME CAMERA NAME'] camera.snap_picture() # Take a new picture with the camera blink.refresh() # Get new information from server camera.image_to_file('/local/path/for/image.jpg') diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index 34b5d387..65dfdc68 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -19,7 +19,7 @@ from blinkpy import api from blinkpy.sync_module import BlinkSyncModule from blinkpy.helpers.util import ( - create_session, BlinkURLHandler, + create_session, merge_dicts, BlinkURLHandler, BlinkAuthenticationException) from blinkpy.helpers.constants import ( BLINK_URL, LOGIN_URL, LOGIN_BACKUP_URL) @@ -57,6 +57,7 @@ def __init__(self, username=None, password=None, self.refresh_rate = refresh_rate self.session = None self.networks = [] + self.cameras = CaseInsensitiveDict({}) self._login_url = LOGIN_URL @property @@ -81,6 +82,7 @@ def start(self): sync_module = BlinkSyncModule(self, network_name, network_id) sync_module.start() self.sync[network_name] = sync_module + self.cameras = self.merge_cameras() def login(self): """Prompt user for username and password.""" @@ -175,3 +177,10 @@ def check_if_ok_to_update(self): self.last_refresh = current_time return True return False + + def merge_cameras(self): + """Merge all sync camera dicts into one.""" + combined = CaseInsensitiveDict({}) + for sync_name, sync in self.sync.items(): + combined = merge_dicts(combined, sync.cameras) + return combined diff --git a/blinkpy/helpers/util.py b/blinkpy/helpers/util.py index 710f7bb0..f31bbb5e 100644 --- a/blinkpy/helpers/util.py +++ b/blinkpy/helpers/util.py @@ -9,6 +9,15 @@ _LOGGER = logging.getLogger(__name__) +def merge_dicts(dict_a, dict_b): + """Merge two dictionaries into one.""" + duplicates = [val for val in dict_a if val in dict_b] + if duplicates: + _LOGGER.warning(("Duplicates found during merge: %s. " + "Renaming is recommended."), duplicates) + return {**dict_a, **dict_b} + + def create_session(): """Create a session for blink communication.""" sess = Session() diff --git a/tests/test_blink_functions.py b/tests/test_blink_functions.py index 1e17adf1..bcea12bd 100644 --- a/tests/test_blink_functions.py +++ b/tests/test_blink_functions.py @@ -70,3 +70,15 @@ def test_backup_url(self, req, mock_sess): self.assertEqual(self.blink.region, 'UNKNOWN') # pylint: disable=protected-access self.assertEqual(self.blink._token, 'foobar123') + + def test_merge_cameras(self, mock_sess): + """Test merge camera functionality.""" + first_dict = {'foo': 'bar', 'test': 123} + next_dict = {'foobar': 456, 'bar': 'foo'} + self.blink.sync['foo'] = BlinkSyncModule(self.blink, 'foo', 1) + self.blink.sync['bar'] = BlinkSyncModule(self.blink, 'bar', 2) + self.blink.sync['foo'].cameras = first_dict + self.blink.sync['bar'].cameras = next_dict + result = self.blink.merge_cameras() + expected = {'foo': 'bar', 'test': 123, 'foobar': 456, 'bar': 'foo'} + self.assertEqual(expected, result) From ece65a9158df795595618646a8e72880127318dc Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 23 Nov 2018 17:52:12 -0500 Subject: [PATCH 10/17] Fix linting errors --- blinkpy/blinkpy.py | 4 ++-- blinkpy/helpers/util.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index 65dfdc68..e4f23ef6 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -181,6 +181,6 @@ def check_if_ok_to_update(self): def merge_cameras(self): """Merge all sync camera dicts into one.""" combined = CaseInsensitiveDict({}) - for sync_name, sync in self.sync.items(): - combined = merge_dicts(combined, sync.cameras) + for sync in self.sync: + combined = merge_dicts(combined, self.sync[sync].cameras) return combined diff --git a/blinkpy/helpers/util.py b/blinkpy/helpers/util.py index f31bbb5e..4bcc497f 100644 --- a/blinkpy/helpers/util.py +++ b/blinkpy/helpers/util.py @@ -14,7 +14,7 @@ def merge_dicts(dict_a, dict_b): duplicates = [val for val in dict_a if val in dict_b] if duplicates: _LOGGER.warning(("Duplicates found during merge: %s. " - "Renaming is recommended."), duplicates) + "Renaming is recommended."), duplicates) return {**dict_a, **dict_b} From c84a0deb5fc5636c3796d4d18185b183cb3f77ca Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 23 Nov 2018 18:08:58 -0500 Subject: [PATCH 11/17] Add sync parent to attributes --- blinkpy/camera.py | 1 + 1 file changed, 1 insertion(+) diff --git a/blinkpy/camera.py b/blinkpy/camera.py index d36a5da5..992310b9 100644 --- a/blinkpy/camera.py +++ b/blinkpy/camera.py @@ -47,6 +47,7 @@ def attributes(self): 'motion_detected': self.motion_detected, 'wifi_strength': self.wifi_strength, 'network_id': self.sync.network_id, + 'sync_module': self.sync.name, 'last_record': self.last_record } return attributes From 90f30deea875e15f29e1294704776b80ab157a99 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 23 Nov 2018 19:20:18 -0500 Subject: [PATCH 12/17] Fix broken sync refresh --- blinkpy/blinkpy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blinkpy/blinkpy.py b/blinkpy/blinkpy.py index e4f23ef6..7285465c 100644 --- a/blinkpy/blinkpy.py +++ b/blinkpy/blinkpy.py @@ -163,8 +163,8 @@ def refresh(self, force_cache=False): :param force_cache: Force an update of the camera cache """ if self.check_if_ok_to_update() or force_cache: - _LOGGER.debug("Attempting refresh of cameras.") - for sync_module in self.sync: + for sync_name, sync_module in self.sync.items(): + _LOGGER.debug("Attempting refresh of sync %s", sync_name) sync_module.refresh(force_cache=force_cache) def check_if_ok_to_update(self): From f7a3b1d0ef261d6d8a17f37c0fddc78dda0151a4 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 23 Nov 2018 19:37:38 -0500 Subject: [PATCH 13/17] Update docs --- docs/api/blinkpy.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/api/blinkpy.rst b/docs/api/blinkpy.rst index c56e5ba8..c590aa0b 100644 --- a/docs/api/blinkpy.rst +++ b/docs/api/blinkpy.rst @@ -1,15 +1,22 @@ .. _core_module: -:mod:`blinkpy` +:mod: blinkpy ---------------------- .. automodule:: blinkpy.blinkpy + :members: .. automodule:: blinkpy.sync_module + :members: .. automodule:: blinkpy.camera + :members: + +.. automodule:: blinkpy.api + :members: .. automodule:: blinkpy.helpers.util + :members: .. autoclass:: blinkpy.blinkpy.Blink :members: From f9615a2d9078bf9b1788cb9d801e783d3675d156 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 23 Nov 2018 19:39:23 -0500 Subject: [PATCH 14/17] fix docs again --- docs/api/blinkpy.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/blinkpy.rst b/docs/api/blinkpy.rst index c590aa0b..45a28b4a 100644 --- a/docs/api/blinkpy.rst +++ b/docs/api/blinkpy.rst @@ -1,6 +1,6 @@ .. _core_module: -:mod: blinkpy +:mod: API Reference ---------------------- .. automodule:: blinkpy.blinkpy From 643ae8c387fbd2f95ff857d0c8241fb9a718f0b2 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 23 Nov 2018 19:44:25 -0500 Subject: [PATCH 15/17] trying again --- docs/api/blinkpy.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/api/blinkpy.rst b/docs/api/blinkpy.rst index 45a28b4a..a88fc62f 100644 --- a/docs/api/blinkpy.rst +++ b/docs/api/blinkpy.rst @@ -4,19 +4,14 @@ ---------------------- .. automodule:: blinkpy.blinkpy - :members: .. automodule:: blinkpy.sync_module - :members: .. automodule:: blinkpy.camera - :members: .. automodule:: blinkpy.api - :members: .. automodule:: blinkpy.helpers.util - :members: .. autoclass:: blinkpy.blinkpy.Blink :members: From 8f250d204f25378ae3341031ebc96f30ee7bb099 Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 23 Nov 2018 20:08:21 -0500 Subject: [PATCH 16/17] Fixed docs for real --- docs/api/blinkpy.rst | 23 ++++------------------- docs/api/implemented.rst | 6 ++++++ 2 files changed, 10 insertions(+), 19 deletions(-) create mode 100644 docs/api/implemented.rst diff --git a/docs/api/blinkpy.rst b/docs/api/blinkpy.rst index a88fc62f..edd9efa5 100644 --- a/docs/api/blinkpy.rst +++ b/docs/api/blinkpy.rst @@ -1,32 +1,17 @@ .. _core_module: -:mod: API Reference +Blinkpy ---------------------- .. automodule:: blinkpy.blinkpy - -.. automodule:: blinkpy.sync_module - -.. automodule:: blinkpy.camera - -.. automodule:: blinkpy.api - -.. automodule:: blinkpy.helpers.util - -.. autoclass:: blinkpy.blinkpy.Blink :members: -.. autoclass:: blinkpy.sync_module.BlinkSyncModule +.. automodule:: blinkpy.sync_module :members: -.. autoclass:: blinkpy.camera.BlinkCamera +.. automodule:: blinkpy.camera :members: -.. autoclass:: blinkpy.helpers.util.BlinkURLHandler +.. automodule:: blinkpy.helpers.util :members: -.. autofunction:: blinkpy.helpers.util.create_session - -.. autofunction:: blinkpy.helpers.util.http_req - -.. autofunction:: blinkpy.helpers.util.attempt_reauthorization diff --git a/docs/api/implemented.rst b/docs/api/implemented.rst new file mode 100644 index 00000000..d0ed9b1f --- /dev/null +++ b/docs/api/implemented.rst @@ -0,0 +1,6 @@ +API Reference +---------------------- + +.. automodule:: blinkpy.api + :members: + From 1d67764dc89bb82d40ba9ec1e66c43065d18c6db Mon Sep 17 00:00:00 2001 From: Kevin Fronczak Date: Fri, 23 Nov 2018 20:09:46 -0500 Subject: [PATCH 17/17] version bump --- blinkpy/helpers/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blinkpy/helpers/constants.py b/blinkpy/helpers/constants.py index c0e1538f..a3ca90c7 100644 --- a/blinkpy/helpers/constants.py +++ b/blinkpy/helpers/constants.py @@ -4,7 +4,7 @@ MAJOR_VERSION = 0 MINOR_VERSION = 11 -PATCH_VERSION = '0.dev' +PATCH_VERSION = 0 __version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)