Compare commits

...
73 Commits
Author SHA1 Message Date
Kevin FronczakandGitHub deba0cbba6 Merge pull request #225 from fronzbot/dev
0.14.3
2020-04-22 10:22:48 -04:00
Kevin FronczakandGitHub 0c3359abcf Changes for release 0.14.3 2020-04-22 10:20:42 -04:00
Kevin FronczakandGitHub a76069174d Version bump to 0.14.3 2020-04-22 10:17:21 -04:00
Kevin FronczakandGitHub 19c9178db7 Merge pull request #223 from fronzbot/fronzbot-patch-1
Update README.rst
2020-04-15 11:51:55 -04:00
Kevin FronczakandGitHub 2ebf2db04d Update README.rst 2020-04-15 11:47:38 -04:00
Kevin FronczakandGitHub 26de89c1b9 Merge pull request #222 from fronzbot/no-camera-enabled-bug
Log response code and message if not trying to re-authorize
2020-04-15 11:26:14 -04:00
Kevin Fronczak 8f68aad238 Create codecov.yml for confugration 2020-04-15 11:21:38 -04:00
Kevin Fronczak 7b586e2947 Log response code and message if not trying to re-authorize (fixes #213) 2020-04-15 11:10:13 -04:00
Kevin FronczakandGitHub 800ddeaf7a Dev version bump 2020-04-15 11:09:46 -04:00
Kevin FronczakandGitHub 4ed2e3dab5 dev version update for pre-release 2020-04-13 20:50:39 -04:00
Kevin FronczakandGitHub 2b9c82b432 Merge pull request #221 from fronzbot/mask-motion-with-arm
Mask motion detection with sync module arm status
2020-04-13 20:48:49 -04:00
Kevin Fronczak 3e7c9f3d5c Mask motion detection with sync module arm status 2020-04-13 20:42:55 -04:00
Kevin FronczakandGitHub 8d0b51ed9c Merge pull request #220 from fronzbot/fix-video-saving
Only store motion clip if motion occurred since last refresh
2020-04-13 20:31:02 -04:00
Kevin Fronczak c178fa5457 Only store motion clip if motion occured since last refresh 2020-04-13 20:02:13 -04:00
Kevin FronczakandGitHub 532ad92714 Merge pull request #219 from fronzbot/json-creds
Load credentials from json file
2020-04-13 19:17:34 -04:00
Kevin Fronczak deb0a682d8 revert tox 2020-04-13 19:05:56 -04:00
Kevin Fronczak 12e22dee8e Load credentials from json file 2020-04-13 18:53:37 -04:00
Kevin FronczakandGitHub ae0f954632 Merge pull request #218 from fronzbot/fix-codecov
Update coverage action
2020-04-13 17:02:40 -04:00
Kevin Fronczak b92941538b Update coverage action 2020-04-13 16:59:53 -04:00
Kevin FronczakandGitHub ce13444427 Merge pull request #217 from fronzbot/bugfix-205
Remove exc_info fixes #205
2020-04-13 16:48:10 -04:00
Kevin FronczakandGitHub 9c461c4cfa Remove exc_info fixes #205 2020-04-13 16:45:36 -04:00
Kevin FronczakandGitHub 23858c2912 Merge pull request #216 from fronzbot/Github-Actions
Migrate to github actions
2020-04-13 16:41:42 -04:00
Kevin FronczakandGitHub 2284a56c82 Delete .travis.yml 2020-04-13 16:37:46 -04:00
Kevin FronczakandGitHub 323143cb2b Fix name 2020-04-13 16:32:50 -04:00
Kevin FronczakandGitHub d6064a9aac Create coverage.yml 2020-04-13 16:32:33 -04:00
Kevin FronczakandGitHub a982aa595f Remove coverage 2020-04-13 16:31:48 -04:00
Kevin FronczakandGitHub 54c52a7f1e Update tests.yml 2020-04-13 16:30:32 -04:00
Kevin FronczakandGitHub bdb8cedf64 Update tests.yml 2020-04-13 16:29:15 -04:00
Kevin FronczakandGitHub 554b18adfe Update README.rst 2020-04-13 16:24:50 -04:00
Kevin FronczakandGitHub 5619af2a00 Update build badge 2020-04-13 16:24:27 -04:00
Kevin FronczakandGitHub 957003aeb1 Rename workflow 2020-04-13 16:22:49 -04:00
Kevin FronczakandGitHub 6fc64a486e Create publish.yml 2020-04-13 16:18:40 -04:00
Kevin FronczakandGitHub 534e3fb34e Reduce to only one VM 2020-04-13 16:14:17 -04:00
Kevin FronczakandGitHub 9ab346ed91 Create tests.yml 2020-04-13 16:11:09 -04:00
Kevin FronczakandGitHub 1bf5dda803 Create lint.yml 2020-04-13 16:02:14 -04:00
Kevin FronczakandGitHub 6cd8fd8ab2 Merge pull request #215 from fronzbot/codecov
Migrate from coveralls to codecov
2020-04-09 15:20:09 -04:00
Kevin Fronczak a689839616 Update codecov badge 2020-04-09 15:16:19 -04:00
Kevin Fronczak 783aa5e547 Migrate from coveralls to codecov 2020-04-09 15:07:30 -04:00
Kevin FronczakandGitHub a6ff2fa0df Merge pull request #214 from fronzbot/bugfix-motion
Fix motion detection getting stuck 'on'
2020-04-09 15:05:44 -04:00
Kevin Fronczak eb498bfb22 Pin coverage in traviswq 2020-04-09 15:01:33 -04:00
Kevin Fronczak 2ebbe46ada Change time conversion to something that supports older python versions 2020-04-09 14:51:06 -04:00
Kevin Fronczak 2f4e8fcade Ignore flake8-black errors 2020-04-09 14:14:47 -04:00
Kevin Fronczak efe05525e4 Fix tests, pin coverage due to sqlite problems 2020-04-09 14:11:19 -04:00
Kevin Fronczak 4122ae8f64 Added time check on latest video 2020-04-08 18:45:59 -04:00
Kevin FronczakandGitHub 5d296cc836 Merge pull request #203 from fronzbot/merge-v0.14.2-into-dev
Merge v0.14.2 into dev
2019-10-12 14:48:45 -04:00
Kevin Fronczak fa50e315a9 dev version bump 2019-10-12 14:42:34 -04:00
Kevin FronczakandGitHub 4a4811bee2 Merge pull request #202 from fronzbot/release-0.14.2
Release 0.14.2
2019-10-12 14:38:35 -04:00
Kevin Fronczak ad7ece4d45 Fixed version.wq 2019-10-12 14:32:26 -04:00
Kevin Fronczak 17d1aa9690 Update changelog, bumped version 2019-10-12 14:31:20 -04:00
Kevin FronczakandGitHub b00839ecb3 Merge pull request #198 from 3ch01c/docker
Add Docker example files
2019-10-12 14:01:25 -04:00
Kevin Fronczak 4900ea8ba6 Add app to tox check 2019-10-12 13:41:57 -04:00
Kevin Fronczak 4fb16398e4 Restructured to allow for future expansion
- Removed docker_compose
2019-10-12 13:27:14 -04:00
Jack Miner a1fc0c5a2b Added TIMEDELTA environment variable to configure how far back to download videos. Wrapped long lines to make linter happy. 2019-09-22 11:43:22 -06:00
Jack Miner 6adb252cdd dockerized 2019-09-14 02:01:44 -06:00
Kevin FronczakandGitHub 3637bbad8d Merge pull request #196 from fronzbot/req
Update python slugify version, better versioning
2019-09-09 11:12:45 -04:00
Kevin FronczakandGitHub 694efe71f8 Update python slugify version, better versioning 2019-09-02 10:29:14 -04:00
Kevin FronczakandGitHub 315735bc2f Merge pull request #192 from fronzbot/dev-version-bump
Dev verison bump
2019-06-20 13:56:16 -04:00
Kevin Fronczak db6403b468 Dev verison bump 2019-06-20 13:44:11 -04:00
Kevin FronczakandGitHub 92dda40e84 Merge pull request #191 from fronzbot/0.14.1
0.14.1
2019-06-20 13:41:22 -04:00
Kevin Fronczak 6c8fc0abce Updated changes 2019-06-20 13:34:59 -04:00
Kevin Fronczak 2d74b12d43 Version bump 2019-06-20 13:32:46 -04:00
Kevin FronczakandGitHub f4480da276 Merge pull request #180 from fronzbot/dev
0.14.0
2019-05-23 16:56:46 -04:00
Kevin FronczakandGitHub afdd73118d Merge pull request #165 from fronzbot/dev
0.13.1
2019-03-01 21:59:29 -05:00
Kevin FronczakandGitHub 0262f14a53 Merge pull request #163 from fronzbot/dev
0.13.0
2019-03-01 21:04:36 -05:00
Kevin FronczakandGitHub ff8f091a74 Merge pull request #154 from fronzbot/dev
0.12.1
2019-01-31 18:22:09 -08:00
Kevin FronczakandGitHub 77e200f6a5 Merge pull request #152 from fronzbot/dev
0.12.0
2019-01-31 17:05:03 -08:00
Kevin FronczakandGitHub f3a8036649 Merge pull request #141 from fronzbot/dev
0.11.2
2019-01-23 08:05:05 -08:00
Kevin FronczakandGitHub 853ccbce79 Merge pull request #136 from fronzbot/dev
0.11.1
2019-01-02 09:19:08 -08:00
Kevin FronczakandGitHub 1a24e1896f Merge pull request #129 from fronzbot/dev
0.11.0
2018-11-23 20:27:40 -05:00
Kevin FronczakandGitHub ae946dbb53 Merge pull request #121 from fronzbot/dev
0.10.3
2018-11-20 21:47:42 -05:00
Kevin FronczakandGitHub 80c6444c2e Merge pull request #112 from fronzbot/dev
0.10.2
2018-10-30 20:22:08 -04:00
Kevin FronczakandGitHub e872fc9ce5 Merge pull request #105 from fronzbot/dev
0.10.1
2018-10-18 13:19:49 -04:00
Kevin FronczakandGitHub 4e6957a772 Merge pull request #100 from fronzbot/dev
Release 0.10.0
2018-10-16 17:43:21 -04:00
29 changed files with 492 additions and 65 deletions
+3 -3
View File
@@ -1,5 +1,5 @@
[run]
omit =
helpers/*
parallel = true
omit =
tests/*
setup.py
setup.py
+2
View File
@@ -0,0 +1,2 @@
[flake8]
ignore = BLK100
+41
View File
@@ -0,0 +1,41 @@
name: coverage
on:
push:
branches: [ master, dev ]
pull_request:
branches: [ master, dev ]
jobs:
build:
runs-on: ${{ matrix.platform }}
strategy:
max-parallel: 4
matrix:
platform:
- ubuntu-latest
python-version: [3.8]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements_test.txt
pip install tox
pip install codecov
- name: Test
run: |
tox -r -e cov
- name: Codecov
uses: codecov/codecov-action@v1.0.6
with:
flags: unittests
file: ./coverage.xml
name: blinkpy
fail_ci_if_error: true
+34
View File
@@ -0,0 +1,34 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Lint
on:
push:
branches: [ master, dev ]
pull_request:
branches: [ master, dev ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements_test.txt
pip install tox
- name: Lint
run: |
tox -r -e lint
+31
View File
@@ -0,0 +1,31 @@
# This workflows will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
name: Upload Python Package
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: '3.7'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py bdist_wheel
twine upload dist/*
+33
View File
@@ -0,0 +1,33 @@
name: build
on:
push:
branches: [ master, dev ]
pull_request:
branches: [ master, dev ]
jobs:
build:
runs-on: ${{ matrix.platform }}
strategy:
max-parallel: 4
matrix:
platform:
- ubuntu-latest
python-version: [3.5, 3.6, 3.7, 3.8]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements_test.txt
pip install tox
- name: Test
run: |
tox -r
+1
View File
@@ -4,6 +4,7 @@
__pycache__/*
htmlcov/*
.coverage
coverage.xml
*.pyc
*.egg*/*
dist/*
+4
View File
@@ -4,4 +4,8 @@ python:
ignored_directories:
- docs
flake8:
enabled: true
config_file: .flake8
fail_on_violations: true
-23
View File
@@ -1,23 +0,0 @@
sudo: required
matrix:
fast_finish: true
include:
- python: "3.5.3"
env: TOXENV=lint
- python: "3.5.3"
env: TOXENV=py35
- python: "3.6"
env: TOXENV=py36
- python: "3.6"
env: TOXENV=build
- python: "3.7"
env: TOXENV=py37
dist: xenial
- python: "3.8-dev"
env: TOXENV=py38
dist: xenial
install: pip install -U tox coveralls
language: python
script: tox
after_success: coveralls
+19
View File
@@ -3,6 +3,25 @@ Changelog
A list of changes between each release
0.14.3 (2020-04-22)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Add time check on recorded videos before determining motion
- Fix motion detection variable suck to ``True``
- Add ability to load credentials from a json file
- Only allow ``motion_detected`` variable to trigger if system was armed
- Log response message from server if not attempting a re-authorization
0.14.2 (2019-10-12)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Update dependencies
- Dockerize `(@3ch01c <https://github.com/fronzbot/blinkpy/pull/198>__)`
0.14.1 (2019-06-20)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fix timeout problems blocking blinkpy startup
- Updated login urls using ``rest-region`` subdomain
- Removed deprecated thumbanil recovery from homescreen
0.14.0 (2019-05-23)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**Breaking Changes:**
+12
View File
@@ -0,0 +1,12 @@
FROM python:3.7-alpine
LABEL maintainer="Kevin Fronczak <kfronczak@gmail.com>"
VOLUME /media
RUN python -m pip install --upgrade pip
RUN pip3 install blinkpy
COPY app/ .
ENTRYPOINT ["python", "./app.py"]
CMD []
+21 -4
View File
@@ -55,6 +55,23 @@ 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.
In addition, you can also save your credentials in a json file and initialize Blink with the credential file as follows:
.. code:: python
from blinkpy import blinkpy
blink = blinkpy.Blink(cred_file="path/to/credentials.json")
blink.start()
The credential file must be json formatted with a ``username`` and ``password`` key like follows:
.. code:: json
{
"username": "YOUR USER NAME",
"password": "YOUR PASSWORD"
}
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:
@@ -100,10 +117,10 @@ Example usage, which downloads all videos recorded since July 4th, 2018 at 9:34a
blink.download_videos('/home/blink', since='2018/07/04 09:34')
.. |Build Status| image:: https://travis-ci.org/fronzbot/blinkpy.svg?branch=dev
:target: https://travis-ci.org/fronzbot/blinkpy
.. |Coverage Status| image:: https://coveralls.io/repos/github/fronzbot/blinkpy/badge.svg?branch=dev
:target: https://coveralls.io/github/fronzbot/blinkpy?branch=dev
.. |Build Status| image:: https://github.com/fronzbot/blinkpy/workflows/build/badge.svg
:target: https://github.com/fronzbot/blinkpy/actions?query=workflow%3Abuild
.. |Coverage Status| image:: https://codecov.io/gh/fronzbot/blinkpy/branch/dev/graph/badge.svg
:target: https://codecov.io/gh/fronzbot/blinkpy
.. |PyPi Version| image:: https://img.shields.io/pypi/v/blinkpy.svg
:target: https://pypi.python.org/pypi/blinkpy
.. |Docs| image:: https://readthedocs.org/projects/blinkpy/badge/?version=latest
+1
View File
@@ -0,0 +1 @@
"""Python init file for app.py."""
+31
View File
@@ -0,0 +1,31 @@
"""Script to run blinkpy as an app."""
from os import environ
from datetime import datetime, timedelta
from blinkpy import blinkpy
USERNAME = environ.get('USERNAME')
PASSWORD = environ.get('PASSWORD')
TIMEDELTA = timedelta(environ.get('TIMEDELTA', 1))
def get_date():
"""Return now - timedelta for blinkpy."""
return (datetime.now() - TIMEDELTA).isoformat()
def download_videos(blink, save_dir='/media'):
"""Make request to download videos."""
blink.download_videos(save_dir, since=get_date())
def start():
"""Startup blink app."""
blink = blinkpy.Blink(username=USERNAME, password=PASSWORD)
blink.start()
return blink
if __name__ == '__main__':
BLINK = start()
download_videos(BLINK)
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
docker build -t fronzbot/blinkpy:latest ./
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
# bash run.sh [username] [password]
if [ "$#" -ne 2 ]; then
echo ""
echo "ERROR: Requries Blink username and password as arguments."
echo "bash run.sh [username] [password]"
echo ""
exit 1
fi
set -ex
USER=fronzbot
IMAGE=blinkpy
CONFIG=$HOME/blinkpy_media
USERNAME=$1
PASSWORD=$2
mkdir -p $CONFIG
result=$(docker images -q $IMAGE)
if [ $result ]; then
docker rm $IMAGE
fi
docker run -it --name ${IMAGE} \
-v $CONFIG:/media \
-e USERNAME=${USERNAME} \
-e PASSWORD=${PASSWORD} \
$USER/$IMAGE \
/bin/bash
+26 -2
View File
@@ -17,6 +17,7 @@ import os.path
import time
import getpass
import logging
import json
from shutil import copyfileobj
from requests.structures import CaseInsensitiveDict
@@ -42,6 +43,7 @@ class Blink():
"""Class to initialize communication."""
def __init__(self, username=None, password=None,
cred_file=None,
refresh_rate=DEFAULT_REFRESH,
motion_interval=DEFAULT_MOTION_INTERVAL,
legacy_subdomain=False):
@@ -50,6 +52,10 @@ class Blink():
:param username: Blink username (usually email address)
:param password: Blink password
:param cred_file: JSON formatted file to store credentials.
If username and password are given, file
is ignored. Otherwise, username and password
are loaded from file.
:param refresh_rate: Refresh rate of blink information.
Defaults to 15 (seconds)
:param motion_interval: How far back to register motion in minutes.
@@ -62,6 +68,7 @@ class Blink():
"""
self._username = username
self._password = password
self._cred_file = cred_file
self._token = None
self._auth_header = None
self._host = None
@@ -117,8 +124,25 @@ class Blink():
def login(self):
"""Prompt user for username and password."""
self._username = input("Username:")
self._password = getpass.getpass("Password:")
if self._cred_file is not None and os.path.isfile(self._cred_file):
try:
with open(self._cred_file, 'r') as json_file:
creds = json.load(json_file)
self._username = creds['username']
self._password = creds['password']
except ValueError:
_LOGGER.error("Improperly formated json file %s.",
self._cred_file,
exc_info=True)
return False
except KeyError:
_LOGGER.error("JSON file information incomplete %s.",
exc_info=True)
return False
else:
self._username = input("Username:")
self._password = getpass.getpass("Password:")
if self.get_auth_token():
_LOGGER.debug("Login successful!")
return True
+2 -4
View File
@@ -178,8 +178,7 @@ class BlinkCamera():
copyfileobj(response.raw, imgfile)
else:
_LOGGER.error("Cannot write image to file, response %s",
response.status_code,
exc_info=True)
response.status_code)
def video_to_file(self, path):
"""Write video to file.
@@ -190,8 +189,7 @@ class BlinkCamera():
response = self._cached_video
if response is None:
_LOGGER.error("No saved video exist for %s.",
self.name,
exc_info=True)
self.name)
return
with open(path, 'wb') as vidfile:
copyfileobj(response.raw, vidfile)
+2 -2
View File
@@ -4,7 +4,7 @@ import os
MAJOR_VERSION = 0
MINOR_VERSION = 14
PATCH_VERSION = '1.dev0'
PATCH_VERSION = 3
__version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)
@@ -60,7 +60,7 @@ ONLINE = {'online': True, 'offline': False}
'''
OTHER
'''
TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%S%Z'
TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%S%z'
DEFAULT_MOTION_INTERVAL = 1
DEFAULT_REFRESH = 30
+2
View File
@@ -11,3 +11,5 @@ AUTH_TOKEN = (
"Authentication header incorrect. Are you sure you received your token?"
)
REQUEST = (4, "Cannot perform request (get/post type incorrect)")
BLINK_ERRORS = [101, 400, 404]
+20 -2
View File
@@ -2,8 +2,10 @@
import logging
import time
from calendar import timegm
from functools import partial, wraps
from requests import Request, Session, exceptions
import dateutil.parser
from blinkpy.helpers.constants import BLINK_URL, TIMESTAMP_FORMAT
import blinkpy.helpers.errors as ERROR
@@ -11,6 +13,17 @@ import blinkpy.helpers.errors as ERROR
_LOGGER = logging.getLogger(__name__)
def time_to_seconds(timestamp):
"""Convert TIMESTAMP_FORMAT time to seconds."""
try:
dtime = dateutil.parser.isoparse(timestamp)
except ValueError:
_LOGGER.error("Incorrect timestamp format for conversion: %s.",
timestamp)
return False
return timegm(dtime.timetuple())
def get_time(time_to_convert=None):
"""Create blink-compatible timestamp."""
if time_to_convert is None:
@@ -73,16 +86,21 @@ def http_req(blink, url='http://example.com', data=None, headers=None,
try:
response = blink.session.send(prepped, stream=stream)
if json_resp and 'code' in response.json():
if is_retry:
resp_dict = response.json()
code = resp_dict['code']
message = resp_dict['message']
if is_retry and code in ERROR.BLINK_ERRORS:
_LOGGER.error("Cannot obtain new token for server auth.")
return None
else:
elif code in ERROR.BLINK_ERRORS:
headers = attempt_reauthorization(blink)
if not headers:
raise exceptions.ConnectionError
return http_req(blink, url=url, data=data, headers=headers,
reqtype=reqtype, stream=stream,
json_resp=json_resp, is_retry=True)
_LOGGER.warning("Response from server: %s - %s", code, message)
except (exceptions.ConnectionError, exceptions.Timeout):
_LOGGER.info("Cannot connect to server with url %s.", url)
if not is_retry:
+9 -3
View File
@@ -5,6 +5,7 @@ import logging
from requests.structures import CaseInsensitiveDict
from blinkpy import api
from blinkpy.camera import BlinkCamera
from blinkpy.helpers.util import time_to_seconds
from blinkpy.helpers.constants import ONLINE
_LOGGER = logging.getLogger(__name__)
@@ -163,7 +164,7 @@ class BlinkSyncModule():
def check_new_videos(self):
"""Check if new videos since last refresh."""
try:
interval = self.blink.last_refresh - self.motion_interval*60
interval = self.blink.last_refresh - self.motion_interval * 60
except TypeError:
# This is the first start, so refresh hasn't happened yet.
# No need to check for motion.
@@ -187,9 +188,14 @@ class BlinkSyncModule():
name = entry['device_name']
clip = entry['media']
timestamp = entry['created_at']
self.motion[name] = True
self.last_record[name] = {'clip': clip, 'time': timestamp}
if self.check_new_video_time(timestamp):
self.motion[name] = True and self.arm
self.last_record[name] = {'clip': clip, 'time': timestamp}
except KeyError:
_LOGGER.debug("No new videos since last refresh.")
return True
def check_new_video_time(self, timestamp):
"""Check if video has timestamp since last refresh."""
return time_to_seconds(timestamp) > self.blink.last_refresh
+13
View File
@@ -0,0 +1,13 @@
codecov:
branch: dev
coverage:
status:
project:
default:
threshold: 0.02
comment: true
require_ci_to_pass: yes
range: 65..90
round: down
precision: 1
+3 -3
View File
@@ -1,4 +1,4 @@
python-dateutil==2.7.5
requests>=2.20.0
python-slugify==3.0.2
python-dateutil~=2.8.0
requests==2.22.0
python-slugify~=3.0.2
testtools==2.3.0
+9 -8
View File
@@ -1,10 +1,11 @@
flake8==3.5.0
flake8-docstrings==1.3.0
pylint==2.3.0
pydocstyle==2.1.1
pytest==3.7.1
pytest-cov>=2.3.1
pytest-sugar>=0.9.0
pytest-timeout>=1.0.0
coverage==4.5.4
flake8==3.7.9
flake8-docstrings==1.5.0
pylint==2.4.4
pydocstyle==5.0.2
pytest==5.3.5
pytest-cov==2.8.1
pytest-sugar>=0.9.2
pytest-timeout>=1.3.3
restructuredtext-lint>=1.0.1
pygments>=2.2.0
+43 -2
View File
@@ -79,11 +79,52 @@ class TestBlinkSetup(unittest.TestCase):
# pylint: disable=protected-access
self.assertEqual(self.blink_no_cred._password, PASSWORD)
@mock.patch('blinkpy.blinkpy.getpass.getpass')
@mock.patch('blinkpy.blinkpy.Blink.get_auth_token')
def test_no_cred_file(self, getpwd, getauth, mock_sess):
"""Check that normal login occurs when cred file doesn't exist."""
# pylint: disable=protected-access
self.blink._cred_file = '/tmp/fake.file'
getpwd.return_value = PASSWORD
getauth.return_value = True
with mock.patch('builtins.input', return_value=USERNAME):
self.assertTrue(self.blink.login())
def test_exit_on_missing_json(self, mock_sess):
"""Test that we fail on missing json data."""
# pylint: disable=protected-access
self.blink._cred_file = '/tmp/fake.file'
with mock.patch('os.path.isfile', return_value=True):
with mock.patch('builtins.open', mock.mock_open(read_data="{}")):
self.assertFalse(self.blink.login())
def test_exit_on_bad_json(self, mock_sess):
"""Test that we fail on bad json format."""
# pylint: disable=protected-access
self.blink._cred_file = '/tmp/fake.file'
with mock.patch('os.path.isfile', return_value=True):
with mock.patch('builtins.open', mock.mock_open(read_data='{]')):
self.assertFalse(self.blink.login())
@mock.patch('blinkpy.blinkpy.json.load')
def test_cred_file(self, mockjson, mock_sess):
"""Test that loading credential file works."""
# pylint: disable=protected-access
self.blink_no_cred._cred_file = '/tmp/fake.file'
mockjson.return_value = {'username': 'foo', 'password': 'bar'}
with mock.patch('os.path.isfile', return_value=True):
with mock.patch('builtins.open', mock.mock_open(read_data='')):
self.assertTrue(self.blink_no_cred.login())
# pylint: disable=protected-access
self.assertEqual(self.blink_no_cred._username, 'foo')
# pylint: disable=protected-access
self.assertEqual(self.blink_no_cred._password, 'bar')
def test_bad_request(self, mock_sess):
"""Check that we raise an Exception with a bad request."""
self.blink.session = create_session()
explog = ("ERROR:blinkpy.helpers.util:"
"Cannot obtain new token for server auth.")
explog = ("WARNING:blinkpy.helpers.util:"
"Response from server: 200 - foo")
with self.assertRaises(BlinkException):
http_req(self.blink, reqtype='bad')
+74 -3
View File
@@ -43,6 +43,7 @@ class TestBlinkSyncModule(unittest.TestCase):
None,
{'devicestatus': {}},
]
self.blink.sync['test'].network_info = {'network': {'armed': True}}
def tearDown(self):
"""Clean up after test."""
@@ -73,23 +74,93 @@ class TestBlinkSyncModule(unittest.TestCase):
'media': [{
'device_name': 'foo',
'media': '/foo/bar.mp4',
'created_at': '1970-01-01T00:00:00+0:00'
'created_at': '1990-01-01T00:00:00+00:00'
}]
}
sync_module = self.blink.sync['test']
sync_module.cameras = {'foo': None}
sync_module.blink.last_refresh = 0
self.assertEqual(sync_module.motion, {})
self.assertTrue(sync_module.check_new_videos())
self.assertEqual(sync_module.last_record['foo'],
{'clip': '/foo/bar.mp4',
'time': '1970-01-01T00:00:00+0:00'})
'time': '1990-01-01T00:00:00+00:00'})
self.assertEqual(sync_module.motion, {'foo': True})
mock_resp.return_value = {'media': []}
self.assertTrue(sync_module.check_new_videos())
self.assertEqual(sync_module.motion, {'foo': False})
self.assertEqual(sync_module.last_record['foo'],
{'clip': '/foo/bar.mp4',
'time': '1970-01-01T00:00:00+0:00'})
'time': '1990-01-01T00:00:00+00:00'})
def test_check_new_videos_old_date(self, mock_resp):
"""Test videos return response with old date."""
mock_resp.return_value = {
'media': [{
'device_name': 'foo',
'media': '/foo/bar.mp4',
'created_at': '1970-01-01T00:00:00+00:00'
}]
}
sync_module = self.blink.sync['test']
sync_module.cameras = {'foo': None}
sync_module.blink.last_refresh = 1000
self.assertTrue(sync_module.check_new_videos())
self.assertEqual(sync_module.motion, {'foo': False})
def test_check_no_motion_if_not_armed(self, mock_resp):
"""Test that motion detection is not set if module unarmed."""
mock_resp.return_value = {
'media': [{
'device_name': 'foo',
'media': '/foo/bar.mp4',
'created_at': '1990-01-01T00:00:00+00:00'
}]
}
sync_module = self.blink.sync['test']
sync_module.cameras = {'foo': None}
sync_module.blink.last_refresh = 1000
self.assertTrue(sync_module.check_new_videos())
self.assertEqual(sync_module.motion, {'foo': True})
sync_module.network_info = {'network': {'armed': False}}
self.assertTrue(sync_module.check_new_videos())
self.assertEqual(sync_module.motion, {'foo': False})
def test_check_multiple_videos(self, mock_resp):
"""Test motion found even with multiple videos."""
mock_resp.return_value = {
'media': [
{
'device_name': 'foo',
'media': '/foo/bar.mp4',
'created_at': '1970-01-01T00:00:00+00:00'
},
{
'device_name': 'foo',
'media': '/bar/foo.mp4',
'created_at': '1990-01-01T00:00:00+00:00'
},
{
'device_name': 'foo',
'media': '/foobar.mp4',
'created_at': '1970-01-01T00:00:01+00:00'
}
]
}
sync_module = self.blink.sync['test']
sync_module.cameras = {'foo': None}
sync_module.blink.last_refresh = 1000
self.assertTrue(sync_module.check_new_videos())
self.assertEqual(sync_module.motion, {'foo': True})
expected_result = {
'foo': {
'clip': '/bar/foo.mp4',
'time': '1990-01-01T00:00:00+00:00'
}
}
self.assertEqual(sync_module.last_record, expected_result)
def test_check_new_videos_failed(self, mock_resp):
"""Test method when response is unexpected."""
+8 -1
View File
@@ -3,7 +3,7 @@
import unittest
from unittest import mock
import time
from blinkpy.helpers.util import Throttle, BlinkURLHandler
from blinkpy.helpers.util import Throttle, BlinkURLHandler, time_to_seconds
class TestUtil(unittest.TestCase):
@@ -105,3 +105,10 @@ class TestUtil(unittest.TestCase):
self.assertEqual(urls.subdomain, 'rest-test')
urls = BlinkURLHandler('test', legacy=True)
self.assertEqual(urls.subdomain, 'rest.test')
def test_time_to_seconds(self):
"""Test time to seconds conversion."""
correct_time = '1970-01-01T00:00:05+00:00'
wrong_time = '1/1/1970 00:00:03'
self.assertEqual(time_to_seconds(correct_time), 5)
self.assertFalse(time_to_seconds(wrong_time))
+15 -5
View File
@@ -8,11 +8,21 @@ setenv =
LANG=en_US.UTF-8
PYTHONPATH = {toxinidir}
commands =
py.test --timeout=30 --duration=10 --cov=blinkpy --cov-report term-missing {posargs}
pytest --timeout=9 --durations=10 --cov=blinkpy --cov-report term-missing {posargs}
deps =
-r{toxinidir}/requirements.txt
-r{toxinidir}/requirements_test.txt
[testenv:cov]
setenv =
LANG=en_US.UTF-8
PYTHONPATH = {toxinidir}
commands =
pytest --timeout=9 --durations=10 --cov=blinkpy --cov-report=xml {posargs}
deps =
-r{toxinidir}/requirements.txt
-r{toxinidir}/requirements_test.txt
[testenv:lint]
deps =
-r{toxinidir}/requirements.txt
@@ -20,9 +30,9 @@ deps =
basepython = python3
ignore_errors = True
commands =
pylint --rcfile={toxinidir}/pylintrc blinkpy tests
flake8 blinkpy tests
pydocstyle blinkpy tests
pylint --rcfile={toxinidir}/pylintrc blinkpy tests app
flake8 blinkpy tests app
pydocstyle blinkpy tests app
rst-lint README.rst
rst-lint CHANGES.rst