Skip to content

Commit ad099b7

Browse files
committed
Added cache. Actually fixes MisterWil#27. Adds feature for MisterWil#20. Bump to version
0.13.1.
1 parent 7179dc2 commit ad099b7

17 files changed

+156
-49
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,4 @@ ENV/
9393
/.project
9494
/.pydevproject
9595
.settings/
96+
*.pickle

abodepy/__init__.py

+54-17
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
import json
2525
import logging
26-
import uuid
26+
import os
2727
import requests
2828
from requests.exceptions import RequestException
2929

@@ -40,6 +40,7 @@
4040
import abodepy.devices.alarm as ALARM
4141
import abodepy.helpers.constants as CONST
4242
import abodepy.helpers.errors as ERROR
43+
import abodepy.utils as UTILS
4344

4445
_LOGGER = logging.getLogger(__name__)
4546

@@ -49,15 +50,14 @@ class Abode():
4950

5051
def __init__(self, username=None, password=None,
5152
auto_login=False, get_devices=False, get_automations=False,
52-
custom_uuid=None):
53+
cache_path=CONST.CACHE_PATH, disable_cache=False):
5354
"""Init Abode object."""
54-
self._username = username
55-
self._password = password
5655
self._session = None
5756
self._token = None
5857
self._panel = None
5958
self._user = None
60-
self._uuid = custom_uuid
59+
self._cache_path = cache_path
60+
self._disable_cache = disable_cache
6161

6262
self._event_controller = AbodeEventController(self,
6363
url=CONST.SOCKETIO_URL)
@@ -71,11 +71,29 @@ def __init__(self, username=None, password=None,
7171
# Create a requests session to persist the cookies
7272
self._session = requests.session()
7373

74-
if not self._uuid:
75-
self._uuid = str(uuid.uuid1())
74+
# Create a new cache template
75+
self._cache = {
76+
CONST.ID: None,
77+
CONST.PASSWORD: None,
78+
CONST.UUID: UTILS.gen_uuid()
79+
}
80+
81+
# Load and merge an existing cache
82+
if not disable_cache:
83+
self._load_cache()
84+
85+
# If the username and password were passed in, update
86+
# the cache and save
87+
if username:
88+
self._cache[CONST.ID] = username
89+
90+
if password:
91+
self._cache[CONST.PASSWORD] = password
92+
93+
self._save_cache()
7694

77-
if (self._username is not None and
78-
self._password is not None and
95+
if (self._cache[CONST.ID] is not None and
96+
self._cache[CONST.PASSWORD] is not None and
7997
auto_login):
8098
self.login()
8199

@@ -88,22 +106,26 @@ def __init__(self, username=None, password=None,
88106
def login(self, username=None, password=None):
89107
"""Explicit Abode login."""
90108
if username is not None:
91-
self._username = username
109+
self._cache[CONST.ID] = username
92110
if password is not None:
93-
self._password = password
111+
self._cache[CONST.PASSWORD] = password
94112

95-
if self._username is None or not isinstance(self._username, str):
113+
if (self._cache[CONST.ID] is None or
114+
not isinstance(self._cache[CONST.ID], str)):
96115
raise AbodeAuthenticationException(ERROR.USERNAME)
97116

98-
if self._password is None or not isinstance(self._password, str):
117+
if (self._cache[CONST.PASSWORD] is None or
118+
not isinstance(self._cache[CONST.PASSWORD], str)):
99119
raise AbodeAuthenticationException(ERROR.PASSWORD)
100120

121+
self._save_cache()
122+
101123
self._token = None
102124

103125
login_data = {
104-
'id': self._username,
105-
'password': self._password,
106-
'uuid': self._uuid
126+
CONST.ID: self._cache[CONST.ID],
127+
CONST.PASSWORD: self._cache[CONST.PASSWORD],
128+
CONST.UUID: self._cache[CONST.UUID]
107129
}
108130

109131
response = self._session.post(CONST.LOGIN_URL, data=login_data)
@@ -426,14 +448,29 @@ def events(self):
426448
@property
427449
def uuid(self):
428450
"""Get the UUID."""
429-
return self._uuid
451+
return self._cache[CONST.UUID]
430452

431453
def _get_session(self):
432454
# Perform a generic update so we know we're logged in
433455
self.send_request("get", CONST.PANEL_URL)
434456

435457
return self._session
436458

459+
def _load_cache(self):
460+
"""Load existing cache and merge for updating if required."""
461+
if not self._disable_cache and os.path.exists(self._cache_path):
462+
_LOGGER.debug("Cache found at: %s", self._cache_path)
463+
loaded_cache = UTILS.load_cache(self._cache_path)
464+
465+
UTILS.update(self._cache, loaded_cache)
466+
467+
self._save_cache()
468+
469+
def _save_cache(self):
470+
"""Trigger a cache save."""
471+
if not self._disable_cache:
472+
UTILS.save_cache(self._cache, self._cache_path)
473+
437474

438475
def _new_sensor(device_json, abode):
439476
statuses = device_json.get(CONST.STATUSES_KEY, {})

abodepy/__main__.py

+24-5
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,18 @@ def get_arguments():
7070
parser.add_argument(
7171
'-u', '--username',
7272
help='Username',
73-
required=True)
73+
required=False)
7474

7575
parser.add_argument(
7676
'-p', '--password',
7777
help='Password',
78-
required=True)
78+
required=False)
79+
80+
parser.add_argument(
81+
'--cache',
82+
metavar='pickle_file',
83+
help='Create/update/use a pickle cache for the username and password.',
84+
required=False)
7985

8086
parser.add_argument(
8187
'--mode',
@@ -205,11 +211,24 @@ def call():
205211

206212
abode = None
207213

214+
if not args.cache:
215+
if not args.username or not args.password:
216+
raise Exception("Please supply a cache or username and password.")
217+
208218
try:
209219
# Create abodepy instance.
210-
abode = abodepy.Abode(username=args.username,
211-
password=args.password,
212-
get_devices=True)
220+
if args.cache and args.username and args.password:
221+
abode = abodepy.Abode(username=args.username,
222+
password=args.password,
223+
get_devices=True,
224+
cache_path=args.cache)
225+
elif args.cache and not (not args.username or not args.password):
226+
abode = abodepy.Abode(get_devices=True,
227+
cache_path=args.cache)
228+
else:
229+
abode = abodepy.Abode(username=args.username,
230+
password=args.password,
231+
get_devices=True)
213232

214233
# Output current mode.
215234
if args.mode:

abodepy/helpers/constants.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
MAJOR_VERSION = 0
55
MINOR_VERSION = 13
6-
PATCH_VERSION = '0'
6+
PATCH_VERSION = '1'
77

88
__version__ = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)
99

@@ -38,6 +38,12 @@
3838

3939
PYPI_URL = 'https://pypi.python.org/pypi/{}'.format(PROJECT_PACKAGE_NAME)
4040

41+
CACHE_PATH = './abode.pickle'
42+
43+
ID = 'id'
44+
PASSWORD = 'password'
45+
UUID = 'uuid'
46+
4147
# URLS
4248
BASE_URL = 'https://my.goabode.com/'
4349

abodepy/utils.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Abodepy utility methods."""
2+
import pickle
3+
import uuid
4+
5+
6+
def save_cache(data, filename):
7+
"""Save cookies to a file."""
8+
with open(filename, 'wb') as handle:
9+
pickle.dump(data, handle)
10+
11+
12+
def load_cache(filename):
13+
"""Load cookies from a file."""
14+
with open(filename, 'rb') as handle:
15+
return pickle.load(handle)
16+
17+
18+
def gen_uuid():
19+
"""Generate a new Abode UUID."""
20+
return str(uuid.uuid1())
21+
22+
23+
def update(dct, dct_merge):
24+
"""Recursively merge dicts."""
25+
for key, value in dct_merge.items():
26+
if key in dct and isinstance(dct[key], dict):
27+
dct[key] = update(dct[key], value)
28+
else:
29+
dct[key] = value
30+
return dct

tests/test_abode.py

+18-15
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ class TestAbode(unittest.TestCase):
2929

3030
def setUp(self):
3131
"""Set up Abode module."""
32-
self.abode_no_cred = abodepy.Abode()
32+
self.abode_no_cred = abodepy.Abode(disable_cache=True)
3333
self.abode = abodepy.Abode(username=USERNAME,
34-
password=PASSWORD)
34+
password=PASSWORD,
35+
disable_cache=True)
3536

3637
def tearDown(self):
3738
"""Clean up after test."""
@@ -41,17 +42,17 @@ def tearDown(self):
4142
def tests_initialization(self):
4243
"""Verify we can initialize abode."""
4344
# pylint: disable=protected-access
44-
self.assertEqual(self.abode._username, USERNAME)
45+
self.assertEqual(self.abode._cache[CONST.ID], USERNAME)
4546
# pylint: disable=protected-access
46-
self.assertEqual(self.abode._password, PASSWORD)
47+
self.assertEqual(self.abode._cache[CONST.PASSWORD], PASSWORD)
4748

4849
def tests_no_credentials(self):
4950
"""Check that we throw an exception when no username/password."""
5051
with self.assertRaises(abodepy.AbodeAuthenticationException):
5152
self.abode_no_cred.login()
5253

5354
# pylint: disable=protected-access
54-
self.abode_no_cred._username = USERNAME
55+
self.abode_no_cred._cache[CONST.ID] = USERNAME
5556
with self.assertRaises(abodepy.AbodeAuthenticationException):
5657
self.abode_no_cred.login()
5758

@@ -63,9 +64,9 @@ def tests_manual_login(self, m):
6364
self.abode_no_cred.login(username=USERNAME, password=PASSWORD)
6465

6566
# pylint: disable=protected-access
66-
self.assertEqual(self.abode_no_cred._username, USERNAME)
67+
self.assertEqual(self.abode_no_cred._cache[CONST.ID], USERNAME)
6768
# pylint: disable=protected-access
68-
self.assertEqual(self.abode_no_cred._password, PASSWORD)
69+
self.assertEqual(self.abode_no_cred._cache[CONST.PASSWORD], PASSWORD)
6970

7071
@requests_mock.mock()
7172
def tests_auto_login(self, m):
@@ -82,11 +83,12 @@ def tests_auto_login(self, m):
8283
abode = abodepy.Abode(username='fizz',
8384
password='buzz',
8485
auto_login=True,
85-
get_devices=False)
86+
get_devices=False,
87+
disable_cache=True)
8688

8789
# pylint: disable=W0212
88-
self.assertEqual(abode._username, 'fizz')
89-
self.assertEqual(abode._password, 'buzz')
90+
self.assertEqual(abode._cache[CONST.ID], 'fizz')
91+
self.assertEqual(abode._cache[CONST.PASSWORD], 'buzz')
9092
self.assertEqual(abode._token, MOCK.AUTH_TOKEN)
9193
self.assertEqual(abode._panel, json.loads(panel_json))
9294
self.assertEqual(abode._user, json.loads(user_json))
@@ -115,11 +117,12 @@ def tests_auto_fetch(self, m):
115117
password='buzz',
116118
auto_login=False,
117119
get_devices=True,
118-
get_automations=True)
120+
get_automations=True,
121+
disable_cache=True)
119122

120123
# pylint: disable=W0212
121-
self.assertEqual(abode._username, 'fizz')
122-
self.assertEqual(abode._password, 'buzz')
124+
self.assertEqual(abode._cache[CONST.ID], 'fizz')
125+
self.assertEqual(abode._cache[CONST.PASSWORD], 'buzz')
123126
self.assertEqual(abode._token, MOCK.AUTH_TOKEN)
124127
self.assertEqual(abode._panel, json.loads(panel_json))
125128
self.assertEqual(abode._user, json.loads(user_json))
@@ -191,8 +194,8 @@ def tests_full_setup(self, m):
191194
original_session = self.abode._session
192195

193196
# pylint: disable=W0212
194-
self.assertEqual(self.abode._username, USERNAME)
195-
self.assertEqual(self.abode._password, PASSWORD)
197+
self.assertEqual(self.abode._cache[CONST.ID], USERNAME)
198+
self.assertEqual(self.abode._cache[CONST.PASSWORD], PASSWORD)
196199
self.assertEqual(self.abode._token, auth_token)
197200
self.assertEqual(self.abode._panel, json.loads(panel_json))
198201
self.assertEqual(self.abode._user, json.loads(user_json))

tests/test_alarm.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ class TestAlarm(unittest.TestCase):
2323
def setUp(self):
2424
"""Set up Abode module."""
2525
self.abode = abodepy.Abode(username=USERNAME,
26-
password=PASSWORD)
26+
password=PASSWORD,
27+
disable_cache=True)
2728

2829
def tearDown(self):
2930
"""Clean up after test."""

tests/test_automation.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ class TestDevice(unittest.TestCase):
2525
def setUp(self):
2626
"""Set up Abode module."""
2727
self.abode = abodepy.Abode(username=USERNAME,
28-
password=PASSWORD)
28+
password=PASSWORD,
29+
disable_cache=True)
2930

3031
def tearDown(self):
3132
"""Clean up after test."""

tests/test_binary_sensor.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ class TestBinarySensors(unittest.TestCase):
2828
def setUp(self):
2929
"""Set up Abode module."""
3030
self.abode = abodepy.Abode(username=USERNAME,
31-
password=PASSWORD)
31+
password=PASSWORD,
32+
disable_cache=True)
3233

3334
def tearDown(self):
3435
"""Clean up after test."""

tests/test_camera.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ class TestCamera(unittest.TestCase):
2323
def setUp(self):
2424
"""Set up Abode module."""
2525
self.abode = abodepy.Abode(username=USERNAME,
26-
password=PASSWORD)
26+
password=PASSWORD,
27+
disable_cache=True)
2728

2829
def tearDown(self):
2930
"""Clean up after test."""

tests/test_device.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ class TestDevice(unittest.TestCase):
4444
def setUp(self):
4545
"""Set up Abode module."""
4646
self.abode = abodepy.Abode(username=USERNAME,
47-
password=PASSWORD)
47+
password=PASSWORD,
48+
disable_cache=True)
4849

4950
def tearDown(self):
5051
"""Clean up after test."""

tests/test_door_lock.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ class TestDoorLock(unittest.TestCase):
2323
def setUp(self):
2424
"""Set up Abode module."""
2525
self.abode = abodepy.Abode(username=USERNAME,
26-
password=PASSWORD)
26+
password=PASSWORD,
27+
disable_cache=True)
2728

2829
def tearDown(self):
2930
"""Clean up after test."""

0 commit comments

Comments
 (0)