Compare commits
42
Commits
v0.12.0
...
v0.14.0.dev0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c10fb432f7 | ||
|
|
37b729b597 | ||
|
|
749c68bb8a | ||
|
|
dcf0ce6394 | ||
|
|
102190e61b | ||
|
|
a7340c97ca | ||
|
|
43c1162634 | ||
|
|
4db7a33ef3 | ||
|
|
bfdc1e47bd | ||
|
|
5511af6244 | ||
|
|
d629d9fa3a | ||
|
|
3e22d83962 | ||
|
|
57b05daad7 | ||
|
|
419eb51b66 | ||
|
|
c4b255d3bf | ||
|
|
13b7debb33 | ||
|
|
a203c4a29f | ||
|
|
d8bac39c52 | ||
|
|
f1c06198b2 | ||
|
|
95b45f507a | ||
|
|
54bfb99d60 | ||
|
|
9dda477e3d | ||
|
|
2ca6b9edda | ||
|
|
b22764614d | ||
|
|
09c168f6e8 | ||
|
|
414eabd3ae | ||
|
|
b536bd7eb5 | ||
|
|
532a8d5071 | ||
|
|
793797e3a7 | ||
|
|
602b6160ab | ||
|
|
aff95ba9e9 | ||
|
|
74aad2af1c | ||
|
|
b96ad8b4e4 | ||
|
|
a8b731fe21 | ||
|
|
147b6143b0 | ||
|
|
2835ccf7d2 | ||
|
|
4deb53eab8 | ||
|
|
1c49b1a943 | ||
|
|
67cd4ab962 | ||
|
|
1a2017b337 | ||
|
|
7775898078 | ||
|
|
8f0bd1224d |
+1
-1
@@ -2,7 +2,7 @@ sudo: required
|
|||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
- python: "3.6"
|
- python: "3.5.3"
|
||||||
env: TOXENV=lint
|
env: TOXENV=lint
|
||||||
- python: "3.5.3"
|
- python: "3.5.3"
|
||||||
env: TOXENV=py35
|
env: TOXENV=py35
|
||||||
|
|||||||
+41
@@ -3,6 +3,47 @@ Changelog
|
|||||||
|
|
||||||
A list of changes between each release
|
A list of changes between each release
|
||||||
|
|
||||||
|
0.13.1 (2019-03-01)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
- Remove throttle decorator from network status request
|
||||||
|
|
||||||
|
0.13.0 (2019-03-01)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
**Breaking change:**
|
||||||
|
Wifi status reported in dBm again, instead of bars (which is great). Also, the old `get_camera_info` method has changed and requires a `camera_id` parameter.
|
||||||
|
|
||||||
|
- Adds throttle decorator
|
||||||
|
- Decorate following functions with 4s throttle (call method with `force=True` to override):
|
||||||
|
- request_network_status
|
||||||
|
- request_syncmodule
|
||||||
|
- request_system_arm
|
||||||
|
- request_system_disarm
|
||||||
|
- request_sync_events
|
||||||
|
- request_new_image
|
||||||
|
- request_new_video
|
||||||
|
- request_video_count
|
||||||
|
- request_cameras
|
||||||
|
- request_camera_info
|
||||||
|
- request_camera_sensors
|
||||||
|
- request_motion_detection_enable
|
||||||
|
- request_motion_detection_disable
|
||||||
|
- Use the updated homescreen api endpoint to retrieve camera information. The old method to retrieve all cameras at once seems to not exist, and this was the only solution I could figure out and confirm to work.
|
||||||
|
- Adds throttle decorator to refresh function to prevent too many frequent calls with `force_cache` flag set to `True`. This additional throttle can be overridden with the `force=True` argument passed to the refresh function.
|
||||||
|
- Add ability to cycle through login api endpoints to anticipate future endpoint deprecation
|
||||||
|
|
||||||
|
|
||||||
|
0.12.1 (2019-01-31)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
- Remove logging improvements since they were incompatible with home-assistant logging
|
||||||
|
|
||||||
|
0.12.0 (2019-01-31)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
- Fix video api endpoint, re-enables motion detection
|
||||||
|
- Add improved logging capability
|
||||||
|
- Add download video method
|
||||||
|
- Prevent blinkpy from failing at setup due to api error
|
||||||
|
|
||||||
|
|
||||||
0.11.2 (2019-01-23)
|
0.11.2 (2019-01-23)
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
- Hotfix to prevent platform from stalling due to API change
|
- Hotfix to prevent platform from stalling due to API change
|
||||||
|
|||||||
-17
@@ -51,23 +51,6 @@ 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.
|
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.
|
||||||
|
|
||||||
At initialization, you may also set the logging level of the ``blinkpy`` library like so (default is ``INFO``:
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from blinkpy import blinkpy
|
|
||||||
blink = blinkpy.Blink(..., loglevel=logging.<LEVEL>)
|
|
||||||
blink.start()
|
|
||||||
|
|
||||||
You can also disable logging of duplicate entries via the ``allow_duplicate_logs`` flag (default is ``True``):
|
|
||||||
|
|
||||||
.. code:: python
|
|
||||||
|
|
||||||
from blinkpy import blinkpy
|
|
||||||
blink = blinkpy.Blink(..., allow_duplicate_logs=False)
|
|
||||||
blink.start()
|
|
||||||
|
|
||||||
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).
|
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:
|
The below code will display cameras and their available attributes:
|
||||||
|
|||||||
+19
-7
@@ -3,11 +3,13 @@
|
|||||||
import logging
|
import logging
|
||||||
from json import dumps
|
from json import dumps
|
||||||
import blinkpy.helpers.errors as ERROR
|
import blinkpy.helpers.errors as ERROR
|
||||||
from blinkpy.helpers.util import http_req, get_time, BlinkException
|
from blinkpy.helpers.util import http_req, get_time, BlinkException, Throttle
|
||||||
from blinkpy.helpers.constants import DEFAULT_URL
|
from blinkpy.helpers.constants import DEFAULT_URL
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
MIN_THROTTLE_TIME = 2
|
||||||
|
|
||||||
|
|
||||||
def request_login(blink, url, username, password, is_retry=False):
|
def request_login(blink, url, username, password, is_retry=False):
|
||||||
"""
|
"""
|
||||||
@@ -60,6 +62,7 @@ def request_syncmodule(blink, network):
|
|||||||
return http_get(blink, url)
|
return http_get(blink, url)
|
||||||
|
|
||||||
|
|
||||||
|
@Throttle(seconds=MIN_THROTTLE_TIME)
|
||||||
def request_system_arm(blink, network):
|
def request_system_arm(blink, network):
|
||||||
"""
|
"""
|
||||||
Arm system.
|
Arm system.
|
||||||
@@ -71,6 +74,7 @@ def request_system_arm(blink, network):
|
|||||||
return http_post(blink, url)
|
return http_post(blink, url)
|
||||||
|
|
||||||
|
|
||||||
|
@Throttle(seconds=MIN_THROTTLE_TIME)
|
||||||
def request_system_disarm(blink, network):
|
def request_system_disarm(blink, network):
|
||||||
"""
|
"""
|
||||||
Disarm system.
|
Disarm system.
|
||||||
@@ -96,12 +100,15 @@ def request_command_status(blink, network, command_id):
|
|||||||
return http_get(blink, url)
|
return http_get(blink, url)
|
||||||
|
|
||||||
|
|
||||||
|
@Throttle(seconds=MIN_THROTTLE_TIME)
|
||||||
def request_homescreen(blink):
|
def request_homescreen(blink):
|
||||||
"""Request homescreen info."""
|
"""Request homescreen info."""
|
||||||
url = "{}/homescreen".format(blink.urls.base_url)
|
url = "{}/api/v3/accounts/{}/homescreen".format(blink.urls.base_url,
|
||||||
|
blink.account_id)
|
||||||
return http_get(blink, url)
|
return http_get(blink, url)
|
||||||
|
|
||||||
|
|
||||||
|
@Throttle(seconds=MIN_THROTTLE_TIME)
|
||||||
def request_sync_events(blink, network):
|
def request_sync_events(blink, network):
|
||||||
"""
|
"""
|
||||||
Request events from sync module.
|
Request events from sync module.
|
||||||
@@ -113,6 +120,7 @@ def request_sync_events(blink, network):
|
|||||||
return http_get(blink, url)
|
return http_get(blink, url)
|
||||||
|
|
||||||
|
|
||||||
|
@Throttle(seconds=MIN_THROTTLE_TIME)
|
||||||
def request_new_image(blink, network, camera_id):
|
def request_new_image(blink, network, camera_id):
|
||||||
"""
|
"""
|
||||||
Request to capture new thumbnail for camera.
|
Request to capture new thumbnail for camera.
|
||||||
@@ -127,6 +135,7 @@ def request_new_image(blink, network, camera_id):
|
|||||||
return http_post(blink, url)
|
return http_post(blink, url)
|
||||||
|
|
||||||
|
|
||||||
|
@Throttle(seconds=MIN_THROTTLE_TIME)
|
||||||
def request_new_video(blink, network, camera_id):
|
def request_new_video(blink, network, camera_id):
|
||||||
"""
|
"""
|
||||||
Request to capture new video clip.
|
Request to capture new video clip.
|
||||||
@@ -141,6 +150,7 @@ def request_new_video(blink, network, camera_id):
|
|||||||
return http_post(blink, url)
|
return http_post(blink, url)
|
||||||
|
|
||||||
|
|
||||||
|
@Throttle(seconds=MIN_THROTTLE_TIME)
|
||||||
def request_video_count(blink):
|
def request_video_count(blink):
|
||||||
"""Request total video count."""
|
"""Request total video count."""
|
||||||
url = "{}/api/v2/videos/count".format(blink.urls.base_url)
|
url = "{}/api/v2/videos/count".format(blink.urls.base_url)
|
||||||
@@ -156,8 +166,8 @@ def request_videos(blink, time=None, page=0):
|
|||||||
:param page: Page number to get videos from.
|
:param page: Page number to get videos from.
|
||||||
"""
|
"""
|
||||||
timestamp = get_time(time)
|
timestamp = get_time(time)
|
||||||
url = "{}/api/v2/videos/changed?since={}&page={}".format(
|
url = "{}/api/v1/accounts/{}/media/changed?since={}&page={}".format(
|
||||||
blink.urls.base_url, timestamp, page)
|
blink.urls.base_url, blink.account_id, timestamp, page)
|
||||||
return http_get(blink, url)
|
return http_get(blink, url)
|
||||||
|
|
||||||
|
|
||||||
@@ -180,9 +190,9 @@ def request_camera_info(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 info from.
|
:param camera_id: Camera ID of camera to request info from.
|
||||||
"""
|
"""
|
||||||
url = "{}/network/{}/camera/{}".format(blink.urls.base_url,
|
url = "{}/network/{}/camera/{}/config".format(blink.urls.base_url,
|
||||||
network,
|
network,
|
||||||
camera_id)
|
camera_id)
|
||||||
return http_get(blink, url)
|
return http_get(blink, url)
|
||||||
|
|
||||||
|
|
||||||
@@ -200,6 +210,7 @@ def request_camera_sensors(blink, network, camera_id):
|
|||||||
return http_get(blink, url)
|
return http_get(blink, url)
|
||||||
|
|
||||||
|
|
||||||
|
@Throttle(seconds=MIN_THROTTLE_TIME)
|
||||||
def request_motion_detection_enable(blink, network, camera_id):
|
def request_motion_detection_enable(blink, network, camera_id):
|
||||||
"""
|
"""
|
||||||
Enable motion detection for a camera.
|
Enable motion detection for a camera.
|
||||||
@@ -214,6 +225,7 @@ def request_motion_detection_enable(blink, network, camera_id):
|
|||||||
return http_post(blink, url)
|
return http_post(blink, url)
|
||||||
|
|
||||||
|
|
||||||
|
@Throttle(seconds=MIN_THROTTLE_TIME)
|
||||||
def request_motion_detection_disable(blink, network, camera_id):
|
def request_motion_detection_disable(blink, network, camera_id):
|
||||||
"""Disable motion detection for a camera.
|
"""Disable motion detection for a camera.
|
||||||
|
|
||||||
|
|||||||
+100
-74
@@ -25,25 +25,27 @@ from dateutil.parser import parse
|
|||||||
from blinkpy import api
|
from blinkpy import api
|
||||||
from blinkpy.sync_module import BlinkSyncModule
|
from blinkpy.sync_module import BlinkSyncModule
|
||||||
from blinkpy.helpers import errors as ERROR
|
from blinkpy.helpers import errors as ERROR
|
||||||
from blinkpy.helpers import log
|
|
||||||
from blinkpy.helpers.util import (
|
from blinkpy.helpers.util import (
|
||||||
create_session, merge_dicts, get_time, BlinkURLHandler,
|
create_session, merge_dicts, get_time, BlinkURLHandler,
|
||||||
BlinkAuthenticationException)
|
BlinkAuthenticationException, Throttle)
|
||||||
from blinkpy.helpers.constants import (
|
from blinkpy.helpers.constants import (
|
||||||
BLINK_URL, LOGIN_URL, LOGIN_BACKUP_URL)
|
BLINK_URL, LOGIN_URL, OLD_LOGIN_URL, LOGIN_BACKUP_URL)
|
||||||
from blinkpy.helpers.constants import __version__
|
from blinkpy.helpers.constants import __version__
|
||||||
|
|
||||||
REFRESH_RATE = 30
|
REFRESH_RATE = 30
|
||||||
|
|
||||||
_LOGGER = log.create_logger('blinkpy')
|
# Prevents rapid calls to blink.refresh()
|
||||||
|
# with the force_cache flag set to True
|
||||||
|
MIN_THROTTLE_TIME = 2
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Blink():
|
class Blink():
|
||||||
"""Class to initialize communication."""
|
"""Class to initialize communication."""
|
||||||
|
|
||||||
def __init__(self, username=None, password=None,
|
def __init__(self, username=None, password=None,
|
||||||
refresh_rate=REFRESH_RATE, loglevel=logging.INFO,
|
refresh_rate=REFRESH_RATE):
|
||||||
allow_duplicate_logs=True):
|
|
||||||
"""
|
"""
|
||||||
Initialize Blink system.
|
Initialize Blink system.
|
||||||
|
|
||||||
@@ -51,9 +53,6 @@ class Blink():
|
|||||||
:param password: Blink password
|
:param password: Blink password
|
||||||
:param refresh_rate: Refresh rate of blink information.
|
:param refresh_rate: Refresh rate of blink information.
|
||||||
Defaults to 15 (seconds)
|
Defaults to 15 (seconds)
|
||||||
:param loglevel: Sets the log level for the logger.
|
|
||||||
:param allow_duplicate_logs: Set to 'False' to only allow a log
|
|
||||||
message to be logged once.
|
|
||||||
"""
|
"""
|
||||||
self._username = username
|
self._username = username
|
||||||
self._password = password
|
self._password = password
|
||||||
@@ -74,10 +73,6 @@ class Blink():
|
|||||||
self.video_list = CaseInsensitiveDict({})
|
self.video_list = CaseInsensitiveDict({})
|
||||||
self._login_url = LOGIN_URL
|
self._login_url = LOGIN_URL
|
||||||
self.version = __version__
|
self.version = __version__
|
||||||
self.allow_duplicate_logs = allow_duplicate_logs
|
|
||||||
|
|
||||||
self.loglevel = loglevel
|
|
||||||
self._reset_logger()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def auth_header(self):
|
def auth_header(self):
|
||||||
@@ -91,17 +86,22 @@ class Blink():
|
|||||||
Method logs in and sets auth token, urls, and ids for future requests.
|
Method logs in and sets auth token, urls, and ids for future requests.
|
||||||
Essentially this is just a wrapper function for ease of use.
|
Essentially this is just a wrapper function for ease of use.
|
||||||
"""
|
"""
|
||||||
self._reset_logger()
|
|
||||||
|
|
||||||
if self._username is None or self._password is None:
|
if self._username is None or self._password is None:
|
||||||
if not self.login():
|
if not self.login():
|
||||||
return
|
return
|
||||||
elif not self.get_auth_token():
|
elif not self.get_auth_token():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
camera_list = self.get_cameras()
|
||||||
networks = self.get_ids()
|
networks = self.get_ids()
|
||||||
for network_name, network_id in networks.items():
|
for network_name, network_id in networks.items():
|
||||||
sync_module = BlinkSyncModule(self, network_name, network_id)
|
if network_id not in camera_list.keys():
|
||||||
|
camera_list[network_id] = {}
|
||||||
|
_LOGGER.warning("No cameras found for %s", network_name)
|
||||||
|
sync_module = BlinkSyncModule(self,
|
||||||
|
network_name,
|
||||||
|
network_id,
|
||||||
|
camera_list[network_id])
|
||||||
sync_module.start()
|
sync_module.start()
|
||||||
self.sync[network_name] = sync_module
|
self.sync[network_name] = sync_module
|
||||||
self.cameras = self.merge_cameras()
|
self.cameras = self.merge_cameras()
|
||||||
@@ -111,7 +111,7 @@ class Blink():
|
|||||||
self._username = input("Username:")
|
self._username = input("Username:")
|
||||||
self._password = getpass.getpass("Password:")
|
self._password = getpass.getpass("Password:")
|
||||||
if self.get_auth_token():
|
if self.get_auth_token():
|
||||||
_LOGGER.info("Login successful!")
|
_LOGGER.debug("Login successful!")
|
||||||
return True
|
return True
|
||||||
_LOGGER.warning("Unable to login with %s.", self._username)
|
_LOGGER.warning("Unable to login with %s.", self._username)
|
||||||
return False
|
return False
|
||||||
@@ -123,33 +123,12 @@ class Blink():
|
|||||||
if not isinstance(self._password, str):
|
if not isinstance(self._password, str):
|
||||||
raise BlinkAuthenticationException(ERROR.PASSWORD)
|
raise BlinkAuthenticationException(ERROR.PASSWORD)
|
||||||
|
|
||||||
login_url = LOGIN_URL
|
login_urls = [LOGIN_URL, OLD_LOGIN_URL, LOGIN_BACKUP_URL]
|
||||||
response = api.request_login(self,
|
|
||||||
login_url,
|
response = self.login_request(login_urls, is_retry=is_retry)
|
||||||
self._username,
|
|
||||||
self._password,
|
if not response:
|
||||||
is_retry=is_retry)
|
|
||||||
try:
|
|
||||||
if response.status_code != 200:
|
|
||||||
_LOGGER.debug("Received response code %s during login.",
|
|
||||||
response.status_code)
|
|
||||||
login_url = LOGIN_BACKUP_URL
|
|
||||||
response = api.request_login(self,
|
|
||||||
login_url,
|
|
||||||
self._username,
|
|
||||||
self._password,
|
|
||||||
is_retry=is_retry)
|
|
||||||
response = response.json()
|
|
||||||
(self.region_id, self.region), = response['region'].items()
|
|
||||||
except AttributeError:
|
|
||||||
_LOGGER.error("Login API endpoint failed with response %s",
|
|
||||||
response,
|
|
||||||
exc_info=True)
|
|
||||||
return False
|
return False
|
||||||
except KeyError:
|
|
||||||
_LOGGER.warning("Could not extract region info.")
|
|
||||||
self.region_id = 'piri'
|
|
||||||
self.region = 'UNKNOWN'
|
|
||||||
|
|
||||||
self._host = "{}.{}".format(self.region_id, BLINK_URL)
|
self._host = "{}.{}".format(self.region_id, BLINK_URL)
|
||||||
self._token = response['authtoken']['authtoken']
|
self._token = response['authtoken']['authtoken']
|
||||||
@@ -158,10 +137,44 @@ class Blink():
|
|||||||
self._auth_header = {'Host': self._host,
|
self._auth_header = {'Host': self._host,
|
||||||
'TOKEN_AUTH': self._token}
|
'TOKEN_AUTH': self._token}
|
||||||
self.urls = BlinkURLHandler(self.region_id)
|
self.urls = BlinkURLHandler(self.region_id)
|
||||||
self._login_url = login_url
|
|
||||||
|
|
||||||
return self._auth_header
|
return self._auth_header
|
||||||
|
|
||||||
|
def login_request(self, login_urls, is_retry=False):
|
||||||
|
"""Make a login request."""
|
||||||
|
try:
|
||||||
|
login_url = login_urls.pop(0)
|
||||||
|
except IndexError:
|
||||||
|
_LOGGER.error("Could not login to blink servers.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
_LOGGER.info("Attempting login with %s", login_url)
|
||||||
|
|
||||||
|
response = api.request_login(self,
|
||||||
|
login_url,
|
||||||
|
self._username,
|
||||||
|
self._password,
|
||||||
|
is_retry=is_retry)
|
||||||
|
try:
|
||||||
|
if response.status_code != 200:
|
||||||
|
response = self.login_request(login_urls)
|
||||||
|
response = response.json()
|
||||||
|
(self.region_id, self.region), = response['region'].items()
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
_LOGGER.error("Login API endpoint failed with response %s",
|
||||||
|
response,
|
||||||
|
exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
_LOGGER.warning("Could not extract region info.")
|
||||||
|
self.region_id = 'piri'
|
||||||
|
self.region = 'UNKNOWN'
|
||||||
|
|
||||||
|
self._login_url = login_url
|
||||||
|
return response
|
||||||
|
|
||||||
def get_ids(self):
|
def get_ids(self):
|
||||||
"""Set the network ID and Account ID."""
|
"""Set the network ID and Account ID."""
|
||||||
response = api.request_networks(self)
|
response = api.request_networks(self)
|
||||||
@@ -181,6 +194,26 @@ class Blink():
|
|||||||
self.network_ids = all_networks
|
self.network_ids = all_networks
|
||||||
return network_dict
|
return network_dict
|
||||||
|
|
||||||
|
def get_cameras(self):
|
||||||
|
"""Retrieve a camera list for each onboarded network."""
|
||||||
|
response = api.request_homescreen(self)
|
||||||
|
try:
|
||||||
|
all_cameras = {}
|
||||||
|
for camera in response['cameras']:
|
||||||
|
camera_network = str(camera['network_id'])
|
||||||
|
camera_name = camera['name']
|
||||||
|
camera_id = camera['id']
|
||||||
|
camera_info = {'name': camera_name, 'id': camera_id}
|
||||||
|
if camera_network not in all_cameras:
|
||||||
|
all_cameras[camera_network] = []
|
||||||
|
|
||||||
|
all_cameras[camera_network].append(camera_info)
|
||||||
|
return all_cameras
|
||||||
|
except KeyError:
|
||||||
|
_LOGGER.error("Initialization failue. Could not retrieve cameras.")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@Throttle(seconds=MIN_THROTTLE_TIME)
|
||||||
def refresh(self, force_cache=False):
|
def refresh(self, force_cache=False):
|
||||||
"""
|
"""
|
||||||
Perform a system refresh.
|
Perform a system refresh.
|
||||||
@@ -194,6 +227,8 @@ class Blink():
|
|||||||
if not force_cache:
|
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 False
|
||||||
|
|
||||||
def check_if_ok_to_update(self):
|
def check_if_ok_to_update(self):
|
||||||
"""Check if it is ok to perform an http request."""
|
"""Check if it is ok to perform an http request."""
|
||||||
@@ -212,7 +247,8 @@ class Blink():
|
|||||||
combined = merge_dicts(combined, self.sync[sync].cameras)
|
combined = merge_dicts(combined, self.sync[sync].cameras)
|
||||||
return combined
|
return combined
|
||||||
|
|
||||||
def download_videos(self, path, since=None, camera='all', stop=10):
|
def download_videos(self, path, since=None,
|
||||||
|
camera='all', stop=10, debug=False):
|
||||||
"""
|
"""
|
||||||
Download all videos from server since specified time.
|
Download all videos from server since specified time.
|
||||||
|
|
||||||
@@ -223,6 +259,8 @@ class Blink():
|
|||||||
:param camera: Camera name to retrieve. Defaults to "all".
|
:param camera: Camera name to retrieve. Defaults to "all".
|
||||||
Use a list for multiple cameras.
|
Use a list for multiple cameras.
|
||||||
:param stop: Page to stop on (~25 items per page. Default page 10).
|
:param stop: Page to stop on (~25 items per page. Default page 10).
|
||||||
|
:param debug: Set to TRUE to prevent downloading of items.
|
||||||
|
Instead of downloading, entries will be printed to log.
|
||||||
"""
|
"""
|
||||||
if since is None:
|
if since is None:
|
||||||
since_epochs = self.last_refresh
|
since_epochs = self.last_refresh
|
||||||
@@ -240,23 +278,23 @@ class Blink():
|
|||||||
response = api.request_videos(self, time=since_epochs, page=page)
|
response = api.request_videos(self, time=since_epochs, page=page)
|
||||||
_LOGGER.debug("Processing page %s", page)
|
_LOGGER.debug("Processing page %s", page)
|
||||||
try:
|
try:
|
||||||
result = response['videos']
|
result = response['media']
|
||||||
if not result:
|
if not result:
|
||||||
raise IndexError
|
raise IndexError
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
_LOGGER.info("No videos found on page %s. Exiting.", page)
|
_LOGGER.info("No videos found on page %s. Exiting.", page)
|
||||||
break
|
break
|
||||||
|
|
||||||
self._parse_downloaded_items(result, camera, path)
|
self._parse_downloaded_items(result, camera, path, debug)
|
||||||
|
|
||||||
def _parse_downloaded_items(self, result, camera, path):
|
def _parse_downloaded_items(self, result, camera, path, debug):
|
||||||
"""Parse downloaded videos."""
|
"""Parse downloaded videos."""
|
||||||
for item in result:
|
for item in result:
|
||||||
try:
|
try:
|
||||||
created_at = item['created_at']
|
created_at = item['created_at']
|
||||||
camera_name = item['camera_name']
|
camera_name = item['device_name']
|
||||||
is_deleted = item['deleted']
|
is_deleted = item['deleted']
|
||||||
address = item['address']
|
address = item['media']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
_LOGGER.info("Missing clip information, skipping...")
|
_LOGGER.info("Missing clip information, skipping...")
|
||||||
continue
|
continue
|
||||||
@@ -275,29 +313,17 @@ class Blink():
|
|||||||
filename = "{}_{}.mp4".format(camera_name, created_at)
|
filename = "{}_{}.mp4".format(camera_name, created_at)
|
||||||
filename = os.path.join(path, filename)
|
filename = os.path.join(path, filename)
|
||||||
|
|
||||||
if os.path.isfile(filename):
|
if not debug:
|
||||||
_LOGGER.info("%s already exists, skipping...", filename)
|
if os.path.isfile(filename):
|
||||||
continue
|
_LOGGER.info("%s already exists, skipping...", filename)
|
||||||
|
continue
|
||||||
|
|
||||||
response = api.http_get(self, url=clip_address,
|
response = api.http_get(self, url=clip_address,
|
||||||
stream=True, json=False)
|
stream=True, json=False)
|
||||||
with open(filename, 'wb') as vidfile:
|
with open(filename, 'wb') as vidfile:
|
||||||
copyfileobj(response.raw, vidfile)
|
copyfileobj(response.raw, vidfile)
|
||||||
|
|
||||||
_LOGGER.info("Downloaded video to %s", filename)
|
_LOGGER.info("Downloaded video to %s", filename)
|
||||||
|
else:
|
||||||
# pylint: disable=no-self-use
|
print("Camera: {}, Timestamp: {}, Address: {}".format(
|
||||||
def _reset_logger(self):
|
camera_name, created_at, address))
|
||||||
"""Reset the log handler."""
|
|
||||||
for handler in _LOGGER.handlers:
|
|
||||||
_LOGGER.removeHandler(handler)
|
|
||||||
handler.close()
|
|
||||||
_LOGGER.setLevel(self.loglevel)
|
|
||||||
if self.allow_duplicate_logs:
|
|
||||||
handler = logging.StreamHandler()
|
|
||||||
handler.setFormatter(log.log_formatter())
|
|
||||||
else:
|
|
||||||
handler = log.RepeatLogHandler()
|
|
||||||
handler.setFormatter(log.log_formatter())
|
|
||||||
|
|
||||||
_LOGGER.addHandler(handler)
|
|
||||||
|
|||||||
+3
-2
@@ -92,10 +92,11 @@ class BlinkCamera():
|
|||||||
self.network_id,
|
self.network_id,
|
||||||
self.camera_id)
|
self.camera_id)
|
||||||
|
|
||||||
def update(self, config, force_cache=False):
|
def update(self, config, force_cache=False, **kwargs):
|
||||||
"""Update camera info."""
|
"""Update camera info."""
|
||||||
|
# force = kwargs.pop('force', False)
|
||||||
self.name = config['name']
|
self.name = config['name']
|
||||||
self.camera_id = str(config['camera_id'])
|
self.camera_id = str(config['id'])
|
||||||
self.network_id = str(config['network_id'])
|
self.network_id = str(config['network_id'])
|
||||||
self.serial = config['serial']
|
self.serial = config['serial']
|
||||||
self.motion_enabled = config['enabled']
|
self.motion_enabled = config['enabled']
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
MAJOR_VERSION = 0
|
MAJOR_VERSION = 0
|
||||||
MINOR_VERSION = 12
|
MINOR_VERSION = 14
|
||||||
PATCH_VERSION = 0
|
PATCH_VERSION = '0.dev0'
|
||||||
|
|
||||||
__version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)
|
__version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)
|
||||||
|
|
||||||
@@ -47,7 +47,8 @@ URLS
|
|||||||
BLINK_URL = 'immedia-semi.com'
|
BLINK_URL = 'immedia-semi.com'
|
||||||
DEFAULT_URL = "{}.{}".format('prod', BLINK_URL)
|
DEFAULT_URL = "{}.{}".format('prod', BLINK_URL)
|
||||||
BASE_URL = "https://{}".format(DEFAULT_URL)
|
BASE_URL = "https://{}".format(DEFAULT_URL)
|
||||||
LOGIN_URL = "{}/login".format(BASE_URL)
|
LOGIN_URL = "{}/api/v2/login".format(BASE_URL)
|
||||||
|
OLD_LOGIN_URL = "{}/login".format(BASE_URL)
|
||||||
LOGIN_BACKUP_URL = "https://{}.{}/login".format('rest.piri', BLINK_URL)
|
LOGIN_BACKUP_URL = "https://{}.{}/login".format('rest.piri', BLINK_URL)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
"""Module for blinkpy logging."""
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
def create_logger(name):
|
|
||||||
"""Create a logger instance."""
|
|
||||||
handler = RepeatLogHandler()
|
|
||||||
handler.setFormatter(log_formatter())
|
|
||||||
logger = logging.getLogger(name)
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
logger.addHandler(handler)
|
|
||||||
return logger
|
|
||||||
|
|
||||||
|
|
||||||
def log_formatter():
|
|
||||||
"""Create log formatter."""
|
|
||||||
fmt = "%(asctime)s %(levelname)s [%(name)s] %(message)s"
|
|
||||||
return logging.Formatter(fmt)
|
|
||||||
|
|
||||||
|
|
||||||
class RepeatLogHandler(logging.StreamHandler):
|
|
||||||
"""Log handler for repeat entries."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""Initialize repeat log handler."""
|
|
||||||
super().__init__()
|
|
||||||
self.log_record = set()
|
|
||||||
|
|
||||||
def emit(self, record):
|
|
||||||
"""Ensure we only log a message once."""
|
|
||||||
if record.msg not in self.log_record:
|
|
||||||
self.log_record.add(record.msg)
|
|
||||||
super().emit(record)
|
|
||||||
+31
-2
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
from functools import wraps
|
||||||
from requests import Request, Session, exceptions
|
from requests import Request, Session, exceptions
|
||||||
from blinkpy.helpers.constants import BLINK_URL, TIMESTAMP_FORMAT
|
from blinkpy.helpers.constants import BLINK_URL, TIMESTAMP_FORMAT
|
||||||
import blinkpy.helpers.errors as ERROR
|
import blinkpy.helpers.errors as ERROR
|
||||||
@@ -106,8 +107,6 @@ class BlinkException(Exception):
|
|||||||
class BlinkAuthenticationException(BlinkException):
|
class BlinkAuthenticationException(BlinkException):
|
||||||
"""Class to throw authentication exception."""
|
"""Class to throw authentication exception."""
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BlinkURLHandler():
|
class BlinkURLHandler():
|
||||||
"""Class that handles Blink URLS."""
|
"""Class that handles Blink URLS."""
|
||||||
@@ -121,3 +120,33 @@ class BlinkURLHandler():
|
|||||||
self.networks_url = "{}/networks".format(self.base_url)
|
self.networks_url = "{}/networks".format(self.base_url)
|
||||||
self.video_url = "{}/api/v2/videos".format(self.base_url)
|
self.video_url = "{}/api/v2/videos".format(self.base_url)
|
||||||
_LOGGER.debug("Setting base url to %s.", self.base_url)
|
_LOGGER.debug("Setting base url to %s.", self.base_url)
|
||||||
|
|
||||||
|
|
||||||
|
class Throttle():
|
||||||
|
"""Class for throttling api calls."""
|
||||||
|
|
||||||
|
def __init__(self, seconds=10):
|
||||||
|
"""Initialize throttle class."""
|
||||||
|
self.throttle_time = seconds
|
||||||
|
self.last_call = 0
|
||||||
|
|
||||||
|
def __call__(self, method):
|
||||||
|
"""Throttle caller method."""
|
||||||
|
def throttle_method():
|
||||||
|
"""Call when method is throttled."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
@wraps(method)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
"""Wrap that checks for throttling."""
|
||||||
|
force = kwargs.pop('force', False)
|
||||||
|
now = int(time.time())
|
||||||
|
last_call_delta = now - self.last_call
|
||||||
|
if force or last_call_delta > self.throttle_time:
|
||||||
|
result = method(*args, *kwargs)
|
||||||
|
self.last_call = now
|
||||||
|
return result
|
||||||
|
|
||||||
|
return throttle_method()
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|||||||
+43
-27
@@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
class BlinkSyncModule():
|
class BlinkSyncModule():
|
||||||
"""Class to initialize sync module."""
|
"""Class to initialize sync module."""
|
||||||
|
|
||||||
def __init__(self, blink, network_name, network_id):
|
def __init__(self, blink, network_name, network_id, camera_list):
|
||||||
"""
|
"""
|
||||||
Initialize Blink sync module.
|
Initialize Blink sync module.
|
||||||
|
|
||||||
@@ -30,12 +30,12 @@ class BlinkSyncModule():
|
|||||||
self.sync_id = None
|
self.sync_id = None
|
||||||
self.host = None
|
self.host = None
|
||||||
self.summary = None
|
self.summary = None
|
||||||
self.homescreen = None
|
|
||||||
self.network_info = None
|
self.network_info = None
|
||||||
self.events = []
|
self.events = []
|
||||||
self.cameras = CaseInsensitiveDict({})
|
self.cameras = CaseInsensitiveDict({})
|
||||||
self.motion = {}
|
self.motion = {}
|
||||||
self.last_record = {}
|
self.last_record = {}
|
||||||
|
self.camera_list = camera_list
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attributes(self):
|
def attributes(self):
|
||||||
@@ -64,7 +64,10 @@ class BlinkSyncModule():
|
|||||||
@property
|
@property
|
||||||
def arm(self):
|
def arm(self):
|
||||||
"""Return status of sync module: armed/disarmed."""
|
"""Return status of sync module: armed/disarmed."""
|
||||||
return self.network_info['network']['armed']
|
try:
|
||||||
|
return self.network_info['network']['armed']
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
return None
|
||||||
|
|
||||||
@arm.setter
|
@arm.setter
|
||||||
def arm(self, value):
|
def arm(self, value):
|
||||||
@@ -76,7 +79,8 @@ class BlinkSyncModule():
|
|||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Initialize the system."""
|
"""Initialize the system."""
|
||||||
response = api.request_syncmodule(self.blink, self.network_id)
|
response = api.request_syncmodule(self.blink,
|
||||||
|
self.network_id)
|
||||||
try:
|
try:
|
||||||
self.summary = response['syncmodule']
|
self.summary = response['syncmodule']
|
||||||
self.network_id = self.summary['network_id']
|
self.network_id = self.summary['network_id']
|
||||||
@@ -94,24 +98,35 @@ class BlinkSyncModule():
|
|||||||
response,
|
response,
|
||||||
exc_info=True)
|
exc_info=True)
|
||||||
|
|
||||||
self.events = self.get_events()
|
|
||||||
self.homescreen = api.request_homescreen(self.blink)
|
|
||||||
self.network_info = api.request_network_status(self.blink,
|
self.network_info = api.request_network_status(self.blink,
|
||||||
self.network_id)
|
self.network_id)
|
||||||
|
|
||||||
self.check_new_videos()
|
self.check_new_videos()
|
||||||
camera_info = self.get_camera_info()
|
try:
|
||||||
for camera_config in camera_info:
|
for camera_config in self.camera_list:
|
||||||
name = camera_config['name']
|
if 'name' not in camera_config:
|
||||||
self.cameras[name] = BlinkCamera(self)
|
break
|
||||||
self.motion[name] = False
|
name = camera_config['name']
|
||||||
self.cameras[name].update(camera_config, force_cache=True)
|
self.cameras[name] = BlinkCamera(self)
|
||||||
|
self.motion[name] = False
|
||||||
|
camera_info = self.get_camera_info(camera_config['id'])
|
||||||
|
self.cameras[name].update(camera_info,
|
||||||
|
force_cache=True,
|
||||||
|
force=True)
|
||||||
|
except KeyError:
|
||||||
|
_LOGGER.error("Could not create cameras instances for %s",
|
||||||
|
self.name,
|
||||||
|
exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_events(self):
|
def get_events(self, **kwargs):
|
||||||
"""Retrieve events from server."""
|
"""Retrieve events from server."""
|
||||||
response = api.request_sync_events(self.blink, self.network_id)
|
force = kwargs.pop('force', False)
|
||||||
|
response = api.request_sync_events(self.blink,
|
||||||
|
self.network_id,
|
||||||
|
force=force)
|
||||||
try:
|
try:
|
||||||
return response['event']
|
return response['event']
|
||||||
except (TypeError, KeyError):
|
except (TypeError, KeyError):
|
||||||
@@ -120,11 +135,13 @@ class BlinkSyncModule():
|
|||||||
exc_info=True)
|
exc_info=True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_camera_info(self):
|
def get_camera_info(self, camera_id):
|
||||||
"""Retrieve camera information."""
|
"""Retrieve camera information."""
|
||||||
response = api.request_cameras(self.blink, self.network_id)
|
response = api.request_camera_info(self.blink,
|
||||||
|
self.network_id,
|
||||||
|
camera_id)
|
||||||
try:
|
try:
|
||||||
return response['devicestatus']
|
return response['camera'][0]
|
||||||
except (TypeError, KeyError):
|
except (TypeError, KeyError):
|
||||||
_LOGGER.error("Could not extract camera info: %s",
|
_LOGGER.error("Could not extract camera info: %s",
|
||||||
response,
|
response,
|
||||||
@@ -133,35 +150,34 @@ class BlinkSyncModule():
|
|||||||
|
|
||||||
def refresh(self, force_cache=False):
|
def refresh(self, force_cache=False):
|
||||||
"""Get all blink cameras and pulls their most recent status."""
|
"""Get all blink cameras and pulls their most recent status."""
|
||||||
self.events = self.get_events()
|
|
||||||
self.homescreen = api.request_homescreen(self.blink)
|
|
||||||
self.network_info = api.request_network_status(self.blink,
|
self.network_info = api.request_network_status(self.blink,
|
||||||
self.network_id)
|
self.network_id)
|
||||||
camera_info = self.get_camera_info()
|
|
||||||
self.check_new_videos()
|
self.check_new_videos()
|
||||||
for camera_config in camera_info:
|
for camera_name in self.cameras.keys():
|
||||||
name = camera_config['name']
|
camera_id = self.cameras[camera_name].camera_id
|
||||||
self.cameras[name].update(camera_config, force_cache=force_cache)
|
camera_info = self.get_camera_info(camera_id)
|
||||||
|
self.cameras[camera_name].update(camera_info,
|
||||||
|
force_cache=force_cache)
|
||||||
|
|
||||||
def check_new_videos(self):
|
def check_new_videos(self):
|
||||||
"""Check if new videos since last refresh."""
|
"""Check if new videos since last refresh."""
|
||||||
resp = api.request_videos(self.blink,
|
resp = api.request_videos(self.blink,
|
||||||
time=self.blink.last_refresh,
|
time=self.blink.last_refresh,
|
||||||
page=0)
|
page=1)
|
||||||
|
|
||||||
for camera in self.cameras.keys():
|
for camera in self.cameras.keys():
|
||||||
self.motion[camera] = False
|
self.motion[camera] = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
info = resp['videos']
|
info = resp['media']
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
_LOGGER.warning("Could not check for motion. Response: %s", resp)
|
_LOGGER.warning("Could not check for motion. Response: %s", resp)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for entry in info:
|
for entry in info:
|
||||||
try:
|
try:
|
||||||
name = entry['camera_name']
|
name = entry['device_name']
|
||||||
clip = entry['address']
|
clip = entry['media']
|
||||||
timestamp = entry['created_at']
|
timestamp = entry['created_at']
|
||||||
self.motion[name] = True
|
self.motion[name] = True
|
||||||
self.last_record[name] = {'clip': clip, 'time': timestamp}
|
self.last_record[name] = {'clip': clip, 'time': timestamp}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ reports=no
|
|||||||
# too-many-* - are not enforced for the sake of readability
|
# too-many-* - are not enforced for the sake of readability
|
||||||
# too-few-* - same as too-many-*
|
# too-few-* - same as too-many-*
|
||||||
# no-else-return - I don't see any reason to enforce this. both forms are readable
|
# no-else-return - I don't see any reason to enforce this. both forms are readable
|
||||||
|
# no-self-use - stupid and only annoying
|
||||||
|
# unexpected-keyword-arg - doesn't allow for use of **kwargs, which is dumb
|
||||||
|
|
||||||
disable=
|
disable=
|
||||||
locally-disabled,
|
locally-disabled,
|
||||||
@@ -23,3 +25,5 @@ disable=
|
|||||||
too-many-lines,
|
too-many-lines,
|
||||||
too-few-public-methods,
|
too-few-public-methods,
|
||||||
no-else-return,
|
no-else-return,
|
||||||
|
no-self-use,
|
||||||
|
unexpected-keyword-arg,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
flake8==3.5.0
|
flake8==3.5.0
|
||||||
flake8-docstrings==1.3.0
|
flake8-docstrings==1.3.0
|
||||||
pylint==2.1.1
|
pylint==2.3.0
|
||||||
pydocstyle==2.1.1
|
pydocstyle==2.1.1
|
||||||
pytest==3.7.1
|
pytest==3.7.1
|
||||||
pytest-cov>=2.3.1
|
pytest-cov>=2.3.1
|
||||||
|
|||||||
@@ -67,5 +67,3 @@ def mocked_session_send(*args, **kwargs):
|
|||||||
|
|
||||||
class MockURLHandler(BlinkURLHandler):
|
class MockURLHandler(BlinkURLHandler):
|
||||||
"""Mocks URL Handler in blinkpy module."""
|
"""Mocks URL Handler in blinkpy module."""
|
||||||
|
|
||||||
pass
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
import logging
|
import logging
|
||||||
from requests import Request
|
|
||||||
|
|
||||||
from blinkpy import blinkpy
|
from blinkpy import blinkpy
|
||||||
from blinkpy.sync_module import BlinkSyncModule
|
from blinkpy.sync_module import BlinkSyncModule
|
||||||
@@ -56,31 +55,39 @@ class TestBlinkFunctions(unittest.TestCase):
|
|||||||
"""Clean up after test."""
|
"""Clean up after test."""
|
||||||
self.blink = None
|
self.blink = None
|
||||||
|
|
||||||
@mock.patch('blinkpy.blinkpy.api.http_req')
|
@mock.patch('blinkpy.blinkpy.api.request_login')
|
||||||
def test_backup_url(self, req, mock_sess):
|
def test_backup_url(self, req, mock_sess):
|
||||||
"""Test backup login method."""
|
"""Test backup login method."""
|
||||||
fake_req = Request('POST', 'http://wrong.url').prepare()
|
|
||||||
json_resp = {
|
json_resp = {
|
||||||
'authtoken': {'authtoken': 'foobar123'},
|
'authtoken': {'authtoken': 'foobar123'},
|
||||||
'networks': {'1234': {'name': 'foobar', 'onboarded': True}}
|
'networks': {'1234': {'name': 'foobar', 'onboarded': True}}
|
||||||
}
|
}
|
||||||
|
bad_req = mresp.MockResponse({}, 404)
|
||||||
new_req = mresp.MockResponse(json_resp, 200)
|
new_req = mresp.MockResponse(json_resp, 200)
|
||||||
req.side_effect = [
|
req.side_effect = [
|
||||||
mresp.mocked_session_send(fake_req),
|
bad_req,
|
||||||
|
bad_req,
|
||||||
new_req
|
new_req
|
||||||
]
|
]
|
||||||
self.blink.get_auth_token()
|
self.blink.login_request(['test1', 'test2', 'test3'])
|
||||||
self.assertEqual(self.blink.region_id, 'piri')
|
|
||||||
self.assertEqual(self.blink.region, 'UNKNOWN')
|
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
self.assertEqual(self.blink._token, 'foobar123')
|
self.assertEqual(self.blink._login_url, 'test3')
|
||||||
|
|
||||||
|
req.side_effect = [
|
||||||
|
bad_req,
|
||||||
|
new_req,
|
||||||
|
bad_req
|
||||||
|
]
|
||||||
|
self.blink.login_request(['test1', 'test2', 'test3'])
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
self.assertEqual(self.blink._login_url, 'test2')
|
||||||
|
|
||||||
def test_merge_cameras(self, mock_sess):
|
def test_merge_cameras(self, mock_sess):
|
||||||
"""Test merge camera functionality."""
|
"""Test merge camera functionality."""
|
||||||
first_dict = {'foo': 'bar', 'test': 123}
|
first_dict = {'foo': 'bar', 'test': 123}
|
||||||
next_dict = {'foobar': 456, 'bar': 'foo'}
|
next_dict = {'foobar': 456, 'bar': 'foo'}
|
||||||
self.blink.sync['foo'] = BlinkSyncModule(self.blink, 'foo', 1)
|
self.blink.sync['foo'] = BlinkSyncModule(self.blink, 'foo', 1, [])
|
||||||
self.blink.sync['bar'] = BlinkSyncModule(self.blink, 'bar', 2)
|
self.blink.sync['bar'] = BlinkSyncModule(self.blink, 'bar', 2, [])
|
||||||
self.blink.sync['foo'].cameras = first_dict
|
self.blink.sync['foo'].cameras = first_dict
|
||||||
self.blink.sync['bar'].cameras = next_dict
|
self.blink.sync['bar'].cameras = next_dict
|
||||||
result = self.blink.merge_cameras()
|
result = self.blink.merge_cameras()
|
||||||
@@ -90,14 +97,17 @@ class TestBlinkFunctions(unittest.TestCase):
|
|||||||
@mock.patch('blinkpy.blinkpy.api.request_videos')
|
@mock.patch('blinkpy.blinkpy.api.request_videos')
|
||||||
def test_download_video_exit(self, mock_req, mock_sess):
|
def test_download_video_exit(self, mock_req, mock_sess):
|
||||||
"""Test we exit method when provided bad response."""
|
"""Test we exit method when provided bad response."""
|
||||||
blink = blinkpy.Blink(loglevel=logging.DEBUG)
|
blink = blinkpy.Blink()
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
blinkpy._LOGGER.setLevel(logging.DEBUG)
|
||||||
blink.last_refresh = 0
|
blink.last_refresh = 0
|
||||||
mock_req.return_value = {}
|
mock_req.return_value = {}
|
||||||
formatted_date = get_time(blink.last_refresh)
|
formatted_date = get_time(blink.last_refresh)
|
||||||
expected_log = [
|
expected_log = [
|
||||||
"INFO:blinkpy:Retrieving videos since {}".format(formatted_date),
|
"INFO:blinkpy.blinkpy:Retrieving videos since {}".format(
|
||||||
"DEBUG:blinkpy:Processing page 1",
|
formatted_date),
|
||||||
"INFO:blinkpy:No videos found on page 1. Exiting."
|
"DEBUG:blinkpy.blinkpy:Processing page 1",
|
||||||
|
"INFO:blinkpy.blinkpy:No videos found on page 1. Exiting."
|
||||||
]
|
]
|
||||||
with self.assertLogs() as dl_log:
|
with self.assertLogs() as dl_log:
|
||||||
blink.download_videos('/tmp')
|
blink.download_videos('/tmp')
|
||||||
@@ -106,21 +116,24 @@ class TestBlinkFunctions(unittest.TestCase):
|
|||||||
@mock.patch('blinkpy.blinkpy.api.request_videos')
|
@mock.patch('blinkpy.blinkpy.api.request_videos')
|
||||||
def test_parse_downloaded_items(self, mock_req, mock_sess):
|
def test_parse_downloaded_items(self, mock_req, mock_sess):
|
||||||
"""Test ability to parse downloaded items list."""
|
"""Test ability to parse downloaded items list."""
|
||||||
blink = blinkpy.Blink(loglevel=logging.DEBUG)
|
blink = blinkpy.Blink()
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
blinkpy._LOGGER.setLevel(logging.DEBUG)
|
||||||
generic_entry = {
|
generic_entry = {
|
||||||
'created_at': '1970',
|
'created_at': '1970',
|
||||||
'camera_name': 'foo',
|
'device_name': 'foo',
|
||||||
'deleted': True,
|
'deleted': True,
|
||||||
'address': '/bar.mp4'
|
'media': '/bar.mp4'
|
||||||
}
|
}
|
||||||
result = [generic_entry]
|
result = [generic_entry]
|
||||||
mock_req.return_value = {'videos': result}
|
mock_req.return_value = {'media': result}
|
||||||
blink.last_refresh = 0
|
blink.last_refresh = 0
|
||||||
formatted_date = get_time(blink.last_refresh)
|
formatted_date = get_time(blink.last_refresh)
|
||||||
expected_log = [
|
expected_log = [
|
||||||
"INFO:blinkpy:Retrieving videos since {}".format(formatted_date),
|
"INFO:blinkpy.blinkpy:Retrieving videos since {}".format(
|
||||||
"DEBUG:blinkpy:Processing page 1",
|
formatted_date),
|
||||||
"DEBUG:blinkpy:foo: /bar.mp4 is marked as deleted."
|
"DEBUG:blinkpy.blinkpy:Processing page 1",
|
||||||
|
"DEBUG:blinkpy.blinkpy:foo: /bar.mp4 is marked as deleted."
|
||||||
]
|
]
|
||||||
with self.assertLogs() as dl_log:
|
with self.assertLogs() as dl_log:
|
||||||
blink.download_videos('/tmp', stop=2)
|
blink.download_videos('/tmp', stop=2)
|
||||||
@@ -129,21 +142,24 @@ class TestBlinkFunctions(unittest.TestCase):
|
|||||||
@mock.patch('blinkpy.blinkpy.api.request_videos')
|
@mock.patch('blinkpy.blinkpy.api.request_videos')
|
||||||
def test_parse_camera_not_in_list(self, mock_req, mock_sess):
|
def test_parse_camera_not_in_list(self, mock_req, mock_sess):
|
||||||
"""Test ability to parse downloaded items list."""
|
"""Test ability to parse downloaded items list."""
|
||||||
blink = blinkpy.Blink(loglevel=logging.DEBUG)
|
blink = blinkpy.Blink()
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
blinkpy._LOGGER.setLevel(logging.DEBUG)
|
||||||
generic_entry = {
|
generic_entry = {
|
||||||
'created_at': '1970',
|
'created_at': '1970',
|
||||||
'camera_name': 'foo',
|
'device_name': 'foo',
|
||||||
'deleted': True,
|
'deleted': True,
|
||||||
'address': '/bar.mp4'
|
'media': '/bar.mp4'
|
||||||
}
|
}
|
||||||
result = [generic_entry]
|
result = [generic_entry]
|
||||||
mock_req.return_value = {'videos': result}
|
mock_req.return_value = {'media': result}
|
||||||
blink.last_refresh = 0
|
blink.last_refresh = 0
|
||||||
formatted_date = get_time(blink.last_refresh)
|
formatted_date = get_time(blink.last_refresh)
|
||||||
expected_log = [
|
expected_log = [
|
||||||
"INFO:blinkpy:Retrieving videos since {}".format(formatted_date),
|
"INFO:blinkpy.blinkpy:Retrieving videos since {}".format(
|
||||||
"DEBUG:blinkpy:Processing page 1",
|
formatted_date),
|
||||||
"DEBUG:blinkpy:Skipping videos for foo."
|
"DEBUG:blinkpy.blinkpy:Processing page 1",
|
||||||
|
"DEBUG:blinkpy.blinkpy:Skipping videos for foo."
|
||||||
]
|
]
|
||||||
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)
|
||||||
|
|||||||
+24
-1
@@ -31,7 +31,10 @@ class TestBlinkSetup(unittest.TestCase):
|
|||||||
self.blink_no_cred = Blink()
|
self.blink_no_cred = Blink()
|
||||||
self.blink = Blink(username=USERNAME,
|
self.blink = Blink(username=USERNAME,
|
||||||
password=PASSWORD)
|
password=PASSWORD)
|
||||||
self.blink.sync['test'] = BlinkSyncModule(self.blink, 'test', '1234')
|
self.blink.sync['test'] = BlinkSyncModule(self.blink,
|
||||||
|
'test',
|
||||||
|
'1234',
|
||||||
|
[])
|
||||||
self.blink.urls = BlinkURLHandler('test')
|
self.blink.urls = BlinkURLHandler('test')
|
||||||
self.blink.session = create_session()
|
self.blink.session = create_session()
|
||||||
|
|
||||||
@@ -160,3 +163,23 @@ class TestBlinkSetup(unittest.TestCase):
|
|||||||
"""Check that we appropriately handle unexpected login info."""
|
"""Check that we appropriately handle unexpected login info."""
|
||||||
mock_login.return_value = None
|
mock_login.return_value = None
|
||||||
self.assertFalse(self.blink.get_auth_token())
|
self.assertFalse(self.blink.get_auth_token())
|
||||||
|
|
||||||
|
@mock.patch('blinkpy.api.request_homescreen')
|
||||||
|
def test_get_cameras(self, mock_home, mock_sess):
|
||||||
|
"""Check retrieval of camera information."""
|
||||||
|
mock_home.return_value = {
|
||||||
|
'cameras': [{'name': 'foo', 'network_id': 1234, 'id': 5678},
|
||||||
|
{'name': 'bar', 'network_id': 1234, 'id': 5679},
|
||||||
|
{'name': 'test', 'network_id': 4321, 'id': 0000}]
|
||||||
|
}
|
||||||
|
result = self.blink.get_cameras()
|
||||||
|
self.assertEqual(result, {'1234': [{'name': 'foo', 'id': 5678},
|
||||||
|
{'name': 'bar', 'id': 5679}],
|
||||||
|
'4321': [{'name': 'test', 'id': 0000}]})
|
||||||
|
|
||||||
|
@mock.patch('blinkpy.api.request_homescreen')
|
||||||
|
def test_get_cameras_failure(self, mock_home, mock_sess):
|
||||||
|
"""Check that on failure we initialize empty info and move on."""
|
||||||
|
mock_home.return_value = {}
|
||||||
|
result = self.blink.get_cameras()
|
||||||
|
self.assertEqual(result, {})
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class TestBlinkCameraSetup(unittest.TestCase):
|
|||||||
self.blink._auth_header = header
|
self.blink._auth_header = header
|
||||||
self.blink.session = create_session()
|
self.blink.session = create_session()
|
||||||
self.blink.urls = BlinkURLHandler('test')
|
self.blink.urls = BlinkURLHandler('test')
|
||||||
self.blink.sync['test'] = BlinkSyncModule(self.blink, 'test', 1234)
|
self.blink.sync['test'] = BlinkSyncModule(self.blink, 'test', 1234, [])
|
||||||
self.camera = BlinkCamera(self.blink.sync['test'])
|
self.camera = BlinkCamera(self.blink.sync['test'])
|
||||||
self.camera.name = 'foobar'
|
self.camera.name = 'foobar'
|
||||||
self.blink.sync['test'].cameras['foobar'] = self.camera
|
self.blink.sync['test'].cameras['foobar'] = self.camera
|
||||||
@@ -59,7 +59,7 @@ class TestBlinkCameraSetup(unittest.TestCase):
|
|||||||
"""Test that we can properly update camera properties."""
|
"""Test that we can properly update camera properties."""
|
||||||
config = {
|
config = {
|
||||||
'name': 'new',
|
'name': 'new',
|
||||||
'camera_id': 1234,
|
'id': 1234,
|
||||||
'network_id': 5678,
|
'network_id': 5678,
|
||||||
'serial': '12345678',
|
'serial': '12345678',
|
||||||
'enabled': False,
|
'enabled': False,
|
||||||
@@ -115,7 +115,7 @@ class TestBlinkCameraSetup(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
config = {
|
config = {
|
||||||
'name': 'new',
|
'name': 'new',
|
||||||
'camera_id': 1234,
|
'id': 1234,
|
||||||
'network_id': 5678,
|
'network_id': 5678,
|
||||||
'serial': '12345678',
|
'serial': '12345678',
|
||||||
'enabled': False,
|
'enabled': False,
|
||||||
@@ -144,7 +144,7 @@ class TestBlinkCameraSetup(unittest.TestCase):
|
|||||||
self.camera.last_record = ['1']
|
self.camera.last_record = ['1']
|
||||||
config = {
|
config = {
|
||||||
'name': 'new',
|
'name': 'new',
|
||||||
'camera_id': 1234,
|
'id': 1234,
|
||||||
'network_id': 5678,
|
'network_id': 5678,
|
||||||
'serial': '12345678',
|
'serial': '12345678',
|
||||||
'enabled': False,
|
'enabled': False,
|
||||||
@@ -159,7 +159,7 @@ class TestBlinkCameraSetup(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
self.assertEqual(self.camera.temperature_calibrated, None)
|
self.assertEqual(self.camera.temperature_calibrated, None)
|
||||||
with self.assertLogs() as logrecord:
|
with self.assertLogs() as logrecord:
|
||||||
self.camera.update(config)
|
self.camera.update(config, force=True)
|
||||||
self.assertEqual(self.camera.thumbnail, None)
|
self.assertEqual(self.camera.thumbnail, None)
|
||||||
self.assertEqual(self.camera.last_record, ['1'])
|
self.assertEqual(self.camera.last_record, ['1'])
|
||||||
self.assertEqual(self.camera.temperature_calibrated, 68)
|
self.assertEqual(self.camera.temperature_calibrated, 68)
|
||||||
@@ -176,7 +176,7 @@ class TestBlinkCameraSetup(unittest.TestCase):
|
|||||||
mock_sess.return_value = 'foobar'
|
mock_sess.return_value = 'foobar'
|
||||||
config = {
|
config = {
|
||||||
'name': 'new',
|
'name': 'new',
|
||||||
'camera_id': 1234,
|
'id': 1234,
|
||||||
'network_id': 5678,
|
'network_id': 5678,
|
||||||
'serial': '12345678',
|
'serial': '12345678',
|
||||||
'enabled': False,
|
'enabled': False,
|
||||||
|
|||||||
+11
-21
@@ -24,7 +24,10 @@ class TestBlinkSyncModule(unittest.TestCase):
|
|||||||
'TOKEN_AUTH': 'foobar123'
|
'TOKEN_AUTH': 'foobar123'
|
||||||
}
|
}
|
||||||
self.blink.urls = blinkpy.BlinkURLHandler('test')
|
self.blink.urls = blinkpy.BlinkURLHandler('test')
|
||||||
self.blink.sync['test'] = BlinkSyncModule(self.blink, 'test', '1234')
|
self.blink.sync['test'] = BlinkSyncModule(self.blink,
|
||||||
|
'test',
|
||||||
|
'1234',
|
||||||
|
[])
|
||||||
self.camera = BlinkCamera(self.blink.sync)
|
self.camera = BlinkCamera(self.blink.sync)
|
||||||
self.mock_start = [
|
self.mock_start = [
|
||||||
{'syncmodule': {
|
{'syncmodule': {
|
||||||
@@ -52,15 +55,16 @@ class TestBlinkSyncModule(unittest.TestCase):
|
|||||||
|
|
||||||
def test_get_camera_info(self, mock_resp):
|
def test_get_camera_info(self, mock_resp):
|
||||||
"""Test get camera info function."""
|
"""Test get camera info function."""
|
||||||
mock_resp.return_value = {'devicestatus': True}
|
mock_resp.return_value = {'camera': ['foobar']}
|
||||||
self.assertEqual(self.blink.sync['test'].get_camera_info(), True)
|
self.assertEqual(self.blink.sync['test'].get_camera_info('1234'),
|
||||||
|
'foobar')
|
||||||
|
|
||||||
def test_check_new_videos(self, mock_resp):
|
def test_check_new_videos(self, mock_resp):
|
||||||
"""Test recent video response."""
|
"""Test recent video response."""
|
||||||
mock_resp.return_value = {
|
mock_resp.return_value = {
|
||||||
'videos': [{
|
'media': [{
|
||||||
'camera_name': 'foo',
|
'device_name': 'foo',
|
||||||
'address': '/foo/bar.mp4',
|
'media': '/foo/bar.mp4',
|
||||||
'created_at': '1970-01-01T00:00:00+0:00'
|
'created_at': '1970-01-01T00:00:00+0:00'
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
@@ -72,7 +76,7 @@ class TestBlinkSyncModule(unittest.TestCase):
|
|||||||
{'clip': '/foo/bar.mp4',
|
{'clip': '/foo/bar.mp4',
|
||||||
'time': '1970-01-01T00:00:00+0:00'})
|
'time': '1970-01-01T00:00:00+0:00'})
|
||||||
self.assertEqual(sync_module.motion, {'foo': True})
|
self.assertEqual(sync_module.motion, {'foo': True})
|
||||||
mock_resp.return_value = {'videos': []}
|
mock_resp.return_value = {'media': []}
|
||||||
self.assertTrue(sync_module.check_new_videos())
|
self.assertTrue(sync_module.check_new_videos())
|
||||||
self.assertEqual(sync_module.motion, {'foo': False})
|
self.assertEqual(sync_module.motion, {'foo': False})
|
||||||
self.assertEqual(sync_module.last_record['foo'],
|
self.assertEqual(sync_module.last_record['foo'],
|
||||||
@@ -126,20 +130,6 @@ class TestBlinkSyncModule(unittest.TestCase):
|
|||||||
self.blink.sync['test'].start()
|
self.blink.sync['test'].start()
|
||||||
self.assertEqual(self.blink.sync['test'].network_id, 8675309)
|
self.assertEqual(self.blink.sync['test'].network_id, 8675309)
|
||||||
|
|
||||||
def test_unexpected_events(self, mock_resp):
|
|
||||||
"""Test unexpected events response."""
|
|
||||||
self.mock_start[1] = None
|
|
||||||
mock_resp.side_effect = self.mock_start
|
|
||||||
self.blink.sync['test'].start()
|
|
||||||
self.assertEqual(self.blink.sync['test'].events, False)
|
|
||||||
|
|
||||||
def test_missing_events(self, mock_resp):
|
|
||||||
"""Test missing events key from response."""
|
|
||||||
self.mock_start[1] = {}
|
|
||||||
mock_resp.side_effect = self.mock_start
|
|
||||||
self.blink.sync['test'].start()
|
|
||||||
self.assertEqual(self.blink.sync['test'].events, False)
|
|
||||||
|
|
||||||
def test_unexpected_camera_info(self, mock_resp):
|
def test_unexpected_camera_info(self, mock_resp):
|
||||||
"""Test unexpected camera info response."""
|
"""Test unexpected camera info response."""
|
||||||
self.blink.sync['test'].cameras['foo'] = None
|
self.blink.sync['test'].cameras['foo'] = None
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
"""Test various api functions."""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
import time
|
||||||
|
from blinkpy.helpers.util import Throttle
|
||||||
|
|
||||||
|
|
||||||
|
class TestUtil(unittest.TestCase):
|
||||||
|
"""Test the helpers/util module."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Initialize the blink module."""
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Tear down blink module."""
|
||||||
|
|
||||||
|
def test_throttle(self):
|
||||||
|
"""Test the throttle decorator."""
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
@Throttle(seconds=5)
|
||||||
|
def test_throttle():
|
||||||
|
calls.append(1)
|
||||||
|
|
||||||
|
now = int(time.time())
|
||||||
|
now_plus_four = now + 4
|
||||||
|
now_plus_six = now + 6
|
||||||
|
|
||||||
|
test_throttle()
|
||||||
|
self.assertEqual(1, len(calls))
|
||||||
|
|
||||||
|
# Call again, still shouldn't fire
|
||||||
|
test_throttle()
|
||||||
|
self.assertEqual(1, len(calls))
|
||||||
|
|
||||||
|
# Call with force
|
||||||
|
test_throttle(force=True)
|
||||||
|
self.assertEqual(2, len(calls))
|
||||||
|
|
||||||
|
# Call without throttle, shouldn't fire
|
||||||
|
test_throttle()
|
||||||
|
self.assertEqual(2, len(calls))
|
||||||
|
|
||||||
|
# Fake time as 4 seconds from now
|
||||||
|
with mock.patch('time.time', return_value=now_plus_four):
|
||||||
|
test_throttle()
|
||||||
|
self.assertEqual(2, len(calls))
|
||||||
|
|
||||||
|
# Fake time as 6 seconds from now
|
||||||
|
with mock.patch('time.time', return_value=now_plus_six):
|
||||||
|
test_throttle()
|
||||||
|
self.assertEqual(3, len(calls))
|
||||||
|
|
||||||
|
def test_throttle_per_instance(self):
|
||||||
|
"""Test that throttle is done once per instance of class."""
|
||||||
|
class Tester:
|
||||||
|
"""A tester class for throttling."""
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
"""Test the throttle."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
tester = Tester()
|
||||||
|
throttled = Throttle(seconds=1)(tester.test)
|
||||||
|
self.assertEqual(throttled(), True)
|
||||||
|
self.assertEqual(throttled(), None)
|
||||||
|
|
||||||
|
def test_throttle_on_two_methods(self):
|
||||||
|
"""Test that throttle works for multiple methods."""
|
||||||
|
class Tester:
|
||||||
|
"""A tester class for throttling."""
|
||||||
|
|
||||||
|
@Throttle(seconds=3)
|
||||||
|
def test1(self):
|
||||||
|
"""Test function for throttle."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
@Throttle(seconds=5)
|
||||||
|
def test2(self):
|
||||||
|
"""Test function for throttle."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
tester = Tester()
|
||||||
|
now = time.time()
|
||||||
|
now_plus_4 = now + 4
|
||||||
|
now_plus_6 = now + 6
|
||||||
|
|
||||||
|
self.assertEqual(tester.test1(), True)
|
||||||
|
self.assertEqual(tester.test2(), True)
|
||||||
|
self.assertEqual(tester.test1(), None)
|
||||||
|
self.assertEqual(tester.test2(), None)
|
||||||
|
|
||||||
|
with mock.patch('time.time', return_value=now_plus_4):
|
||||||
|
self.assertEqual(tester.test1(), True)
|
||||||
|
self.assertEqual(tester.test2(), None)
|
||||||
|
|
||||||
|
with mock.patch('time.time', return_value=now_plus_6):
|
||||||
|
self.assertEqual(tester.test1(), None)
|
||||||
|
self.assertEqual(tester.test2(), True)
|
||||||
Reference in New Issue
Block a user