Compare commits
73
Commits
v0.14.1.dev0
...
v0.14.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
deba0cbba6 | ||
|
|
0c3359abcf | ||
|
|
a76069174d | ||
|
|
19c9178db7 | ||
|
|
2ebf2db04d | ||
|
|
26de89c1b9 | ||
|
|
8f68aad238 | ||
|
|
7b586e2947 | ||
|
|
800ddeaf7a | ||
|
|
4ed2e3dab5 | ||
|
|
2b9c82b432 | ||
|
|
3e7c9f3d5c | ||
|
|
8d0b51ed9c | ||
|
|
c178fa5457 | ||
|
|
532ad92714 | ||
|
|
deb0a682d8 | ||
|
|
12e22dee8e | ||
|
|
ae0f954632 | ||
|
|
b92941538b | ||
|
|
ce13444427 | ||
|
|
9c461c4cfa | ||
|
|
23858c2912 | ||
|
|
2284a56c82 | ||
|
|
323143cb2b | ||
|
|
d6064a9aac | ||
|
|
a982aa595f | ||
|
|
54c52a7f1e | ||
|
|
bdb8cedf64 | ||
|
|
554b18adfe | ||
|
|
5619af2a00 | ||
|
|
957003aeb1 | ||
|
|
6fc64a486e | ||
|
|
534e3fb34e | ||
|
|
9ab346ed91 | ||
|
|
1bf5dda803 | ||
|
|
6cd8fd8ab2 | ||
|
|
a689839616 | ||
|
|
783aa5e547 | ||
|
|
a6ff2fa0df | ||
|
|
eb498bfb22 | ||
|
|
2ebbe46ada | ||
|
|
2f4e8fcade | ||
|
|
efe05525e4 | ||
|
|
4122ae8f64 | ||
|
|
5d296cc836 | ||
|
|
fa50e315a9 | ||
|
|
4a4811bee2 | ||
|
|
ad7ece4d45 | ||
|
|
17d1aa9690 | ||
|
|
b00839ecb3 | ||
|
|
4900ea8ba6 | ||
|
|
4fb16398e4 | ||
|
|
a1fc0c5a2b | ||
|
|
6adb252cdd | ||
|
|
3637bbad8d | ||
|
|
694efe71f8 | ||
|
|
315735bc2f | ||
|
|
db6403b468 | ||
|
|
92dda40e84 | ||
|
|
6c8fc0abce | ||
|
|
2d74b12d43 | ||
|
|
f4480da276 | ||
|
|
afdd73118d | ||
|
|
0262f14a53 | ||
|
|
ff8f091a74 | ||
|
|
77e200f6a5 | ||
|
|
f3a8036649 | ||
|
|
853ccbce79 | ||
|
|
1a24e1896f | ||
|
|
ae946dbb53 | ||
|
|
80c6444c2e | ||
|
|
e872fc9ce5 | ||
|
|
4e6957a772 |
+3
-3
@@ -1,5 +1,5 @@
|
||||
[run]
|
||||
omit =
|
||||
helpers/*
|
||||
parallel = true
|
||||
omit =
|
||||
tests/*
|
||||
setup.py
|
||||
setup.py
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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/*
|
||||
@@ -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
|
||||
@@ -4,6 +4,7 @@
|
||||
__pycache__/*
|
||||
htmlcov/*
|
||||
.coverage
|
||||
coverage.xml
|
||||
*.pyc
|
||||
*.egg*/*
|
||||
dist/*
|
||||
|
||||
@@ -4,4 +4,8 @@ python:
|
||||
ignored_directories:
|
||||
- docs
|
||||
|
||||
flake8:
|
||||
enabled: true
|
||||
config_file: .flake8
|
||||
|
||||
fail_on_violations: true
|
||||
|
||||
-23
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
"""Python init file for app.py."""
|
||||
+31
@@ -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)
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
docker build -t fronzbot/blinkpy:latest ./
|
||||
+31
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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')
|
||||
|
||||
|
||||
@@ -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
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user