From 1d4da3acfde3d3b200b12d1baec6de1b276a37e9 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Mon, 20 Mar 2017 02:55:52 -0400 Subject: [PATCH 01/12] Bump version to 0.1.3 dev --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b537b5ec..0e91085f 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='ring_doorbell', packages=['ring_doorbell'], - version='0.1.2', + version='0.1.3', description='A Python library to communicate with Ring' + ' Door Bell (https://ring.com/)', author='Marcelo Moreira de Mello', From e033f9ffcc574d14fad7e7e4b3e394f647aca21d Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Fri, 31 Mar 2017 02:36:33 -0400 Subject: [PATCH 02/12] Make session token reusable (#36) * Make session token reusable across multiple object instances via cache file. With this patch, we can have more than one Ring object pointing to the same cache file to share credentials to avoid multiple authentications. * Added unittest for utils.py * Make sure if a different username is used, a new cache should be initialized. --- ring_doorbell/__init__.py | 133 +++++++++++++++++++++++++------------- ring_doorbell/const.py | 11 ++++ ring_doorbell/utils.py | 29 +++++++-- tests/test_ring.py | 22 ++++--- tests/test_ring_utils.py | 49 ++++++++++++++ 5 files changed, 182 insertions(+), 62 deletions(-) create mode 100644 tests/test_ring_utils.py diff --git a/ring_doorbell/__init__.py b/ring_doorbell/__init__.py index d6ebf6eb..4d875044 100644 --- a/ring_doorbell/__init__.py +++ b/ring_doorbell/__init__.py @@ -14,9 +14,10 @@ import pytz from ring_doorbell.utils import ( - _locator, _clean_cache, _save_cache, _read_cache) + _locator, _exists_cache, _save_cache, _read_cache) from ring_doorbell.const import ( - API_VERSION, API_URI, CHIMES_ENDPOINT, CHIME_VOL_MIN, CHIME_VOL_MAX, + API_VERSION, API_URI, CACHE_ATTRS, CACHE_FILE, CHIMES_ENDPOINT, + CHIME_VOL_MIN, CHIME_VOL_MAX, DEVICES_ENDPOINT, DOORBELLS_ENDPOINT, DOORBELL_VOL_MIN, DOORBELL_VOL_MAX, DOORBELL_EXISTING_TYPE, DINGS_ENDPOINT, FILE_EXISTS, HEADERS, LINKED_CHIMES_ENDPOINT, LIVE_STREAMING_ENDPOINT, @@ -33,11 +34,10 @@ class Ring(object): """A Python Abstraction object to Ring Door Bell.""" def __init__(self, username, password, debug=False, persist_token=False, - push_token_notify_url="http://localhost/"): + push_token_notify_url="http://localhost/", reuse_session=True, + cache_file=CACHE_FILE): """Initialize the Ring object.""" - self.features = None self.is_connected = None - self._id = None self.token = None self.params = None self._persist_token = persist_token @@ -49,9 +49,50 @@ def __init__(self, username, password, debug=False, persist_token=False, self.session = requests.Session() self.session.auth = (self.username, self.password) - self._authenticate() + self.cache = CACHE_ATTRS + self.cache['account'] = self.username + self.cache_file = cache_file + self._reuse_session = reuse_session - def _authenticate(self, attempts=RETRY_TOKEN): + # tries to re-use old session + if self._reuse_session: + self.cache['token'] = self.token + self._process_cached_session() + else: + self._authenticate() + + def _process_cached_session(self): + """Process cache_file to reuse token instead.""" + if _exists_cache(self.cache_file): + self.cache = _read_cache(self.cache_file) + + # if self.cache['token'] is None, the cache file was corrupted. + # of if self.cache['account'] does not match with self.username + # In both cases, a new auth token is required. + if (self.cache['token'] is None) or \ + (self.cache['account'] is None) or \ + (self.cache['account'] != self.username): + self._authenticate() + else: + # we need to set the self.token and self.params + # to make use of the self.query() method + self.token = self.cache['token'] + self.params = {'api_version': API_VERSION, + 'auth_token': self.token} + + # test if token from cache_file is still valid and functional + # if not, it should continue to get a new auth token + url = API_URI + DEVICES_ENDPOINT + req = self.query(url, raw=True) + if req.status_code == 200: + self._authenticate(session=req) + else: + self._authenticate() + else: + # first time executing, so we have to create a cache file + self._authenticate() + + def _authenticate(self, attempts=RETRY_TOKEN, session=None): """Authenticate user against Ring API.""" url = API_URI + NEW_SESSION_ENDPOINT @@ -59,17 +100,25 @@ def _authenticate(self, attempts=RETRY_TOKEN): while loop <= attempts: loop += 1 try: - req = self.session.post((url), data=POST_DATA, headers=HEADERS) + if session is None: + req = self.session.post((url), + data=POST_DATA, + headers=HEADERS) + else: + req = session except: raise # if token is expired, refresh credentials and try again - if req.status_code == 201: - data = req.json().get('profile') - self.features = data.get('features') - self._id = data.get('id') + if req.status_code == 200 or req.status_code == 201: + + # the only way to get a JSON with token is via POST, + # so we need a special conditional for 201 code + if req.status_code == 201: + data = req.json().get('profile') + self.token = data.get('authentication_token') + self.is_connected = True - self.token = data.get('authentication_token') self.params = {'api_version': API_VERSION, 'auth_token': self.token} @@ -80,6 +129,13 @@ def _authenticate(self, attempts=RETRY_TOKEN): self._push_token_notify_url req = self.session.put((url), headers=HEADERS, data=PERSIST_TOKEN_DATA) + + # update token if reuse_session is True + if self._reuse_session: + self.cache['account'] = self.username + self.cache['token'] = self.token + + _save_cache(self.cache, self.cache_file) return True self.is_connected = False @@ -146,14 +202,6 @@ def query(self, _LOGGER.debug("%s", MSG_GENERIC_FAIL) return response - @property - def has_subscription(self): - """Return if account has subscription.""" - try: - return self.features.get('subscriptions_enabled') - except AttributeError: - return NOT_FOUND - @property def devices(self): """Return all devices.""" @@ -203,13 +251,12 @@ class RingGeneric(object): def __init__(self): """Initialize Ring Generic.""" self._attrs = None + self._ring = None self.debug = None self.family = None self.name = None # alerts notifications - self._alert_cache = None - self.alert = None self.alert_expires_at = None def __repr__(self): @@ -221,26 +268,26 @@ def update(self): self._get_attrs() self._update_alert() + @property + def alert(self): + """Return alert attribute.""" + return self._ring.cache['alerts'] + + @alert.setter + def alert(self, value): + """Set attribute to alert.""" + self._ring.cache['alerts'] = value + _save_cache(self._ring.cache, self._ring.cache_file) + return True + def _update_alert(self): """Verify if alert received is still valid.""" + # alert is no longer valid if self.alert and self.alert_expires_at: if datetime.now() >= self.alert_expires_at: self.alert = None self.alert_expires_at = None - elif self._alert_cache: - aux = _read_cache(self._alert_cache) - if ((isinstance(aux, dict)) and - ('now' in aux) and - ('expires_in' in aux)): - aux_expires_at = datetime.fromtimestamp( - aux.get('now') + aux.get('expires_in')) - - # verify if pickle object is still valid - if datetime.now() <= aux_expires_at: - self.alert = aux - self.alert_expires_at = aux_expires_at - else: - _save_cache(None, self._alert_cache) + _save_cache(self._ring.cache, self._ring.cache_file) def _get_attrs(self): """Return attributes.""" @@ -371,14 +418,8 @@ def battery_life(self): value = 100 return value - def check_alerts(self, cache=None): + def check_alerts(self): """Return JSON when motion or ring is detected.""" - # save alerts attributes to an external pickle file - # when multiple resources are checking for alerts - if cache: - _clean_cache(cache) - self._alert_cache = cache - url = API_URI + DINGS_ENDPOINT self.update() @@ -393,8 +434,8 @@ def check_alerts(self, cache=None): self.alert_expires_at = datetime.fromtimestamp(timestamp) # save to a pickle data - if self._alert_cache: - _save_cache(self.alert, self._alert_cache) + if self.alert: + _save_cache(self._ring.cache, self._ring.cache_file) return True return None diff --git a/ring_doorbell/const.py b/ring_doorbell/const.py index 777d4e3b..084ccfe1 100644 --- a/ring_doorbell/const.py +++ b/ring_doorbell/const.py @@ -1,6 +1,7 @@ # coding: utf-8 # vim:sw=4:ts=4:et: """Constants.""" +import os from uuid import uuid4 as uuid HEADERS = {'Content-Type': 'application/x-www-form-urlencoded; charset: UTF-8', @@ -10,6 +11,16 @@ # number of attempts to refresh token RETRY_TOKEN = 3 +# default suffix for session cache file +CACHE_ATTRS = {'account': None, 'alerts': None, 'token': None} + +try: + CACHE_FILE = os.path.join(os.getenv("HOME"), + '.ring_doorbell-session.cache') +except (AttributeError, TypeError): + CACHE_FILE = os.path.join('.', '.ring_doorbell-session.cache') + + # code when item was not found NOT_FOUND = -1 diff --git a/ring_doorbell/utils.py b/ring_doorbell/utils.py index f396a9d4..24797781 100644 --- a/ring_doorbell/utils.py +++ b/ring_doorbell/utils.py @@ -2,7 +2,7 @@ # vim:sw=4:ts=4:et: """Python Ring Doorbell utils.""" import os -from ring_doorbell.const import NOT_FOUND +from ring_doorbell.const import CACHE_ATTRS, NOT_FOUND try: import cPickle as pickle @@ -23,10 +23,19 @@ def _clean_cache(filename): """Remove filename if pickle version mismatch.""" try: if os.path.isfile(filename): - _read_cache(filename) - except ValueError: - os.remove(filename) - return True + os.remove(filename) + except: + raise + + # initialize cache since file was removed + initial_cache_data = CACHE_ATTRS + _save_cache(initial_cache_data, filename) + return initial_cache_data + + +def _exists_cache(filename): + """Check if filename exists and if is pickle object.""" + return bool(os.path.isfile(filename)) def _save_cache(data, filename): @@ -43,6 +52,14 @@ def _read_cache(filename): """Read data from a pickle file.""" try: if os.path.isfile(filename): - return pickle.load(open(filename, 'rb')) + data = pickle.load(open(filename, 'rb')) + + # make sure pickle obj has the expected defined keys + # if not reinitialize cache + if data.keys() != CACHE_ATTRS.keys(): + raise EOFError + return data + except EOFError: + return _clean_cache(filename) except: raise diff --git a/tests/test_ring.py b/tests/test_ring.py index 05049433..ac2912f3 100644 --- a/tests/test_ring.py +++ b/tests/test_ring.py @@ -11,7 +11,7 @@ USERNAME = 'foo' PASSWORD = 'bar' -ALERT_CACHE_DB = 'tests/cache.db' +CACHE = 'tests/cache.db' def mocked_requests_get(*args, **kwargs): @@ -245,9 +245,9 @@ def test_basic_attributes(self, get_mock, post_mock): """Test the Ring class and methods.""" from ring_doorbell import Ring - myring = Ring(USERNAME, PASSWORD) + myring = Ring(USERNAME, PASSWORD, cache_file=CACHE) self.assertTrue(myring.is_connected) - self.assertIsInstance(myring.features, dict) + self.assertIsInstance(myring.cache, dict) self.assertFalse(myring.debug) self.assertEqual(1, len(myring.chimes)) self.assertEqual(2, len(myring.doorbells)) @@ -264,7 +264,7 @@ def test_chime_attributes(self, get_mock, post_mock): """Test the Ring Chime class and methods.""" from ring_doorbell import Ring - myring = Ring(USERNAME, PASSWORD) + myring = Ring(USERNAME, PASSWORD, cache_file=CACHE) dev = myring.chimes[0] self.assertEqual('123 Main St', dev.address) @@ -285,7 +285,7 @@ def test_doorbell_attributes(self, get_mock, post_mock): """Test the Ring DoorBell class and methods.""" from ring_doorbell import Ring - myring = Ring(USERNAME, PASSWORD, persist_token=True) + myring = Ring(USERNAME, PASSWORD, cache_file=CACHE, persist_token=True) for dev in myring.doorbells: if not dev.shared: self.assertEqual('Front Door', dev.name) @@ -309,7 +309,7 @@ def test_shared_doorbell_attributes(self, get_mock, post_mock): """Test the Ring Shared DoorBell class and methods.""" from ring_doorbell import Ring - myring = Ring(USERNAME, PASSWORD, persist_token=True) + myring = Ring(USERNAME, PASSWORD, cache_file=CACHE, persist_token=True) for dev in myring.doorbells: if dev.shared: self.assertEqual(987653, dev.account_id) @@ -321,6 +321,8 @@ def test_shared_doorbell_attributes(self, get_mock, post_mock): self.assertEqual(5, dev.volume) self.assertEqual('Digital', dev.existing_doorbell_type) + os.remove(CACHE) + class TestRingDoorBellAlerts(unittest.TestCase): """Test the Ring DoorBell alerts.""" @@ -331,16 +333,16 @@ def test_doorbell_alerts(self, get_mock, post_mock): """Test the Ring DoorBell alerts.""" from ring_doorbell import Ring - myring = Ring(USERNAME, PASSWORD, persist_token=True) + myring = Ring(USERNAME, PASSWORD, cache_file=CACHE, persist_token=True) for dev in myring.doorbells: self.assertEqual('America/New_York', dev.timezone) # call alerts - dev.check_alerts(cache=ALERT_CACHE_DB) + dev.check_alerts() self.assertIsInstance(dev.alert, dict) self.assertIsInstance(dev.alert_expires_at, datetime) self.assertTrue(datetime.now() <= dev.alert_expires_at) - self.assertIsNotNone(dev._alert_cache) + self.assertIsNotNone(dev._ring.cache_file) - os.remove(ALERT_CACHE_DB) + os.remove(CACHE) diff --git a/tests/test_ring_utils.py b/tests/test_ring_utils.py new file mode 100644 index 00000000..4dfe64d5 --- /dev/null +++ b/tests/test_ring_utils.py @@ -0,0 +1,49 @@ +"""The tests utils.py for the Ring platform.""" +import os +import unittest +from ring_doorbell.utils import ( + _locator, _clean_cache, _exists_cache, _save_cache, _read_cache) +from ring_doorbell.const import CACHE_ATTRS + +CACHE = 'tests/cache.db' +FAKE = 'tests/fake.db' +DATA = {'key': 'value'} + + +class TestUtils(unittest.TestCase): + """Test utils.py.""" + + def test_locator(self): + """Test _locator method.""" + self.assertEquals(-1, _locator([DATA], 'key', 'bar')) + self.assertEquals(0, _locator([DATA], 'key', 'value')) + + def test_initiliaze_clean_cache(self): + """Test _clean_cache method.""" + self.assertTrue(_save_cache(DATA, CACHE)) + self.assertIsInstance(_clean_cache(CACHE), dict) + os.remove(CACHE) + + def test_exists_cache(self): + """Test _exists_cache method.""" + self.assertTrue(_save_cache(DATA, CACHE)) + self.assertTrue(_exists_cache(CACHE)) + os.remove(CACHE) + + def test_read_cache(self): + """Test _read_cache method.""" + self.assertTrue(_save_cache(DATA, CACHE)) + self.assertIsInstance(_read_cache(CACHE), dict) + os.remove(CACHE) + + def test_read_cache_eoferror(self): + """Test _read_cache method.""" + open(CACHE, 'a').close() + self.assertIsInstance(_read_cache(CACHE), dict) + os.remove(CACHE) + + def test_read_cache_dict(self): + """Test _read_cache with expected dict.""" + self.assertTrue(_save_cache(CACHE_ATTRS, CACHE)) + self.assertIsInstance(_read_cache(CACHE), dict) + os.remove(CACHE) From 5ba19d3a4487c4d2ffd2081635ae54d0eb411d33 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Fri, 31 Mar 2017 02:41:50 -0400 Subject: [PATCH 03/12] Added Python 3.6 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 0e91085f..98143dd1 100644 --- a/setup.py +++ b/setup.py @@ -29,6 +29,7 @@ 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Topic :: Home Automation', 'Topic :: Software Development :: Libraries :: Python Modules' ], From db396edabea597ecb5467cea686c323231b01f2c Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Fri, 31 Mar 2017 02:57:56 -0400 Subject: [PATCH 04/12] Bump dev branch to verson 0.1.4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 98143dd1..4708b7cb 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='ring_doorbell', packages=['ring_doorbell'], - version='0.1.3', + version='0.1.4', description='A Python library to communicate with Ring' + ' Door Bell (https://ring.com/)', author='Marcelo Moreira de Mello', From c59bcb05a40ea3525a3704bca024f7d9ac4b4e13 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Sun, 23 Apr 2017 22:14:58 -0400 Subject: [PATCH 05/12] Combining coverage and tox with --cov-report --- requirements_tests.txt | 1 + tox.ini | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_tests.txt b/requirements_tests.txt index e07ebba6..f871e407 100644 --- a/requirements_tests.txt +++ b/requirements_tests.txt @@ -3,4 +3,5 @@ flake8 mock pylint pytest +pytest-cov tox diff --git a/tox.ini b/tox.ini index 32f31eda..3cfc590b 100644 --- a/tox.ini +++ b/tox.ini @@ -8,8 +8,7 @@ setenv = whitelist_externals = /usr/bin/env install_command = /usr/bin/env LANG=C.UTF-8 pip install {opts} {packages} commands = - py.test --verbose --color=auto --duration=0 - coverage run --source=ring_doorbell setup.py test + py.test --basetemp={envtmpdir} --cov --cov-report term-missing deps = -r{toxinidir}/requirements.txt -r{toxinidir}/requirements_tests.txt From 04532c7e80ecb9b6b1a6c01c59063ec14c337622 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Mon, 24 Apr 2017 23:03:11 -0400 Subject: [PATCH 06/12] Added contributing.rst instructions --- CONTRIBUTING.rst | 81 ++++++++++++++++++++++++++++++++++++++++++++++++ MANIFEST.in | 8 ++++- README.rst | 14 ++++++--- 3 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 CONTRIBUTING.rst diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 00000000..c47a15c6 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,81 @@ +============ +Contributing +============ + +Contributions are welcome and very appreciated!! +Keep in mind that every little contribution helps, don't matter what. + +Types of Contributions +---------------------- + +Report Bugs +~~~~~~~~~~~ + +Report bugs at https://github.com/tchellomello/python-ring-doorbell/issues + +If you are reporting a bug, please include: + +* Ring product and firmware version +* Steps to reproduce the issue +* Anything you judge interesting for the troubleshooting + +Fix Bugs +~~~~~~~~ + +Look through the GitHub issues for bugs. Anything tagged with "bug" +and "help wanted" is open to whoever wants to implement it. + +Implement Features +~~~~~~~~~~~~~~~~~~ + +Look through the GitHub issues for features. Anything tagged with "enhancement" +and "help wanted" is open to whoever wants to implement it. + +Documentation +~~~~~~~~~~~~~ + +Documentation is always good. So please feel free to add any documentation +you think will help our users. + +Request Features +~~~~~~~~~~~~~~~~ + +File an issue at https://github.com/tchellomello/python-ring-doorbell/issues. + +Get Started! +------------ + +Ready to contribute? Here's how to set up `python-ring_doorbell` for local development. + +1. Fork the `python-ring-doorbel` repo on GitHub. +2. Clone your fork locally:: + + $ git clone git@github.com:your_name_here/python-ring-doorbell.git + +3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: + + $ mkvirtualenv python-ring-doorbell + $ cd python-ring-doorbell/ + $ python setup.py develop + $ pip install -r requirements_tests.txt + +4. Create a branch for local development:: + + $ git checkout -b name-of-your-bugfix-or-feature + + Now you can make your changes locally. + +5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: + + $ tox -r + +6. Commit your changes and push your branch to GitHub:: + + $ git add . + $ git commit -m "Your detailed description of your changes." + $ git push origin name-of-your-bugfix-or-feature + +7. Submit a pull request through the GitHub website. + + +Thank you!! diff --git a/MANIFEST.in b/MANIFEST.in index bb3ec5f0..c07abee0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,7 @@ -include README.md +include CONTRIBUTING.rst +include LICENSE +include README.rst + +recursive-include tests * +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] diff --git a/README.rst b/README.rst index 81078485..67148fc5 100644 --- a/README.rst +++ b/README.rst @@ -51,7 +51,7 @@ Initializing your Ring object Listing devices linked to your account ------------------------------------------- +-------------------------------------- .. code-block:: python @@ -69,7 +69,7 @@ Listing devices linked to your account [] Playing with the attributes --------------------------------- +--------------------------- .. code-block:: python for dev in list(myring.chimes + myring.doorbells): @@ -113,7 +113,7 @@ Showing door bell events Downloading the last video triggered by ding -------------------------------------------- +-------------------------------------------- .. code-block:: python doorbell = myring.doorbells[0] @@ -124,12 +124,18 @@ Downloading the last video triggered by ding Displaying the last video capture URL -------------------------------------------- +------------------------------------- .. code-block:: python print(doorbell.recording_url(doorbell.last_recording_id)) 'https://ring-transcoded-videos.s3.amazonaws.com/99999999.mp4?X-Amz-Expires=3600&X-Amz-Date=20170313T232537Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=TOKEN_SECRET/us-east-1/s3/aws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=secret' + +How to contribute +----------------- +See CONTRIBUTING.rst + + Credits && Thanks ----------------- From 5c366e9f22560afcb7e3ffcff209108198cb5330 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Tue, 25 Apr 2017 00:11:36 -0400 Subject: [PATCH 07/12] Cleanup tests (#39) * Cleaning up tests using tearDown() method * Increased coverage tests --- ring_doorbell/utils.py | 2 -- tests/test_ring.py | 22 ++++++++++++++++++---- tests/test_ring_utils.py | 28 +++++++++++++++++++++++----- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/ring_doorbell/utils.py b/ring_doorbell/utils.py index 24797781..ade18a66 100644 --- a/ring_doorbell/utils.py +++ b/ring_doorbell/utils.py @@ -61,5 +61,3 @@ def _read_cache(filename): return data except EOFError: return _clean_cache(filename) - except: - raise diff --git a/tests/test_ring.py b/tests/test_ring.py index ac2912f3..3cc3b8b1 100644 --- a/tests/test_ring.py +++ b/tests/test_ring.py @@ -279,6 +279,15 @@ def test_chime_attributes(self, get_mock, post_mock): class TestRingDoorBell(unittest.TestCase): """Test the Ring DoorBell object.""" + def cleanup(self): + """Cleanup any data created from the tests.""" + if os.path.isfile(CACHE): + os.remove(CACHE) + + def tearDown(self): + """Stop everything started.""" + self.cleanup() + @mock.patch('requests.Session.get', side_effect=mocked_requests_get) @mock.patch('requests.Session.post', side_effect=mocked_requests_get) def test_doorbell_attributes(self, get_mock, post_mock): @@ -321,12 +330,19 @@ def test_shared_doorbell_attributes(self, get_mock, post_mock): self.assertEqual(5, dev.volume) self.assertEqual('Digital', dev.existing_doorbell_type) - os.remove(CACHE) - class TestRingDoorBellAlerts(unittest.TestCase): """Test the Ring DoorBell alerts.""" + def cleanup(self): + """Cleanup any data created from the tests.""" + if os.path.isfile(CACHE): + os.remove(CACHE) + + def tearDown(self): + """Stop everything started.""" + self.cleanup() + @mock.patch('requests.Session.get', side_effect=mocked_requests_get) @mock.patch('requests.Session.post', side_effect=mocked_requests_get) def test_doorbell_alerts(self, get_mock, post_mock): @@ -344,5 +360,3 @@ def test_doorbell_alerts(self, get_mock, post_mock): self.assertIsInstance(dev.alert_expires_at, datetime) self.assertTrue(datetime.now() <= dev.alert_expires_at) self.assertIsNotNone(dev._ring.cache_file) - - os.remove(CACHE) diff --git a/tests/test_ring_utils.py b/tests/test_ring_utils.py index 4dfe64d5..10e056ba 100644 --- a/tests/test_ring_utils.py +++ b/tests/test_ring_utils.py @@ -1,5 +1,6 @@ """The tests utils.py for the Ring platform.""" import os +import sys import unittest from ring_doorbell.utils import ( _locator, _clean_cache, _exists_cache, _save_cache, _read_cache) @@ -13,6 +14,15 @@ class TestUtils(unittest.TestCase): """Test utils.py.""" + def cleanup(self): + """Cleanup any data created from the tests.""" + if os.path.isfile(CACHE): + os.remove(CACHE) + + def tearDown(self): + """Stop everything started.""" + self.cleanup() + def test_locator(self): """Test _locator method.""" self.assertEquals(-1, _locator([DATA], 'key', 'bar')) @@ -22,28 +32,36 @@ def test_initiliaze_clean_cache(self): """Test _clean_cache method.""" self.assertTrue(_save_cache(DATA, CACHE)) self.assertIsInstance(_clean_cache(CACHE), dict) - os.remove(CACHE) + self.cleanup() def test_exists_cache(self): """Test _exists_cache method.""" self.assertTrue(_save_cache(DATA, CACHE)) self.assertTrue(_exists_cache(CACHE)) - os.remove(CACHE) + self.cleanup() def test_read_cache(self): """Test _read_cache method.""" self.assertTrue(_save_cache(DATA, CACHE)) self.assertIsInstance(_read_cache(CACHE), dict) - os.remove(CACHE) + self.cleanup() def test_read_cache_eoferror(self): """Test _read_cache method.""" open(CACHE, 'a').close() self.assertIsInstance(_read_cache(CACHE), dict) - os.remove(CACHE) + self.cleanup() def test_read_cache_dict(self): """Test _read_cache with expected dict.""" self.assertTrue(_save_cache(CACHE_ATTRS, CACHE)) self.assertIsInstance(_read_cache(CACHE), dict) - os.remove(CACHE) + self.cleanup() + + def test_general_exceptions(self): + """Test exception triggers on utils.py""" + self.assertRaises(TypeError, _clean_cache, True) + if sys.version_info.major == 2: + self.assertRaises(TypeError, _read_cache, True) + else: + self.assertRaises(OSError, _read_cache, True) From aca86835cea6fc260d459883559a9c46c87295ca Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Wed, 26 Apr 2017 14:01:07 -0400 Subject: [PATCH 08/12] Updated pip on readme --- README.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 67148fc5..43c8a78a 100644 --- a/README.rst +++ b/README.rst @@ -27,8 +27,7 @@ Installation .. code-block:: bash # Installing from PyPi - $ pip install ring_doorbell #python 2.7 - $ pip3 install ring_doorbell #python 3.x + $ pip install ring_doorbell # Installing latest development $ pip3 install \ From b0e67a2c1a33210cf0e35a1e5e9e87af868c9d5a Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Sun, 30 Apr 2017 17:37:14 -0400 Subject: [PATCH 09/12] Fixes #40 - Dropped has_subscription attr (#41) --- README.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.rst b/README.rst index 43c8a78a..68457496 100644 --- a/README.rst +++ b/README.rst @@ -45,10 +45,6 @@ Initializing your Ring object myring.is_connected True - myring.has_subscription - True - - Listing devices linked to your account -------------------------------------- From 6bd08364f917db7383edc7f7d8e345ab0ce470bc Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Sun, 30 Apr 2017 17:41:13 -0400 Subject: [PATCH 10/12] Force new pickle object to be create in case of version incompability --- ring_doorbell/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ring_doorbell/utils.py b/ring_doorbell/utils.py index ade18a66..46cf5ab0 100644 --- a/ring_doorbell/utils.py +++ b/ring_doorbell/utils.py @@ -59,5 +59,5 @@ def _read_cache(filename): if data.keys() != CACHE_ATTRS.keys(): raise EOFError return data - except EOFError: + except (EOFError, ValueError): return _clean_cache(filename) From d2ffc1a3d47a37046b63b4953d8c0402e9b11e92 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Sun, 30 Apr 2017 18:29:22 -0400 Subject: [PATCH 11/12] Update test_ring_utils.py --- tests/test_ring_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_ring_utils.py b/tests/test_ring_utils.py index 50ff25a5..c1cef687 100644 --- a/tests/test_ring_utils.py +++ b/tests/test_ring_utils.py @@ -64,4 +64,5 @@ def test_general_exceptions(self): if sys.version_info.major == 2: self.assertRaises(TypeError, _read_cache, True) else: - self.assertRaises(OSError, _read_cache, True) \ No newline at end of file + self.assertRaises(OSError, _read_cache, True) + From 42f6664b84f7a0a7243a9eef5e9c3391f4e9aac2 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Sun, 30 Apr 2017 18:31:28 -0400 Subject: [PATCH 12/12] Makes lint happy --- tests/test_ring_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_ring_utils.py b/tests/test_ring_utils.py index c1cef687..10e056ba 100644 --- a/tests/test_ring_utils.py +++ b/tests/test_ring_utils.py @@ -65,4 +65,3 @@ def test_general_exceptions(self): self.assertRaises(TypeError, _read_cache, True) else: self.assertRaises(OSError, _read_cache, True) -