Compare commits
41
Commits
v0.16.2
...
0.16.4-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba3c2e3489 | ||
|
|
92a2cc2d9a | ||
|
|
0842739197 | ||
|
|
f09a66c083 | ||
|
|
88984365fe | ||
|
|
469b0c257e | ||
|
|
a94f59eb53 | ||
|
|
eb1fbe90d1 | ||
|
|
b5b7498041 | ||
|
|
828b41197b | ||
|
|
b0fed670b9 | ||
|
|
f13d177615 | ||
|
|
fcce28c9c4 | ||
|
|
e8e9d4e3cd | ||
|
|
59ef398452 | ||
|
|
80e39845ce | ||
|
|
cbab673084 | ||
|
|
f27df12e64 | ||
|
|
bdcb3f6203 | ||
|
|
f619b20e2a | ||
|
|
69aaee3fc4 | ||
|
|
7140895c58 | ||
|
|
190cef875e | ||
|
|
7aef8570a5 | ||
|
|
721a3ad8f2 | ||
|
|
1b1c30bff0 | ||
|
|
8c23087f89 | ||
|
|
dae2413ed1 | ||
|
|
cd9792193f | ||
|
|
75dbd1e498 | ||
|
|
a374009835 | ||
|
|
9a9501635f | ||
|
|
e00600c955 | ||
|
|
141fea542c | ||
|
|
ed4450778d | ||
|
|
41d59b1df0 | ||
|
|
fd104c09da | ||
|
|
6dbed91426 | ||
|
|
cb43f17d75 | ||
|
|
5ebf278ad3 | ||
|
|
7f782533b8 |
@@ -33,8 +33,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
tox -r -e cov
|
tox -r -e cov
|
||||||
- name: Codecov
|
- name: Codecov
|
||||||
uses: codecov/codecov-action@v1.0.6
|
uses: codecov/codecov-action@v1
|
||||||
with:
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
flags: unittests
|
flags: unittests
|
||||||
file: ./coverage.xml
|
file: ./coverage.xml
|
||||||
name: blinkpy
|
name: blinkpy
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ repos:
|
|||||||
- --quiet
|
- --quiet
|
||||||
files: ^((blinkpy|tests)/.+)?[^/]+\.py$
|
files: ^((blinkpy|tests)/.+)?[^/]+\.py$
|
||||||
- repo: https://gitlab.com/pycqa/flake8
|
- repo: https://gitlab.com/pycqa/flake8
|
||||||
rev: 3.8.2
|
rev: 3.8.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- flake8-docstrings==1.5.0
|
- flake8-docstrings==1.5.0
|
||||||
- pydocstyle==5.0.2
|
- pydocstyle==5.1.1
|
||||||
files: ^(blinkpy|tests)/.+\.py$
|
files: ^(blinkpy|tests)/.+\.py$
|
||||||
- repo: https://github.com/Lucas-C/pre-commit-hooks-markup
|
- repo: https://github.com/Lucas-C/pre-commit-hooks-markup
|
||||||
rev: v1.0.0
|
rev: v1.0.0
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ Changelog
|
|||||||
|
|
||||||
A list of changes between each release
|
A list of changes between each release
|
||||||
|
|
||||||
|
0.16.3 (2020-08-02)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- Add user-agent to all headers
|
||||||
|
|
||||||
0.16.2 (2020-08-01)
|
0.16.2 (2020-08-01)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -174,7 +174,7 @@ Similar methods exist for individual cameras:
|
|||||||
|
|
||||||
Download videos
|
Download videos
|
||||||
----------------
|
----------------
|
||||||
You can also use this library to download all videos from the server. In order to do this, you must specify a ``path``. You may also specifiy a how far back in time to go to retrieve videos via the ``since=`` variable (a simple string such as ``"2017/09/21"`` is sufficient), as well as how many pages to traverse via the ``page=`` variable. Note that by default, the library will search the first ten pages which is sufficient in most use cases. Additionally, you can specidy one or more cameras via the ``camera=`` property. This can be a single string indicating the name of the camera, or a list of camera names. By default, it is set to the string ``'all'`` to grab videos from all cameras.
|
You can also use this library to download all videos from the server. In order to do this, you must specify a ``path``. You may also specifiy a how far back in time to go to retrieve videos via the ``since=`` variable (a simple string such as ``"2017/09/21"`` is sufficient), as well as how many pages to traverse via the ``stop=`` variable. Note that by default, the library will search the first ten pages which is sufficient in most use cases. Additionally, you can specify one or more cameras via the ``camera=`` property. This can be a single string indicating the name of the camera, or a list of camera names. By default, it is set to the string ``'all'`` to grab videos from all cameras.
|
||||||
|
|
||||||
Example usage, which downloads all videos recorded since July 4th, 2018 at 9:34am to the ``/home/blink`` directory:
|
Example usage, which downloads all videos recorded since July 4th, 2018 at 9:34am to the ``/home/blink`` directory:
|
||||||
|
|
||||||
|
|||||||
+1
-3
@@ -244,9 +244,7 @@ def request_camera_liveview(blink, network, camera_id):
|
|||||||
:param network: Sync module network id.
|
:param network: Sync module network id.
|
||||||
:param camera_id: Camera ID of camera to request liveview from.
|
:param camera_id: Camera ID of camera to request liveview from.
|
||||||
"""
|
"""
|
||||||
url = (
|
url = f"{blink.urls.base_url}/api/v5/accounts/{blink.account_id}/networks/{network}/cameras/{camera_id}/liveview"
|
||||||
f"{blink.urls.base_url}/api/v3/networks/{network}/cameras/{camera_id}/liveview"
|
|
||||||
)
|
|
||||||
return http_post(blink, url)
|
return http_post(blink, url)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -6,7 +6,12 @@ from requests.adapters import HTTPAdapter
|
|||||||
from urllib3.util.retry import Retry
|
from urllib3.util.retry import Retry
|
||||||
from blinkpy import api
|
from blinkpy import api
|
||||||
from blinkpy.helpers import util
|
from blinkpy.helpers import util
|
||||||
from blinkpy.helpers.constants import BLINK_URL, LOGIN_ENDPOINT, TIMEOUT
|
from blinkpy.helpers.constants import (
|
||||||
|
BLINK_URL,
|
||||||
|
DEFAULT_USER_AGENT,
|
||||||
|
LOGIN_ENDPOINT,
|
||||||
|
TIMEOUT,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -53,7 +58,7 @@ class Auth:
|
|||||||
"""Return authorization header."""
|
"""Return authorization header."""
|
||||||
if self.token is None:
|
if self.token is None:
|
||||||
return None
|
return None
|
||||||
return {"TOKEN_AUTH": self.token}
|
return {"TOKEN_AUTH": self.token, "user-agent": DEFAULT_USER_AGENT}
|
||||||
|
|
||||||
def create_session(self, opts=None):
|
def create_session(self, opts=None):
|
||||||
"""Create a session for blink communication."""
|
"""Create a session for blink communication."""
|
||||||
|
|||||||
+7
-5
@@ -76,20 +76,22 @@ class Blink:
|
|||||||
self.no_owls = no_owls
|
self.no_owls = no_owls
|
||||||
|
|
||||||
@util.Throttle(seconds=MIN_THROTTLE_TIME)
|
@util.Throttle(seconds=MIN_THROTTLE_TIME)
|
||||||
def refresh(self, force=False):
|
def refresh(self, force=False, force_cache=False):
|
||||||
"""
|
"""
|
||||||
Perform a system refresh.
|
Perform a system refresh.
|
||||||
|
|
||||||
:param force: Force an update of the camera data
|
:param force: Used to override throttle, resets refresh
|
||||||
|
:param force_cache: Used to force update without overriding throttle
|
||||||
"""
|
"""
|
||||||
if self.check_if_ok_to_update() or force:
|
if self.check_if_ok_to_update() or force or force_cache:
|
||||||
if not self.available:
|
if not self.available:
|
||||||
self.setup_post_verify()
|
self.setup_post_verify()
|
||||||
|
|
||||||
|
self.get_homescreen()
|
||||||
for sync_name, sync_module in self.sync.items():
|
for sync_name, sync_module in self.sync.items():
|
||||||
_LOGGER.debug("Attempting refresh of sync %s", sync_name)
|
_LOGGER.debug("Attempting refresh of sync %s", sync_name)
|
||||||
sync_module.refresh(force_cache=force)
|
sync_module.refresh(force_cache=(force or force_cache))
|
||||||
if not force:
|
if not force_cache:
|
||||||
# Prevents rapid clearing of motion detect property
|
# Prevents rapid clearing of motion detect property
|
||||||
self.last_refresh = int(time.time())
|
self.last_refresh = int(time.time())
|
||||||
return True
|
return True
|
||||||
|
|||||||
+15
-17
@@ -98,6 +98,15 @@ class BlinkCamera:
|
|||||||
self.sync.blink, self.network_id, self.camera_id
|
self.sync.blink, self.network_id, self.camera_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_media(self, media_type="image"):
|
||||||
|
"""Download media (image or video)."""
|
||||||
|
url = self.thumbnail
|
||||||
|
if media_type.lower() == "video":
|
||||||
|
url = self.clip
|
||||||
|
return api.http_get(
|
||||||
|
self.sync.blink, url=url, stream=True, json=False, timeout=TIMEOUT_MEDIA,
|
||||||
|
)
|
||||||
|
|
||||||
def snap_picture(self):
|
def snap_picture(self):
|
||||||
"""Take a picture with camera to create a new thumbnail."""
|
"""Take a picture with camera to create a new thumbnail."""
|
||||||
return api.request_new_image(self.sync.blink, self.network_id, self.camera_id)
|
return api.request_new_image(self.sync.blink, self.network_id, self.camera_id)
|
||||||
@@ -180,21 +189,10 @@ class BlinkCamera:
|
|||||||
update_cached_video = True
|
update_cached_video = True
|
||||||
|
|
||||||
if new_thumbnail is not None and (update_cached_image or force_cache):
|
if new_thumbnail is not None and (update_cached_image or force_cache):
|
||||||
self._cached_image = api.http_get(
|
self._cached_image = self.get_media()
|
||||||
self.sync.blink,
|
|
||||||
url=self.thumbnail,
|
|
||||||
stream=True,
|
|
||||||
json=False,
|
|
||||||
timeout=TIMEOUT_MEDIA,
|
|
||||||
)
|
|
||||||
if clip_addr is not None and (update_cached_video or force_cache):
|
if clip_addr is not None and (update_cached_video or force_cache):
|
||||||
self._cached_video = api.http_get(
|
self._cached_video = self.get_media(media_type="video")
|
||||||
self.sync.blink,
|
|
||||||
url=self.clip,
|
|
||||||
stream=True,
|
|
||||||
json=False,
|
|
||||||
timeout=TIMEOUT_MEDIA,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_liveview(self):
|
def get_liveview(self):
|
||||||
"""Get livewview rtsps link."""
|
"""Get livewview rtsps link."""
|
||||||
@@ -210,7 +208,7 @@ class BlinkCamera:
|
|||||||
:param path: Path to write file
|
:param path: Path to write file
|
||||||
"""
|
"""
|
||||||
_LOGGER.debug("Writing image from %s to %s", self.name, path)
|
_LOGGER.debug("Writing image from %s to %s", self.name, path)
|
||||||
response = self._cached_image
|
response = self.get_media()
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
with open(path, "wb") as imgfile:
|
with open(path, "wb") as imgfile:
|
||||||
copyfileobj(response.raw, imgfile)
|
copyfileobj(response.raw, imgfile)
|
||||||
@@ -226,7 +224,7 @@ class BlinkCamera:
|
|||||||
:param path: Path to write file
|
:param path: Path to write file
|
||||||
"""
|
"""
|
||||||
_LOGGER.debug("Writing video from %s to %s", self.name, path)
|
_LOGGER.debug("Writing video from %s to %s", self.name, path)
|
||||||
response = self._cached_video
|
response = self.get_media(media_type="video")
|
||||||
if response is None:
|
if response is None:
|
||||||
_LOGGER.error("No saved video exist for %s.", self.name)
|
_LOGGER.error("No saved video exist for %s.", self.name)
|
||||||
return
|
return
|
||||||
@@ -268,6 +266,6 @@ class BlinkCameraMini(BlinkCamera):
|
|||||||
response = api.http_post(self.sync.blink, url)
|
response = api.http_post(self.sync.blink, url)
|
||||||
server = response["server"]
|
server = response["server"]
|
||||||
server_split = server.split(":")
|
server_split = server.split(":")
|
||||||
server_split[0] = "rtsps"
|
server_split[0] = "rtsps:"
|
||||||
link = "".join(server_split)
|
link = "".join(server_split)
|
||||||
return link
|
return link
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
MAJOR_VERSION = 0
|
MAJOR_VERSION = 0
|
||||||
MINOR_VERSION = 16
|
MINOR_VERSION = 17
|
||||||
PATCH_VERSION = 2
|
PATCH_VERSION = "0.dev1"
|
||||||
|
|
||||||
__version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}.{PATCH_VERSION}"
|
__version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}.{PATCH_VERSION}"
|
||||||
|
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class BlinkSyncModule:
|
|||||||
for owl in self.blink.homescreen["owls"]:
|
for owl in self.blink.homescreen["owls"]:
|
||||||
if owl["name"] == name:
|
if owl["name"] == name:
|
||||||
return owl
|
return owl
|
||||||
except KeyError:
|
except (TypeError, KeyError):
|
||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -270,7 +270,7 @@ class BlinkOwl(BlinkSyncModule):
|
|||||||
if owl["name"] == self.name:
|
if owl["name"] == self.name:
|
||||||
self.status = owl["enabled"]
|
self.status = owl["enabled"]
|
||||||
return owl
|
return owl
|
||||||
except KeyError:
|
except (TypeError, KeyError):
|
||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
python-dateutil>=2.8.1
|
python-dateutil>=2.8.1
|
||||||
requests>=2.24.0
|
requests>=2.24.0
|
||||||
python-slugify>=4.0.1
|
python-slugify>=4.0.1
|
||||||
testtools>=2.4.0
|
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
black==19.10b0
|
black==19.10b0
|
||||||
coverage==5.2.1
|
coverage==5.3
|
||||||
flake8==3.8.3
|
flake8==3.8.4
|
||||||
flake8-docstrings==1.5.0
|
flake8-docstrings==1.5.0
|
||||||
pre-commit==2.6.0
|
pre-commit==2.7.1
|
||||||
pylint==2.5.3
|
pylint==2.6.0
|
||||||
pydocstyle==5.0.2
|
pydocstyle==5.1.1
|
||||||
pytest==6.0.1
|
pytest==6.1.1
|
||||||
pytest-cov==2.10.0
|
pytest-cov==2.10.1
|
||||||
pytest-sugar==0.9.4
|
pytest-sugar==0.9.4
|
||||||
pytest-timeout==1.4.2
|
pytest-timeout==1.4.2
|
||||||
restructuredtext-lint==1.3.1
|
restructuredtext-lint==1.3.1
|
||||||
pygments==2.6.1
|
pygments==2.7.1
|
||||||
|
testtools>=2.4.0
|
||||||
|
|||||||
+1
-1
@@ -128,7 +128,7 @@ class TestAuth(unittest.TestCase):
|
|||||||
def test_header(self):
|
def test_header(self):
|
||||||
"""Test header data."""
|
"""Test header data."""
|
||||||
self.auth.token = "bar"
|
self.auth.token = "bar"
|
||||||
expected_header = {"TOKEN_AUTH": "bar"}
|
expected_header = {"TOKEN_AUTH": "bar", "user-agent": const.DEFAULT_USER_AGENT}
|
||||||
self.assertDictEqual(self.auth.header, expected_header)
|
self.assertDictEqual(self.auth.header, expected_header)
|
||||||
|
|
||||||
def test_header_no_token(self):
|
def test_header_no_token(self):
|
||||||
|
|||||||
@@ -5,29 +5,23 @@ import logging
|
|||||||
|
|
||||||
from blinkpy import blinkpy
|
from blinkpy import blinkpy
|
||||||
from blinkpy.sync_module import BlinkSyncModule
|
from blinkpy.sync_module import BlinkSyncModule
|
||||||
|
from blinkpy.camera import BlinkCamera
|
||||||
from blinkpy.helpers.util import get_time, BlinkURLHandler
|
from blinkpy.helpers.util import get_time, BlinkURLHandler
|
||||||
|
|
||||||
|
|
||||||
class MockSyncModule(BlinkSyncModule):
|
class MockSyncModule(BlinkSyncModule):
|
||||||
"""Mock http requests from sync module."""
|
"""Mock blink sync module object."""
|
||||||
|
|
||||||
def __init__(self, blink, header):
|
def get_network_info(self):
|
||||||
"""Create mock sync module instance."""
|
"""Mock network info method."""
|
||||||
super().__init__(blink, header, network_id=None, camera_list=None)
|
return True
|
||||||
self.blink = blink
|
|
||||||
self.header = header
|
|
||||||
self.return_value = None
|
|
||||||
self.return_value2 = None
|
|
||||||
|
|
||||||
def http_get(self, url, stream=False, json=True):
|
|
||||||
"""Mock get request."""
|
|
||||||
if stream and self.return_value2 is not None:
|
|
||||||
return self.return_value2
|
|
||||||
return self.return_value
|
|
||||||
|
|
||||||
def http_post(self, url):
|
class MockCamera(BlinkCamera):
|
||||||
"""Mock post request."""
|
"""Mock blink camera object."""
|
||||||
return self.return_value
|
|
||||||
|
def update(self, config, force_cache=False, **kwargs):
|
||||||
|
"""Mock camera update method."""
|
||||||
|
|
||||||
|
|
||||||
class TestBlinkFunctions(unittest.TestCase):
|
class TestBlinkFunctions(unittest.TestCase):
|
||||||
@@ -121,3 +115,16 @@ class TestBlinkFunctions(unittest.TestCase):
|
|||||||
with self.assertLogs() as dl_log:
|
with self.assertLogs() as dl_log:
|
||||||
blink.download_videos("/tmp", camera="bar", stop=2)
|
blink.download_videos("/tmp", camera="bar", stop=2)
|
||||||
self.assertEqual(dl_log.output, expected_log)
|
self.assertEqual(dl_log.output, expected_log)
|
||||||
|
|
||||||
|
@mock.patch("blinkpy.blinkpy.api.request_network_update")
|
||||||
|
@mock.patch("blinkpy.auth.Auth.query")
|
||||||
|
def test_refresh(self, mock_req, mock_update):
|
||||||
|
"""Test ability to refresh system."""
|
||||||
|
mock_update.return_value = {"network": {"sync_module_error": False}}
|
||||||
|
mock_req.return_value = None
|
||||||
|
self.blink.last_refresh = 0
|
||||||
|
self.blink.available = True
|
||||||
|
self.blink.sync["foo"] = MockSyncModule(self.blink, "foo", 1, [])
|
||||||
|
self.blink.cameras = {"bar": MockCamera(self.blink.sync)}
|
||||||
|
self.blink.sync["foo"].cameras = self.blink.cameras
|
||||||
|
self.assertTrue(self.blink.refresh())
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ class TestBlinkSetup(unittest.TestCase):
|
|||||||
self.assertEqual(self.blink.last_refresh, None)
|
self.assertEqual(self.blink.last_refresh, None)
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"blinkpy.sync_module.BlinkSyncModule.refresh", return_value=True
|
"blinkpy.sync_module.BlinkSyncModule.refresh", return_value=True
|
||||||
):
|
), mock.patch("blinkpy.blinkpy.Blink.get_homescreen", return_value=True):
|
||||||
self.blink.refresh()
|
self.blink.refresh(force=True)
|
||||||
|
|
||||||
self.assertEqual(self.blink.last_refresh, now)
|
self.assertEqual(self.blink.last_refresh, now)
|
||||||
self.assertEqual(self.blink.check_if_ok_to_update(), False)
|
self.assertEqual(self.blink.check_if_ok_to_update(), False)
|
||||||
|
|||||||
@@ -176,3 +176,10 @@ class TestBlinkCameraSetup(unittest.TestCase):
|
|||||||
attr = camera.attributes
|
attr = camera.attributes
|
||||||
for key in attr:
|
for key in attr:
|
||||||
self.assertEqual(attr[key], None)
|
self.assertEqual(attr[key], None)
|
||||||
|
|
||||||
|
def test_camera_stream(self, mock_resp):
|
||||||
|
"""Test that camera stream returns correct url."""
|
||||||
|
mock_resp.return_value = {"server": "rtsps://foo.bar"}
|
||||||
|
mini_camera = BlinkCameraMini(self.blink.sync["test"])
|
||||||
|
self.assertEqual(self.camera.get_liveview(), "rtsps://foo.bar")
|
||||||
|
self.assertEqual(mini_camera.get_liveview(), "rtsps://foo.bar")
|
||||||
|
|||||||
Reference in New Issue
Block a user