Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a97a928de | ||
|
|
0322414de1 | ||
|
|
1c75e86863 | ||
|
|
0ae71f6367 | ||
|
|
af7c38695a | ||
|
|
c741c22876 | ||
|
|
06b9b12eeb | ||
|
|
daf7078f98 | ||
|
|
c2db57ecb3 | ||
|
|
0336c6a8f8 | ||
|
|
22d3fa9565 | ||
|
|
9d81973b3a | ||
|
|
fe8eac3dc6 | ||
|
|
5fc315201a | ||
|
|
a9d3d5568b | ||
|
|
f88dbdaeba | ||
|
|
48028914de | ||
|
|
fcdb70d246 | ||
|
|
e8589bfa05 | ||
|
|
854ffbf26a | ||
|
|
ca109f703c | ||
|
|
82d5a7ea51 | ||
|
|
79ead7b826 | ||
|
|
6e360b2bb1 | ||
|
|
1aaa5b649f | ||
|
|
435f3b9831 | ||
|
|
bcf0bb5525 | ||
|
|
322aae37dc | ||
|
|
45cb4028b8 | ||
|
|
151d9b7375 | ||
|
|
d393c861d7 | ||
|
|
02c4b2e7e1 |
+37
@@ -4,6 +4,43 @@ Changelog
|
|||||||
|
|
||||||
A list of changes between each release
|
A list of changes between each release
|
||||||
|
|
||||||
|
0.16.1 (2020-07-29)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- Unpin requeirements, set minimum version instead
|
||||||
|
- Bump coverage to 5.2.1
|
||||||
|
- Bump pytest to 6.0.0
|
||||||
|
|
||||||
|
|
||||||
|
0.16.0 (2020-07-20)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
**Breaking Changes:**
|
||||||
|
|
||||||
|
- Add arm property to camera, deprecate motion enable method (`#273 <https://github.com/fronzbot/blinkpy/pull/273>`__)
|
||||||
|
- Complete refactoring of auth logic (breaks all pre-0.16.0 setups!) (`#261 <https://github.com/fronzbot/blinkpy/pull/261>`__)
|
||||||
|
|
||||||
|
**New Features:**
|
||||||
|
|
||||||
|
- Add is_errored property to Auth class (`#275 <https://github.com/fronzbot/blinkpy/pull/275>`__)
|
||||||
|
- Add new endpoint to get user infor (`#280 <https://github.com/fronzbot/blinkpy/pull/280>`__)
|
||||||
|
- Add get_liveview command to camera module (`#289 <https://github.com/fronzbot/blinkpy/pull/289>`__)
|
||||||
|
- Add blink Mini Camera support (`#290 <https://github.com/fronzbot/blinkpy/pull/290>`__)
|
||||||
|
- Add option to skip homescreen check (`#305 <https://github.com/fronzbot/blinkpy/pull/305>`__)
|
||||||
|
- Add different timeout for video and image retrieval (`#323 <https://github.com/fronzbot/blinkpy/pull/323>`__)
|
||||||
|
- Modifiy session to use HTTPAdapter and handle retries (`#324 <https://github.com/fronzbot/blinkpy/pull/324>`__)
|
||||||
|
- Add retry option overrides (`#339 <https://github.com/fronzbot/blinkpy/pull/339>`__)
|
||||||
|
|
||||||
|
**All changes:**
|
||||||
|
|
||||||
|
Please see the change list in the (`Release Notes <https://github.com/fronzbot/releases/tag/v0.16.0>`__)
|
||||||
|
|
||||||
|
|
||||||
|
0.15.1 (2020-07-11)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
- Bugfix: remove "Host" from auth header (`#330 <https://github.com/fronzbot/blinkpy/pull/330>`__)
|
||||||
|
|
||||||
|
|
||||||
0.15.0 (2020-05-08)
|
0.15.0 (2020-05-08)
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
**Breaking Changes:**
|
**Breaking Changes:**
|
||||||
|
|||||||
+17
-5
@@ -53,17 +53,22 @@ class Auth:
|
|||||||
"""Return authorization header."""
|
"""Return authorization header."""
|
||||||
if self.token is None:
|
if self.token is None:
|
||||||
return None
|
return None
|
||||||
return {"Host": self.host, "TOKEN_AUTH": self.token}
|
return {"TOKEN_AUTH": self.token}
|
||||||
|
|
||||||
def create_session(self):
|
def create_session(self, opts=None):
|
||||||
"""Create a session for blink communication."""
|
"""Create a session for blink communication."""
|
||||||
|
if opts is None:
|
||||||
|
opts = {}
|
||||||
|
backoff = opts.get("backoff", 1)
|
||||||
|
retries = opts.get("retries", 3)
|
||||||
|
retry_list = opts.get("retry_list", [429, 500, 502, 503, 504])
|
||||||
sess = Session()
|
sess = Session()
|
||||||
assert_status_hook = [
|
assert_status_hook = [
|
||||||
lambda response, *args, **kwargs: response.raise_for_status()
|
lambda response, *args, **kwargs: response.raise_for_status()
|
||||||
]
|
]
|
||||||
sess.hooks["response"] = assert_status_hook
|
sess.hooks["response"] = assert_status_hook
|
||||||
retry = Retry(
|
retry = Retry(
|
||||||
total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504]
|
total=retries, backoff_factor=backoff, status_forcelist=retry_list
|
||||||
)
|
)
|
||||||
adapter = HTTPAdapter(max_retries=retry)
|
adapter = HTTPAdapter(max_retries=retry)
|
||||||
sess.mount("https://", adapter)
|
sess.mount("https://", adapter)
|
||||||
@@ -174,11 +179,18 @@ class Auth:
|
|||||||
"Connection error. Endpoint %s possibly down or throttled.", url,
|
"Connection error. Endpoint %s possibly down or throttled.", url,
|
||||||
)
|
)
|
||||||
except BlinkBadResponse:
|
except BlinkBadResponse:
|
||||||
|
code = None
|
||||||
|
reason = None
|
||||||
|
try:
|
||||||
|
code = response.status_code
|
||||||
|
reason = response.reason
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Expected json response from %s, but received: %s: %s",
|
"Expected json response from %s, but received: %s: %s",
|
||||||
url,
|
url,
|
||||||
response.status_code,
|
code,
|
||||||
response.reason,
|
reason,
|
||||||
)
|
)
|
||||||
except UnauthorizedError:
|
except UnauthorizedError:
|
||||||
try:
|
try:
|
||||||
|
|||||||
+1
-3
@@ -151,9 +151,7 @@ class BlinkCamera:
|
|||||||
if config.get("thumbnail", False):
|
if config.get("thumbnail", False):
|
||||||
thumb_addr = config["thumbnail"]
|
thumb_addr = config["thumbnail"]
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning("Could not find thumbnail for camera %s", self.name)
|
||||||
"Could not find thumbnail for camera %s", self.name, exc_info=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if thumb_addr is not None:
|
if thumb_addr is not None:
|
||||||
new_thumbnail = f"{self.sync.urls.base_url}{thumb_addr}.jpg"
|
new_thumbnail = f"{self.sync.urls.base_url}{thumb_addr}.jpg"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import os
|
|||||||
|
|
||||||
MAJOR_VERSION = 0
|
MAJOR_VERSION = 0
|
||||||
MINOR_VERSION = 16
|
MINOR_VERSION = 16
|
||||||
PATCH_VERSION = "0-rc11"
|
PATCH_VERSION = 1
|
||||||
|
|
||||||
__version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}.{PATCH_VERSION}"
|
__version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}.{PATCH_VERSION}"
|
||||||
|
|
||||||
|
|||||||
@@ -93,9 +93,7 @@ class BlinkSyncModule:
|
|||||||
self.serial = self.summary["serial"]
|
self.serial = self.summary["serial"]
|
||||||
self.status = self.summary["status"]
|
self.status = self.summary["status"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
_LOGGER.error(
|
_LOGGER.error("Could not extract some sync module info: %s", response)
|
||||||
"Could not extract some sync module info: %s", response, exc_info=True
|
|
||||||
)
|
|
||||||
|
|
||||||
is_ok = self.get_network_info()
|
is_ok = self.get_network_info()
|
||||||
self.check_new_videos()
|
self.check_new_videos()
|
||||||
@@ -158,7 +156,7 @@ class BlinkSyncModule:
|
|||||||
try:
|
try:
|
||||||
return response["event"]
|
return response["event"]
|
||||||
except (TypeError, KeyError):
|
except (TypeError, KeyError):
|
||||||
_LOGGER.error("Could not extract events: %s", response, exc_info=True)
|
_LOGGER.error("Could not extract events: %s", response)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_camera_info(self, camera_id, **kwargs):
|
def get_camera_info(self, camera_id, **kwargs):
|
||||||
@@ -170,7 +168,7 @@ class BlinkSyncModule:
|
|||||||
try:
|
try:
|
||||||
return response["camera"][0]
|
return response["camera"][0]
|
||||||
except (TypeError, KeyError):
|
except (TypeError, KeyError):
|
||||||
_LOGGER.error("Could not extract camera info: %s", response, exc_info=True)
|
_LOGGER.error("Could not extract camera info: %s", response)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_network_info(self):
|
def get_network_info(self):
|
||||||
|
|||||||
@@ -30,6 +30,29 @@ By default, the ``blink.auth.Auth`` class creates its own websession via its ``c
|
|||||||
blink.auth = Auth()
|
blink.auth = Auth()
|
||||||
blink.auth.session = YourCustomSession
|
blink.auth.session = YourCustomSession
|
||||||
|
|
||||||
|
|
||||||
|
Custom Retry Logic
|
||||||
|
--------------------
|
||||||
|
The built-in auth session via the ``create_session`` method allows for customizable retry intervals and conditions. These parameters are:
|
||||||
|
|
||||||
|
- retries
|
||||||
|
- backoff
|
||||||
|
- retry_list
|
||||||
|
|
||||||
|
``retries`` is the total number of retry attempts that each http request can do before timing out. ``backoff`` is a parameter that allows for non-linear retry times such that the time between retries is backoff*(2^(retries) - 1). ``retry_list`` is simply a list of status codes to force a retry. By default ``retries=3``, ``backoff=1``, and ``retry_list=[429, 500, 502, 503, 504]``. To override them, you need to add you overrides to a dictionary and use that to create a new session with the ``opts`` variable in the ``create_session`` method. The following example can serve as a guide where only the number of retries and backoff factor are overridden:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from blinkpy.blinkpy import Blink
|
||||||
|
from blinkpy.auth import Auth
|
||||||
|
|
||||||
|
blink = Blink()
|
||||||
|
blink.auth = Auth()
|
||||||
|
|
||||||
|
opts = {"retries": 10, "backoff": 2}
|
||||||
|
blink.auth.session = blink.auth.create_session(opts=opts)
|
||||||
|
|
||||||
|
|
||||||
Custom HTTP requests
|
Custom HTTP requests
|
||||||
---------------------
|
---------------------
|
||||||
In addition to custom sessions, custom blink server requests can be performed. This give you the ability to bypass the built-in ``Auth.query`` method. It also allows flexibility by giving you the option to pass your own url, rather than be limited to what is currently implemented in the ``blinkpy.api`` module.
|
In addition to custom sessions, custom blink server requests can be performed. This give you the ability to bypass the built-in ``Auth.query`` method. It also allows flexibility by giving you the option to pass your own url, rather than be limited to what is currently implemented in the ``blinkpy.api`` module.
|
||||||
|
|||||||
+4
-4
@@ -1,4 +1,4 @@
|
|||||||
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
|
testtools>=2.4.0
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
black==19.10b0
|
black==19.10b0
|
||||||
coverage==5.2
|
coverage==5.2.1
|
||||||
flake8==3.8.3
|
flake8==3.8.3
|
||||||
flake8-docstrings==1.5.0
|
flake8-docstrings==1.5.0
|
||||||
pre-commit==2.6.0
|
pre-commit==2.6.0
|
||||||
pylint==2.5.3
|
pylint==2.5.3
|
||||||
pydocstyle==5.0.2
|
pydocstyle==5.0.2
|
||||||
pytest==5.4.3
|
pytest==6.0.0
|
||||||
pytest-cov==2.10.0
|
pytest-cov==2.10.0
|
||||||
pytest-sugar==0.9.4
|
pytest-sugar==0.9.4
|
||||||
pytest-timeout==1.4.1
|
pytest-timeout==1.4.2
|
||||||
restructuredtext-lint==1.3.1
|
restructuredtext-lint==1.3.1
|
||||||
pygments==2.6.1
|
pygments==2.6.1
|
||||||
|
|||||||
+45
-3
@@ -128,8 +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"
|
||||||
self.auth.host = "foo"
|
expected_header = {"TOKEN_AUTH": "bar"}
|
||||||
expected_header = {"Host": "foo", "TOKEN_AUTH": "bar"}
|
|
||||||
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):
|
||||||
@@ -231,7 +230,7 @@ class TestAuth(unittest.TestCase):
|
|||||||
@mock.patch("blinkpy.auth.Auth.refresh_token")
|
@mock.patch("blinkpy.auth.Auth.refresh_token")
|
||||||
def test_query_retry_failed(self, mock_refresh, mock_validate):
|
def test_query_retry_failed(self, mock_refresh, mock_validate):
|
||||||
"""Check handling of failed retry request."""
|
"""Check handling of failed retry request."""
|
||||||
self.auth.seession = MockSession()
|
self.auth.session = MockSession()
|
||||||
mock_validate.side_effect = [UnauthorizedError, BlinkBadResponse]
|
mock_validate.side_effect = [UnauthorizedError, BlinkBadResponse]
|
||||||
mock_refresh.return_value = True
|
mock_refresh.return_value = True
|
||||||
self.assertEqual(self.auth.query(url="http://example.com"), None)
|
self.assertEqual(self.auth.query(url="http://example.com"), None)
|
||||||
@@ -239,6 +238,49 @@ class TestAuth(unittest.TestCase):
|
|||||||
mock_validate.side_effect = [UnauthorizedError, TokenRefreshFailed]
|
mock_validate.side_effect = [UnauthorizedError, TokenRefreshFailed]
|
||||||
self.assertEqual(self.auth.query(url="http://example.com"), None)
|
self.assertEqual(self.auth.query(url="http://example.com"), None)
|
||||||
|
|
||||||
|
def test_default_session(self):
|
||||||
|
"""Test default session creation."""
|
||||||
|
sess = self.auth.create_session()
|
||||||
|
adapter = sess.adapters["https://"]
|
||||||
|
self.assertEqual(adapter.max_retries.total, 3)
|
||||||
|
self.assertEqual(adapter.max_retries.backoff_factor, 1)
|
||||||
|
self.assertEqual(
|
||||||
|
adapter.max_retries.status_forcelist, [429, 500, 502, 503, 504]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_custom_session_full(self):
|
||||||
|
"""Test full custom session creation."""
|
||||||
|
opts = {"backoff": 2, "retries": 10, "retry_list": [404]}
|
||||||
|
sess = self.auth.create_session(opts=opts)
|
||||||
|
adapter = sess.adapters["https://"]
|
||||||
|
self.assertEqual(adapter.max_retries.total, 10)
|
||||||
|
self.assertEqual(adapter.max_retries.backoff_factor, 2)
|
||||||
|
self.assertEqual(adapter.max_retries.status_forcelist, [404])
|
||||||
|
|
||||||
|
def test_custom_session_partial(self):
|
||||||
|
"""Test partial custom session creation."""
|
||||||
|
opts1 = {"backoff": 2}
|
||||||
|
opts2 = {"retries": 5}
|
||||||
|
opts3 = {"retry_list": [101, 202]}
|
||||||
|
sess1 = self.auth.create_session(opts=opts1)
|
||||||
|
sess2 = self.auth.create_session(opts=opts2)
|
||||||
|
sess3 = self.auth.create_session(opts=opts3)
|
||||||
|
adapt1 = sess1.adapters["https://"]
|
||||||
|
adapt2 = sess2.adapters["https://"]
|
||||||
|
adapt3 = sess3.adapters["https://"]
|
||||||
|
|
||||||
|
self.assertEqual(adapt1.max_retries.total, 3)
|
||||||
|
self.assertEqual(adapt1.max_retries.backoff_factor, 2)
|
||||||
|
self.assertEqual(adapt1.max_retries.status_forcelist, [429, 500, 502, 503, 504])
|
||||||
|
|
||||||
|
self.assertEqual(adapt2.max_retries.total, 5)
|
||||||
|
self.assertEqual(adapt2.max_retries.backoff_factor, 1)
|
||||||
|
self.assertEqual(adapt2.max_retries.status_forcelist, [429, 500, 502, 503, 504])
|
||||||
|
|
||||||
|
self.assertEqual(adapt3.max_retries.total, 3)
|
||||||
|
self.assertEqual(adapt3.max_retries.backoff_factor, 1)
|
||||||
|
self.assertEqual(adapt3.max_retries.status_forcelist, [101, 202])
|
||||||
|
|
||||||
|
|
||||||
class MockSession:
|
class MockSession:
|
||||||
"""Object to mock a session."""
|
"""Object to mock a session."""
|
||||||
|
|||||||
@@ -117,10 +117,7 @@ class TestBlinkCameraSetup(unittest.TestCase):
|
|||||||
"WARNING:blinkpy.camera:Could not retrieve calibrated "
|
"WARNING:blinkpy.camera:Could not retrieve calibrated "
|
||||||
"temperature."
|
"temperature."
|
||||||
),
|
),
|
||||||
(
|
("WARNING:blinkpy.camera:Could not find thumbnail for camera new"),
|
||||||
"WARNING:blinkpy.camera:Could not find thumbnail for camera new"
|
|
||||||
"\nNoneType: None"
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user