Skip to content

Commit 478003b

Browse files
committed
Some optimizations to the as_tuples parameter; Added as_tuples to Operations, Users, AuditRecords.
1 parent a5502dc commit 478003b

File tree

13 files changed

+173
-17
lines changed

13 files changed

+173
-17
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Changelog
22

3+
* Added `as_tuples` parameter to `get_all` and `select` functions of the Inventory, DeviceInventory,
4+
DeviceGroupInventory, Events, Alarms, Users, Operations, and AuditRecords API.
35
* Added code coverage reporting to `test` target for _invoke_.
46
* Updated `as_tuple` for complex objects as well as the `as_tuples` parameter for `select`
57
and `get_all` functions to work with strings or 2-tuples. The use of a dictionary

c8y_api/model/_parser.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
from c8y_api.model._util import _StringUtil
99

1010

11-
def as_tuples(json_data, *path: str | tuple):
11+
def as_tuples(json_data, paths: str | tuple | list[str|tuple]):
1212
"""Parse a JSON structure as tuples from paths.
1313
1414
Args:
1515
json_data (dict): A JSON structure as Python dict
16-
path (*str|tuple): Path(s) to extract from
16+
paths (list[str|tuple]): Path(s) to extract from
1717
the structure; Use dots to separate JSON levels; Arrays are not
1818
supported.
1919
@@ -39,7 +39,9 @@ def resolve(segments, default=None):
3939
return json_level.get(segments[-1], json_level.get(_StringUtil.to_pascal_case(segments[-1]), default))
4040

4141
# each p in path(s) can be a string or a tuple
42-
return tuple(resolve(p[0].split('.'), p[1]) if isinstance(p, tuple) else resolve(p.split('.')) for p in path )
42+
return tuple(
43+
resolve(p[0].split('.'), p[1]) if isinstance(p, tuple)
44+
else resolve(p.split('.')) for p in ([paths] if isinstance(paths, (str, tuple)) else paths))
4345

4446
class SimpleObjectParser(object):
4547
"""A parser for simple (without fragments) Cumulocity database objects.

c8y_api/model/administration.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from c8y_api._base_api import CumulocityRestApi, AccessDeniedError
1111
from c8y_api.model._base import CumulocityResource, SimpleObject
12-
from c8y_api.model._parser import SimpleObjectParser, ComplexObjectParser
12+
from c8y_api.model._parser import SimpleObjectParser, ComplexObjectParser, as_tuples as parse_as_tuples
1313
from c8y_api.model._util import _DateUtil
1414

1515

@@ -1067,6 +1067,7 @@ def select(
10671067
limit: int = None,
10681068
page_size: int = 5,
10691069
page_number: int = None,
1070+
as_tuples: str | tuple | list[str | tuple] = None,
10701071
**kwargs
10711072
) -> Generator[User]:
10721073
"""Lazily select and yield User instances.
@@ -1091,6 +1092,10 @@ def select(
10911092
parsed in one chunk). This is a performance related setting.
10921093
page_number (int): Pull a specific page; this effectively disables
10931094
automatic follow-up page retrieval.
1095+
as_tuples: (*str|tuple): Don't parse objects, but directly extract
1096+
the values at certain JSON paths as tuples; If the path is not
1097+
defined in a result, None is used; Specify a tuple to define
1098+
a proper default value for each path.
10941099
10951100
Returns:
10961101
Generator of User instances
@@ -1120,10 +1125,14 @@ def select(
11201125
only_devices=only_devices,
11211126
with_subusers_count=with_subusers_count,
11221127
page_size=page_size,
1123-
11241128
**kwargs
11251129
)
1126-
return super()._iterate(base_query, page_number, limit, User.from_json)
1130+
return super()._iterate(
1131+
base_query,
1132+
page_number,
1133+
limit,
1134+
User.from_json if not as_tuples else
1135+
lambda x: parse_as_tuples(x, as_tuples))
11271136

11281137
def get_all(
11291138
self,
@@ -1133,6 +1142,7 @@ def get_all(
11331142
only_devices: bool = None,
11341143
with_subusers_count: bool = None,
11351144
page_size: int = 1000,
1145+
as_tuples: str | tuple | list[str|tuple] = None,
11361146
**kwargs
11371147
) -> list[User]:
11381148
"""Select and retrieve User instances as list.
@@ -1151,6 +1161,7 @@ def get_all(
11511161
only_devices=only_devices,
11521162
with_subusers_count=with_subusers_count,
11531163
page_size=page_size,
1164+
as_tuples=as_tuples,
11541165
**kwargs))
11551166

11561167
def create(self, *users):

c8y_api/model/alarms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ def select(self,
334334
page_number,
335335
limit,
336336
Alarm.from_json if not as_tuples else
337-
lambda x: parse_as_tuples(x, *([as_tuples] if isinstance(as_tuples, str) else as_tuples)))
337+
lambda x: parse_as_tuples(x, as_tuples))
338338

339339
def get_all(
340340
self,

c8y_api/model/audit.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from c8y_api._base_api import CumulocityRestApi
1010
from c8y_api.model._base import CumulocityResource, ComplexObject
11-
from c8y_api.model._parser import ComplexObjectParser, SimpleObjectParser
11+
from c8y_api.model._parser import ComplexObjectParser, SimpleObjectParser, as_tuples as parse_as_tuples
1212
from c8y_api.model._util import _DateUtil
1313

1414

@@ -207,6 +207,7 @@ def select(
207207
min_age: timedelta = None, max_age: timedelta = None,
208208
reverse: bool = False, limit: int = None,
209209
page_size: int = 1000, page_number: int = None,
210+
as_tuples: str | tuple | list[str | tuple] = None,
210211
**kwargs
211212
) -> Generator[AuditRecord]:
212213
"""Query the database for audit records and iterate over the results.
@@ -239,6 +240,10 @@ def select(
239240
parsed in one chunk). This is a performance related setting.
240241
page_number (int): Pull a specific page; this effectively disables
241242
automatic follow-up page retrieval.
243+
as_tuples: (*str|tuple): Don't parse objects, but directly extract
244+
the values at certain JSON paths as tuples; If the path is not
245+
defined in a result, None is used; Specify a tuple to define
246+
a proper default value for each path.
242247
243248
Returns:
244249
Generator for AuditRecord objects
@@ -250,7 +255,12 @@ def select(
250255
min_age=min_age, max_age=max_age,
251256
reverse=reverse, page_size=page_size,
252257
**kwargs)
253-
return super()._iterate(base_query, page_number, limit, AuditRecord.from_json)
258+
return super()._iterate(
259+
base_query,
260+
page_number,
261+
limit,
262+
AuditRecord.from_json if not as_tuples else
263+
lambda x: parse_as_tuples(x, as_tuples))
254264

255265
def get_all(
256266
self,
@@ -260,6 +270,7 @@ def get_all(
260270
min_age: timedelta = None, max_age: timedelta = None,
261271
reverse: bool = False, limit: int = None,
262272
page_size: int = 1000, page_number: int = None,
273+
as_tuples: str | tuple | list[str | tuple] = None,
263274
**kwargs
264275
) -> List[AuditRecord]:
265276
"""Query the database for audit records and return the results as list.
@@ -278,6 +289,7 @@ def get_all(
278289
before=before, after=after,
279290
min_age=min_age, max_age=max_age,
280291
reverse=reverse, limit=limit, page_size=page_size, page_number=page_number,
292+
as_tuples=as_tuples,
281293
**kwargs,
282294
))
283295

c8y_api/model/events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ def select(self,
345345
page_number,
346346
limit,
347347
Event.from_json if not as_tuples else
348-
lambda x: parse_as_tuples(x, *([as_tuples] if isinstance(as_tuples, str) else as_tuples)))
348+
lambda x: parse_as_tuples(x, as_tuples))
349349

350350
def get_all(
351351
self,

c8y_api/model/inventory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ def _select(self, parse_fun, device_mode: bool, page_number, limit, as_tuples, *
353353
page_number,
354354
limit,
355355
parse_fun if not as_tuples else
356-
lambda x: parse_as_tuples(x, *([as_tuples] if isinstance(as_tuples, str) else as_tuples)))
356+
lambda x: parse_as_tuples(x, as_tuples))
357357

358358
def create(self, *objects: ManagedObject):
359359
"""Create managed objects within the database.

c8y_api/model/measurements.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ def select(
569569
page_number,
570570
limit,
571571
Measurement.from_json if not as_tuples else
572-
lambda x: parse_as_tuples(x, *([as_tuples] if isinstance(as_tuples, str) else as_tuples)))
572+
lambda x: parse_as_tuples(x, as_tuples))
573573

574574
def get_all(
575575
self,

c8y_api/model/operations.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from c8y_api._base_api import CumulocityRestApi
99

1010
from c8y_api.model._base import CumulocityResource, ComplexObject, SimpleObject, _DictWrapper
11-
from c8y_api.model._parser import ComplexObjectParser
11+
from c8y_api.model._parser import ComplexObjectParser, as_tuples as parse_as_tuples
1212
from c8y_api.model._util import _DateUtil
1313

1414

@@ -161,6 +161,7 @@ def select(
161161
min_age: timedelta = None, max_age: timedelta = None,
162162
reverse: bool = False, limit: int = None,
163163
page_size: int = 1000, page_number: int = None,
164+
as_tuples: str | tuple | list[str|tuple] = None,
164165
**kwargs
165166
) -> Generator[Operation]:
166167
""" Query the database for operations and iterate over the results.
@@ -199,6 +200,10 @@ def select(
199200
related setting.
200201
page_number (int): Pull a specific page; this effectively disables
201202
automatic follow-up page retrieval.
203+
as_tuples: (*str|tuple): Don't parse objects, but directly extract
204+
the values at certain JSON paths as tuples; If the path is not
205+
defined in a result, None is used; Specify a tuple to define
206+
a proper default value for each path.
202207
203208
Returns:
204209
Generator[Operation]: Iterable of matching Operation objects
@@ -211,7 +216,12 @@ def select(
211216
reverse=reverse, page_size=page_size,
212217
**kwargs
213218
)
214-
return super()._iterate(base_query, page_number, limit, Operation.from_json)
219+
return super()._iterate(
220+
base_query,
221+
page_number,
222+
limit,
223+
Operation.from_json if not as_tuples else
224+
lambda x: parse_as_tuples(x, as_tuples))
215225

216226
def get_all(
217227
self,
@@ -222,6 +232,7 @@ def get_all(
222232
min_age: timedelta = None, max_age: timedelta = None,
223233
reverse: bool = False, limit: int = None,
224234
page_size: int = 1000, page_number: int = None,
235+
as_tuples: str | tuple | list[str|tuple] = None,
225236
**kwargs
226237
) -> List[Operation]:
227238
""" Query the database for operations and return the results
@@ -237,7 +248,7 @@ def get_all(
237248
expression=expression,
238249
agent_id=agent_id, device_id=device_id, status=status, bulk_id=bulk_id,
239250
fragment=fragment, before=before, after=after, min_age=min_age, max_age=max_age,
240-
reverse=reverse, limit=limit, page_size=page_size, page_number=page_number,
251+
reverse=reverse, limit=limit, page_size=page_size, page_number=page_number, as_tuples=as_tuples,
241252
**kwargs
242253
))
243254

tests/model/test_administration.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
import pytest
77

8-
from c8y_api import CumulocityApi
8+
from c8y_api import CumulocityApi, CumulocityRestApi
9+
from c8y_api.model import Users
910
from tests.utils import isolate_last_call_arg
1011

1112

@@ -40,3 +41,47 @@ def test_select_users(params, expected, not_expected):
4041
assert e in resource
4142
for ne in not_expected:
4243
assert ne not in resource
44+
45+
46+
def test_select_as_tuples():
47+
"""Verify that select as tuples works as expected."""
48+
jsons = [
49+
{'userName': 'user1',
50+
'enabled': True,
51+
'applications': [],
52+
'customProperties': {'p1': 'v1', 'p2': 'v2'}},
53+
{'userName': 'user2',
54+
'enabled': False,
55+
'applications': [{'a': 1}, {'b': 2}],
56+
'customProperties': {'p1': 'v2'},
57+
'phone': '+123'},
58+
]
59+
60+
c8y = CumulocityRestApi(base_url='base', tenant_id='t123', username='u', password='p')
61+
api = Users(c8y)
62+
api.c8y.get = Mock(side_effect=[{'users': jsons}, {'users': []}])
63+
result = api.get_all(as_tuples=[
64+
'user_name', 'enabled', 'applications', 'customProperties.p1', 'customProperties.p2', 'phone'])
65+
assert result == [
66+
('user1', True, [], 'v1', 'v2', None),
67+
('user2', False, [{'a': 1}, {'b': 2}], 'v2', None, '+123'),
68+
]
69+
70+
c8y = CumulocityRestApi(base_url='base', tenant_id='t123', username='u', password='p')
71+
api = Users(c8y)
72+
api.c8y.get = Mock(side_effect=[{'users': jsons}, {'users': []}])
73+
result = api.get_all(as_tuples=[
74+
'userName', 'enabled', 'applications', 'custom_properties.p1', ('customProperties.p2', 'v3'), ('phone', '')])
75+
assert result == [
76+
('user1', True, [], 'v1', 'v2', ''),
77+
('user2', False, [{'a': 1}, {'b': 2}], 'v2', 'v3', '+123'),
78+
]
79+
80+
c8y = CumulocityRestApi(base_url='base', tenant_id='t123', username='u', password='p')
81+
api = Users(c8y)
82+
api.c8y.get = Mock(side_effect=[{'users': jsons}, {'users': []}])
83+
result = api.get_all(as_tuples='enabled')
84+
assert result == [
85+
(True, ),
86+
(False, ),
87+
]

0 commit comments

Comments
 (0)