Skip to content

Commit c657bde

Browse files
authored
GID improvements (#32)
* Better handling of corner cases for GID * Up version * Add future package
1 parent b378b33 commit c657bde

6 files changed

Lines changed: 125 additions & 47 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
### v0.8.8 (2018-03-02)
2+
3+
* Better testing of IOTileCloudSlug corner cases
4+
* Add `future` package to make utils.gid python2 friendly
5+
16
### v0.8.7 (2018-03-02)
27

38
* Ensure IOTileDeviceSlug only considers 48 bits when converting formatted ids

iotile_cloud/utils/gid.py

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from builtins import int
12

23
int16gid = lambda n: '-'.join(['{:04x}'.format(n >> (i << 4) & 0xFFFF) for i in range(0, 1)[::-1]])
34
int32gid = lambda n: '-'.join(['{:04x}'.format(n >> (i << 4) & 0xFFFF) for i in range(0, 2)[::-1]])
@@ -33,7 +34,11 @@ def fix_gid(gid, num_terms):
3334
def gid2int(gid):
3435
elements = gid.split('-')
3536
hex_value = ''.join(elements)
36-
return int(hex_value, 16)
37+
try:
38+
id = int(hex_value, 16)
39+
except Exception:
40+
raise ValueError('Expected HEX number. Got {}'.format(hex_value))
41+
return id
3742

3843

3944
class IOTileCloudSlug(object):
@@ -47,8 +52,10 @@ def formatted_id(self):
4752
return gid_join(parts[1:])
4853

4954
def set_from_single_id_slug(self, type, terms, id):
50-
assert(type in ['p', 'd', 'b', 'g'])
51-
assert (isinstance(id, str))
55+
if type not in ['p', 'd', 'b', 'g']:
56+
raise ValueError('Slugs must start with p/d/b/g')
57+
if not isinstance(id, str):
58+
raise ValueError('Slug must be a string')
5259
parts = gid_split(id)
5360
if parts[0] in ['p', 'd', 'b', 'g']:
5461
id = parts[1]
@@ -57,8 +64,10 @@ def set_from_single_id_slug(self, type, terms, id):
5764

5865
def get_id(self):
5966
parts = gid_split(self._slug)
60-
assert(len(parts) == 2)
61-
assert(parts[0] in ['p', 'd', 'g'])
67+
if len(parts) != 2:
68+
raise ValueError('Cannot call get_id() for IDs with more than one term')
69+
if parts[0] not in ['p', 'd', 'g']:
70+
raise ValueError('Only Devices/DataBlocks/Fleets have single IDs')
6271
return gid2int(parts[1])
6372

6473

@@ -67,6 +76,8 @@ class IOTileProjectSlug(IOTileCloudSlug):
6776

6877
def __init__(self, id):
6978
if isinstance(id, int):
79+
if id < 0:
80+
raise ValueError('IOTileProjectSlug: UUID should be greater or equal than zero')
7081
pid = int2pid(id)
7182
else:
7283
pid = id
@@ -82,17 +93,24 @@ def __init__(self, id):
8293
return
8394

8495
if isinstance(id, int):
96+
if id <= 0:
97+
raise ValueError('IOTileDeviceSlug: UUID should be greater than zero')
8598
did = int2did(id)
8699
else:
87-
assert isinstance(id, str)
100+
if not isinstance(id, str):
101+
raise ValueError('IOTileDeviceSlug: must be an int or str')
88102
parts = gid_split(id)
89103
if len(parts) == 1:
90104
did = parts[0]
91105
else:
106+
if parts[0] != 'd':
107+
raise ValueError('IOTileDeviceSlug: must start with a "d"')
92108
did = gid_join(parts[1:])
93109

94110
# Convert to int and back to get rid of anything above 48 bits
95111
id = gid2int(did)
112+
if id <= 0:
113+
raise ValueError('IOTileDeviceSlug: UUID should be greater than zero')
96114
did = int2did(id)
97115

98116
self.set_from_single_id_slug('d', 4, did)
@@ -115,34 +133,45 @@ class IOTileBlockSlug(IOTileCloudSlug):
115133

116134
def __init__(self, id, block=0):
117135
if isinstance(id, int):
136+
if id <= 0:
137+
raise ValueError('IOTileBlockSlug: UUID should be greater than zero')
118138
did = int2did(id)
119139
else:
120140
parts = gid_split(id)
121141
if(len(parts) == 1):
142+
# gid2int will raise exception if not a proper HEX string
143+
id = gid2int(parts[0])
144+
if id <= 0:
145+
raise ValueError('IOTileBlockSlug: UUID should be greater than zero')
122146
parts = ['d',] + parts
123-
assert(parts[0] in ['d', 'b'])
147+
if parts[0] not in ['d', 'b']:
148+
raise ValueError('IOTileBlockSlug: Slug must start with "b" or "d"')
124149
id_parts = parts[1].split('-')
125150
if parts[0] == 'b':
126-
assert(len(id_parts) == 4)
151+
if len(id_parts) != 4:
152+
raise ValueError('IOTileBlockSlug: Expected format: b--xxxx-xxxx-xxxx-xxxx')
127153
self._slug = id
128154
self._block = id_parts[0]
129155
return
130156
if len(id_parts) == 4:
131157
self._block = id_parts[0]
132158
did = '-'.join(id_parts[1:])
133159
else:
134-
assert(parts[0] == 'd')
160+
if parts[0] != 'd':
161+
raise ValueError('DataBlock Slug must start with "b" or "d"')
135162
did = '-'.join(id_parts[0:])
136163
did = fix_gid(did, 3)
137164
if not self._block:
138165
self._block = int2bid(block)
139166
self.set_from_single_id_slug('b', 4, '-'.join([self._block, did]))
140167

168+
141169
def get_id(self):
142170
# DataBlocks should behave like Devices
143171
# get_id returns the device ID
144172
parts = gid_split(self._slug)
145-
assert(len(parts) == 2)
173+
if len(parts) != 2:
174+
raise ValueError('IOTileBlockSlug: Cannot call get_id() for IDs with more than one term')
146175
id_parts = parts[1].split('-')
147176
hex_value = ''.join(id_parts[1:])
148177
return int(hex_value, 16)
@@ -167,7 +196,7 @@ def __init__(self, device, index):
167196
elif isinstance(device, str):
168197
device_id = IOTileDeviceSlug(device).get_id()
169198
else:
170-
raise ValueError("Unknown device specifier, must be string, int or IOTileDeviceSlug")
199+
raise ValueError("IOTileStreamerSlug: Unknown device specifier, must be string, int or IOTileDeviceSlug")
171200

172201
index = int(index)
173202

@@ -194,26 +223,33 @@ class IOTileVariableSlug(IOTileCloudSlug):
194223
# Store local variable ID on top of globally unique slug
195224
_local = None
196225

197-
def __init__(self, id, project=None):
226+
def __init__(self, id, project=IOTileProjectSlug(0)):
198227
"""
199228
200229
:param id: Variable Local Id (string or int)
201-
:param project: IOTileCProjectSlug instance
230+
:param project: IOTileCProjectSlug instance. Defaults to zero, which represent a wildcard
202231
"""
203232
if project:
204233
if not isinstance(project, IOTileProjectSlug):
205234
project = IOTileProjectSlug(project)
206-
if isinstance(id, int) and project != None:
235+
236+
if isinstance(id, int):
237+
if id <= 0:
238+
raise ValueError('IOTileVariableSlug: UUID should be greater than zero')
207239
vid = int2vid(id)
208240
self._slug = gid_join(['v', project.formatted_id(), vid])
209241
else:
210-
assert (isinstance(id, str))
242+
if not isinstance(id, str):
243+
raise ValueError("IOTileVariableSlug: ID must be int or str")
244+
211245
parts = gid_split(id)
212-
if len(parts) == 1 and project != None:
246+
if len(parts) == 1:
247+
# gid2int will raise exception if not a proper HEX string
248+
gid2int(id)
213249
self._slug = gid_join(['v', project.formatted_id(), id])
214250
else:
215-
assert(project == None)
216-
assert(len(parts) == 3)
251+
if len(parts) != 3:
252+
raise ValueError("IOTileVariableSlug: Expected format: v--xxxx-xxxx--xxxx")
217253
self._slug = id
218254
self._local = gid_split(self._slug)[2]
219255

@@ -225,8 +261,10 @@ class IOTileStreamSlug(IOTileCloudSlug):
225261

226262
def __init__(self, id=None):
227263
if id:
228-
assert(isinstance(id, str))
229-
assert(len(gid_split(id)) == 4)
264+
if not isinstance(id, str):
265+
raise ValueError("Variable ID must be int or str")
266+
if len(gid_split(id)) != 4:
267+
raise ValueError("Stream slug must have three terms: s--<prj>--<dev>--<var>")
230268
self._slug = id
231269

232270
def from_parts(self, project, device, variable):

requirements-test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ coverage==3.7.1
77
coveralls==1.1
88
mock==2.0.0
99
requests-mock==1.3.0
10+
future==0.16.0

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
'pytest11': ['mock_cloud = iotile_cloud.utils.mock_cloud']
1919
},
2020
install_requires=[
21+
'future',
2122
'requests',
2223
'python-dateutil'
2324
],

tests/test_gid.py

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,19 @@ def test_device_slug(self):
5050
self.assertEqual(str(id), 'd--0000-0000-0000-0001')
5151
self.assertEqual(id.get_id(), 1)
5252

53+
self.assertRaises(ValueError, IOTileDeviceSlug, 'string')
54+
self.assertRaises(ValueError, IOTileDeviceSlug, 'x--0000-0000-0000-0001')
55+
self.assertRaises(ValueError, IOTileDeviceSlug, '0000-0000-0000-0000')
56+
self.assertRaises(ValueError, IOTileDeviceSlug, -5)
57+
self.assertRaises(ValueError, IOTileDeviceSlug, 0)
58+
5359
def test_block_slug(self):
5460
id = IOTileBlockSlug(5)
5561
self.assertEqual(str(id), 'b--0000-0000-0000-0005')
5662
id = IOTileBlockSlug(0xa)
5763
self.assertEqual(str(id), 'b--0000-0000-0000-000a')
5864

59-
self.assertRaises(AssertionError, IOTileBlockSlug, 'b--0000-0000-1234')
65+
self.assertRaises(ValueError, IOTileBlockSlug, 'b--0000-0000-1234')
6066

6167
id = IOTileBlockSlug('b--0001-0000-0000-1234')
6268
self.assertEqual(str(id), 'b--0001-0000-0000-1234')
@@ -74,13 +80,23 @@ def test_block_slug(self):
7480

7581
self.assertEqual(id.formatted_id(), '0003-0000-0000-0005')
7682

83+
self.assertRaises(ValueError, IOTileBlockSlug, 'string')
84+
self.assertRaises(ValueError, IOTileBlockSlug, 'x--0000-0000-0000-0001')
85+
self.assertRaises(ValueError, IOTileBlockSlug, '0000-0000-0000-0000')
86+
self.assertRaises(ValueError, IOTileBlockSlug, -5)
87+
self.assertRaises(ValueError, IOTileBlockSlug, 0)
88+
7789
def test_variable_slug(self):
78-
self.assertRaises(AssertionError, IOTileVariableSlug, 5)
90+
self.assertRaises(ValueError, IOTileVariableSlug, 'foo')
7991

8092
id = IOTileVariableSlug(5, '1234')
8193
self.assertEqual(str(id), 'v--0000-1234--0005')
8294
self.assertEqual(id.formatted_local_id(), '0005')
8395

96+
id = IOTileVariableSlug(5)
97+
self.assertEqual(str(id), 'v--0000-0000--0005')
98+
self.assertEqual(id.formatted_local_id(), '0005')
99+
84100
id = IOTileVariableSlug(5, IOTileProjectSlug('1234'))
85101
self.assertEqual(str(id), 'v--0000-1234--0005')
86102
self.assertEqual(id.formatted_local_id(), '0005')
@@ -91,9 +107,12 @@ def test_variable_slug(self):
91107
id = IOTileVariableSlug('v--0000-1234--0005')
92108
self.assertEqual(str(id), 'v--0000-1234--0005')
93109

110+
self.assertRaises(ValueError, IOTileVariableSlug, -5)
111+
self.assertRaises(ValueError, IOTileVariableSlug, 0)
112+
94113
def test_stream_slug(self):
95-
self.assertRaises(AssertionError, IOTileStreamSlug, 5)
96-
self.assertRaises(AssertionError, IOTileStreamSlug, 's--0001')
114+
self.assertRaises(ValueError, IOTileStreamSlug, 5)
115+
self.assertRaises(ValueError, IOTileStreamSlug, 's--0001')
97116

98117
id = IOTileStreamSlug('s--0000-0001--0000-0000-0000-0002--0003')
99118
self.assertEqual(str(id), 's--0000-0001--0000-0000-0000-0002--0003')
@@ -135,6 +154,22 @@ def test_stream_from_parts(self):
135154
id.from_parts(project=7, device=1, variable=vslug)
136155
self.assertEqual(str(id), 's--0000-0007--0000-0000-0000-0001--5002')
137156

157+
id = IOTileStreamSlug()
158+
id.from_parts(project=7, device=1, variable='5002')
159+
self.assertEqual(str(id), 's--0000-0007--0000-0000-0000-0001--5002')
160+
161+
# Project is the only one that can be zero (wildcard)
162+
id = IOTileStreamSlug()
163+
id.from_parts(project=0, device=1, variable='5002')
164+
self.assertEqual(str(id), 's--0000-0000--0000-0000-0000-0001--5002')
165+
166+
id = IOTileStreamSlug()
167+
with pytest.raises(ValueError):
168+
id.from_parts(project=-1, device=1, variable='5002')
169+
with pytest.raises(ValueError):
170+
id.from_parts(project=1, device=-1, variable='5002')
171+
with pytest.raises(ValueError):
172+
id.from_parts(project=1, device=1, variable=-1)
138173

139174
def test_id_property(self):
140175
project = IOTileProjectSlug(5)
@@ -145,32 +180,30 @@ def test_id_property(self):
145180

146181
id = IOTileStreamSlug()
147182
id.from_parts(project=project, device=device, variable=variable)
148-
self.assertRaises(AssertionError, id.get_id)
149-
150-
151-
def test_streamer_gid():
152-
"""Ensure that IOTileStreamerSlug works."""
183+
self.assertRaises(ValueError, id.get_id)
153184

154-
s_gid = IOTileStreamerSlug(1, 2)
155-
assert str(s_gid) == "t--0000-0000-0000-0001--0002"
156-
assert s_gid.get_device() == "d--0000-0000-0000-0001"
157-
assert s_gid.get_index() == "0002"
185+
def test_streamer_gid(self):
186+
"""Ensure that IOTileStreamerSlug works."""
158187

159-
s_gid = IOTileStreamerSlug("d--0000-0000-0000-1234", 1)
160-
assert str(s_gid) == "t--0000-0000-0000-1234--0001"
188+
s_gid = IOTileStreamerSlug(1, 2)
189+
assert str(s_gid) == "t--0000-0000-0000-0001--0002"
190+
assert s_gid.get_device() == "d--0000-0000-0000-0001"
191+
assert s_gid.get_index() == "0002"
161192

162-
with pytest.raises(ValueError):
163-
IOTileStreamerSlug([], 1)
193+
s_gid = IOTileStreamerSlug("d--0000-0000-0000-1234", 1)
194+
assert str(s_gid) == "t--0000-0000-0000-1234--0001"
164195

165-
d_gid = IOTileDeviceSlug(15)
166-
s_gid = IOTileStreamerSlug(d_gid, 0)
167-
assert str(s_gid) == "t--0000-0000-0000-000f--0000"
168-
assert s_gid.get_device() == str(d_gid)
169-
assert s_gid.get_index() == "0000"
196+
with pytest.raises(ValueError):
197+
IOTileStreamerSlug([], 1)
170198

199+
d_gid = IOTileDeviceSlug(15)
200+
s_gid = IOTileStreamerSlug(d_gid, 0)
201+
assert str(s_gid) == "t--0000-0000-0000-000f--0000"
202+
assert s_gid.get_device() == str(d_gid)
203+
assert s_gid.get_index() == "0000"
171204

172-
def test_fleet_gid():
173-
"""Ensure that IOTileFleetSlug works."""
205+
def test_fleet_gid(self):
206+
"""Ensure that IOTileFleetSlug works."""
174207

175-
f_gid = IOTileFleetSlug(1)
176-
assert str(f_gid) == 'g--0000-0000-0001'
208+
f_gid = IOTileFleetSlug(1)
209+
assert str(f_gid) == 'g--0000-0000-0001'

version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version = '0.8.7'
1+
version = '0.8.8'

0 commit comments

Comments
 (0)