Skip to content

Commit

Permalink
Merge pull request #129 from fronzbot/dev
Browse files Browse the repository at this point in the history
0.11.0
  • Loading branch information
fronzbot authored Nov 24, 2018
2 parents ae946db + 1d67764 commit 1a24e18
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 116 deletions.
88 changes: 47 additions & 41 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<YOUR_GIT_USERNAME>/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 <your-branch-name>``


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/<YOUR_GIT_USERNAME>/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 <your-branch-name>``
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
Expand All @@ -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/<testname>.py -x`. This will run the test `<testname>.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

Expand All @@ -73,11 +87,3 @@ If rebase detects conflicts, repeat the following process until all changes have
2. Add the modified file: ``git add <file>`` 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.
23 changes: 13 additions & 10 deletions README.rst
Original file line number Diff line number Diff line change
@@ -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:
~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -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 <https://github.com/fronzbot/blinkpy/blob/dev/CONTRIBUTING.md>`__.

For more information on how to use this library, please `read the docs <https://blinkpy.readthedocs.io/en/latest/>`__.

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 <https://home-assistant.io/components/blink>`__ in `homeassistant <https://home-assistant.io/>`__.

Usage
=========
Quick Start
=============
The simplest way to use this package from a terminal is to call ``Blink.start()`` which will prompt for your Blink username and password and then log you in. Alternatively, you can instantiate the Blink class with a username and password, and call ``Blink.start()`` to login and setup without prompt, as shown below. 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
Expand All @@ -48,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:

Expand All @@ -59,15 +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 name, camera in blink.sync.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.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)
Expand All @@ -76,7 +79,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.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')
Expand Down
49 changes: 32 additions & 17 deletions blinkpy/blinkpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
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
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, PROJECT_URL)
BLINK_URL, LOGIN_URL, LOGIN_BACKUP_URL)

REFRESH_RATE = 30

Expand All @@ -46,16 +47,17 @@ 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 = CaseInsensitiveDict({})
self.region = None
self.region_id = None
self.last_refresh = None
self.refresh_rate = refresh_rate
self.session = None
self.networks = []
self.cameras = CaseInsensitiveDict({})
self._login_url = LOGIN_URL

@property
Expand All @@ -75,9 +77,12 @@ def start(self):
else:
self.get_auth_token()

self.get_ids()
self.sync = BlinkSyncModule(self)
self.sync.start()
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_name] = sync_module
self.cameras = self.merge_cameras()

def login(self):
"""Prompt user for username and password."""
Expand Down Expand Up @@ -136,18 +141,20 @@ 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(network)
self.network_id = all_networks.pop(0)
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']:
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
return network_dict

def refresh(self, force_cache=False):
"""
Expand All @@ -156,8 +163,9 @@ 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.")
self.sync.refresh(force_cache=force_cache)
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):
"""Check if it is ok to perform an http request."""
Expand All @@ -169,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 in self.sync:
combined = merge_dicts(combined, self.sync[sync].cameras)
return combined
1 change: 1 addition & 0 deletions blinkpy/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions blinkpy/helpers/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import os

MAJOR_VERSION = 0
MINOR_VERSION = 10
PATCH_VERSION = 3
MINOR_VERSION = 11
PATCH_VERSION = 0

__version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)

Expand Down
9 changes: 9 additions & 0 deletions blinkpy/helpers/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
7 changes: 3 additions & 4 deletions blinkpy/sync_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@
class BlinkSyncModule():
"""Class to initialize sync module."""

def __init__(self, blink):
def __init__(self, blink, network_name, network_id):
"""
Initialize Blink sync module.
:param blink: Blink class instantiation
"""
self.blink = blink
self._auth_header = blink.auth_header
self.network_id = blink.network_id
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
Expand Down Expand Up @@ -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']
Expand Down
21 changes: 4 additions & 17 deletions docs/api/blinkpy.rst
Original file line number Diff line number Diff line change
@@ -1,30 +1,17 @@
.. _core_module:

:mod:`blinkpy`
Blinkpy
----------------------

.. automodule:: blinkpy.blinkpy

.. automodule:: blinkpy.sync_module

.. automodule:: blinkpy.camera

.. 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
6 changes: 6 additions & 0 deletions docs/api/implemented.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
API Reference
----------------------

.. automodule:: blinkpy.api
:members:

Loading

0 comments on commit 1a24e18

Please sign in to comment.