Skip to content

Commit 6a487e4

Browse files
committed
Switching to include/exclude filters; refactored all APIs.
1 parent 621f138 commit 6a487e4

30 files changed

+380
-99
lines changed

buster37.dockerfile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
FROM python:3.7-slim-buster
22

3-
RUN apt-get update && apt-get -y install git
3+
RUN sed -i 's|deb.debian.org/debian|archive.debian.org/debian|g' /etc/apt/sources.list \
4+
&& sed -i 's|security.debian.org/debian-security|archive.debian.org/debian-security|g' /etc/apt/sources.list \
5+
&& apt-get update \
6+
&& apt-get -y install git \
7+
&& rm -rf /var/lib/apt/lists/*
48

59
COPY requirements.txt /
610
RUN pip install --upgrade pip && pip install -r requirements.txt

c8y_api/model/_base.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,8 @@ def _iterate(
789789
base_query: str,
790790
page_number: int | None,
791791
limit: int | None,
792-
filter: str | JsonMatcher | None,
792+
include: str | JsonMatcher | None,
793+
exclude: str | JsonMatcher | None,
793794
parse_fun
794795
):
795796
# if no specific page is defined we just start at 1
@@ -799,15 +800,17 @@ def _iterate(
799800
# - there is no result (i.e. we were at the last page)
800801
num_results = 0
801802
# compile/prepare filter if defined
802-
if isinstance(filter, str):
803-
filter = self.default_matcher(filter)
803+
if isinstance(include, str):
804+
include = self.default_matcher(include)
805+
if isinstance(exclude, str):
806+
exclude = self.default_matcher(exclude)
804807

805808
while True:
806-
if filter:
807-
results = [parse_fun(x) for x in self._get_page(base_query, current_page) if filter.safe_matches(x)]
808-
else:
809-
results = [parse_fun(x) for x in self._get_page(base_query, current_page)]
810-
# no results, so we are done
809+
results = [
810+
parse_fun(x) for x in self._get_page(base_query, current_page)
811+
if (not include or include.safe_matches(x))
812+
and (not exclude or not exclude.safe_matches(x))
813+
]
811814
if not results:
812815
break
813816
for result in results:

c8y_api/model/_util.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from typing import Union
66

77
from dateutil import parser
8-
from re import sub, PatternError
98

109

1110
class _StringUtil(object):
@@ -48,7 +47,7 @@ def matches(expression: str, string: str):
4847
"""Check if regex expression matches a string."""
4948
try:
5049
return re.search(expression, string) is not None
51-
except PatternError:
50+
except re.error:
5251
return False
5352

5453
class _QueryUtil(object):
@@ -59,15 +58,15 @@ def encode_odata_query_value(value):
5958
http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_URLParsing
6059
http://docs.oasis-open.org/odata/odata/v4.01/cs01/abnf/odata-abnf-construction-rules.txt """
6160
# single quotes escaped through single quote
62-
return sub('\'', '\'\'', value)
61+
return re.sub('\'', '\'\'', value)
6362

6463
@staticmethod
6564
def encode_odata_text_value(value):
6665
"""Encode value strings according to OData query rules.
6766
http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_URLParsing
6867
http://docs.oasis-open.org/odata/odata/v4.01/cs01/abnf/odata-abnf-construction-rules.txt """
6968
# single quotes escaped through single quote
70-
encoded_quotes = sub('\'', '\'\'', value)
69+
encoded_quotes = re.sub('\'', '\'\'', value)
7170
return encoded_quotes if " " not in encoded_quotes else f"'{encoded_quotes}'"
7271

7372

c8y_api/model/administration.py

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,9 @@ def from_json(cls, json: dict) -> GlobalRole:
262262
role: GlobalRole = cls._from_json(json, GlobalRole())
263263
# role ID are int for some reason - convert for consistency
264264
role.id = str(role.id)
265-
if json['roles'] and json['roles']['references']:
265+
if 'roles' in json and json['roles'] and json['roles']['references']:
266266
role.permission_ids = {ref['role']['id'] for ref in json['roles']['references']}
267-
if json['applications']:
267+
if 'applications' in json and json['applications']:
268268
role.application_ids = {ref['id'] for ref in json['applications']}
269269
return role
270270

@@ -943,7 +943,8 @@ def get(self, role_id: str | int) -> InventoryRole:
943943
def select(
944944
self,
945945
limit: int = None,
946-
filter: str | JsonMatcher = None,
946+
include: str | JsonMatcher = None,
947+
exclude: str | JsonMatcher = None,
947948
page_size: int = 1000,
948949
page_number: int = None,
949950
) -> Generator[InventoryRole]:
@@ -956,8 +957,12 @@ def select(
956957
957958
Args:
958959
limit (int): Limit the number of results to this number.
959-
filter (str | JsonMatcher): Matcher/expression to filter the query
960-
results (on client side). Uses JMESPath by default.
960+
include (str | JsonMatcher): Matcher/expression to filter the query
961+
results (on client side). The inclusion is applied first.
962+
Creates a JMESPath matcher by default for strings.
963+
exclude (str | JsonMatcher): Matcher/expression to filter the query
964+
results (on client side). The exclusion is applied second.
965+
Creates a JMESPath matcher by default for strings.
961966
page_size (int): Define the number of objects read (and parsed
962967
in one chunk). This is a performance related setting.
963968
page_number (int): Pull a specific page; this effectively disables
@@ -967,12 +972,12 @@ def select(
967972
Generator for InventoryRole objects
968973
"""
969974
base_query = self._prepare_query(page_size=page_size)
970-
return super()._iterate(base_query, page_number, limit, filter, InventoryRole.from_json)
975+
return super()._iterate(base_query, page_number, limit, include, exclude, InventoryRole.from_json)
971976

972977
def get_all(
973978
self,
974979
limit: int = None,
975-
filter: str | JsonMatcher = None,
980+
include: str | JsonMatcher = None, exclude: str | JsonMatcher = None,
976981
page_size: int = 1000,
977982
page_number: int = None,
978983
) -> List[InventoryRole]:
@@ -986,7 +991,12 @@ def get_all(
986991
Returns:
987992
List of InventoryRole objects
988993
"""
989-
return list(self.select(limit=limit, filter=filter, page_size=page_size, page_number=page_number))
994+
return list(self.select(
995+
limit=limit,
996+
include=include,
997+
exclude=exclude,
998+
page_size=page_size,
999+
page_number=page_number))
9901000

9911001
def select_assignments(self, username: str) -> Generator[InventoryRoleAssignment]:
9921002
"""Get all inventory role assignments of a user.
@@ -1080,7 +1090,7 @@ def select(
10801090
only_devices: bool = None,
10811091
with_subusers_count: bool = None,
10821092
limit: int = None,
1083-
filter: str | JsonMatcher = None,
1093+
include: str | JsonMatcher = None, exclude: str | JsonMatcher = None,
10841094
page_size: int = 5,
10851095
page_number: int = None,
10861096
as_values: str | tuple | list[str | tuple] = None,
@@ -1104,8 +1114,12 @@ def select(
11041114
with_subusers_count (bool): Whether to include an additional field
11051115
`subusersCount` which holds the number of direct sub users.
11061116
limit (int): Limit the number of results to this number.
1107-
filter (str | JsonMatcher): Matcher/expression to filter the query
1108-
results (on client side). Uses JMESPath by default.
1117+
include (str | JsonMatcher): Matcher/expression to filter the query
1118+
results (on client side). The inclusion is applied first.
1119+
Creates a JMESPath matcher by default for strings.
1120+
exclude (str | JsonMatcher): Matcher/expression to filter the query
1121+
results (on client side). The exclusion is applied second.
1122+
Creates a JMESPath matcher by default for strings.
11091123
page_size (int): Define the number of events which are read (and
11101124
parsed in one chunk). This is a performance related setting.
11111125
page_number (int): Pull a specific page; this effectively disables
@@ -1149,7 +1163,8 @@ def select(
11491163
base_query,
11501164
page_number,
11511165
limit,
1152-
filter,
1166+
include,
1167+
exclude,
11531168
User.from_json if not as_values else
11541169
lambda x: parse_as_values(x, as_values))
11551170

@@ -1160,7 +1175,7 @@ def get_all(
11601175
owner: str = None,
11611176
only_devices: bool = None,
11621177
with_subusers_count: bool = None,
1163-
filter: str | JsonMatcher = None,
1178+
include: str | JsonMatcher = None, exclude: str | JsonMatcher = None,
11641179
page_size: int = 1000,
11651180
as_values: str | tuple | list[str|tuple] = None,
11661181
**kwargs
@@ -1180,7 +1195,8 @@ def get_all(
11801195
owner=owner,
11811196
only_devices=only_devices,
11821197
with_subusers_count=with_subusers_count,
1183-
filter=filter,
1198+
include=include,
1199+
exclude=exclude,
11841200
page_size=page_size,
11851201
as_values=as_values,
11861202
**kwargs))
@@ -1310,16 +1326,21 @@ def get(self, role_id: int | str) -> GlobalRole:
13101326
def select(
13111327
self,
13121328
username: str = None,
1313-
filter: str | JsonMatcher = None,
1329+
include: str | JsonMatcher = None,
1330+
exclude: str | JsonMatcher = None,
13141331
page_size: int = 5,
13151332
) -> Generator[GlobalRole]:
13161333
"""Iterate over global roles.
13171334
13181335
Args:
13191336
username (str): Retrieve global roles assigned to a specified user
13201337
If omitted, all available global roles are returned
1321-
filter (str | JsonMatcher): Matcher/expression to filter the query
1322-
results (on client side). Uses JMESPath by default.
1338+
include (str | JsonMatcher): Matcher/expression to filter the query
1339+
results (on client side). The inclusion is applied first.
1340+
Creates a JMESPath matcher by default for strings.
1341+
exclude (str | JsonMatcher): Matcher/expression to filter the query
1342+
results (on client side). The exclusion is applied second.
1343+
Creates a JMESPath matcher by default for strings.
13231344
page_size (int): Maximum number of entries fetched per requests;
13241345
this is a performance setting
13251346
@@ -1330,16 +1351,22 @@ def select(
13301351
# generic _iterate method, we have to do everything manually.
13311352
if username:
13321353
# compile/prepare filter if defined
1333-
if isinstance(filter, str):
1334-
filter = self.default_matcher(filter)
1354+
if isinstance(include, str):
1355+
include = self.default_matcher(include)
1356+
if isinstance(exclude, str):
1357+
exclude = self.default_matcher(exclude)
13351358
# select by username
13361359
query = f'/user/{self.c8y.tenant_id}/users/{username}/groups?pageSize={page_size}&currentPage='
13371360
page_number = 1
13381361
while True:
13391362
response_json = self.c8y.get(query + str(page_number))
13401363
references = (
1341-
response_json['references'] if not filter
1342-
else [x for x in response_json['references'] if filter.safe_matches(x['group'])]
1364+
response_json['references'] if not include and not exclude
1365+
else [
1366+
x for x in response_json['references']
1367+
if (not include or include.safe_matches(x['group']))
1368+
and (not exclude or not exclude.safe_matches(x['group']))
1369+
]
13431370
)
13441371
if not references:
13451372
break
@@ -1352,28 +1379,38 @@ def select(
13521379
# select all
13531380
base_query = self._prepare_query(page_size=page_size)
13541381
yield from super()._iterate(
1355-
base_query, page_number=None, limit=None, filter=filter, parse_fun=GlobalRole.from_json)
1382+
base_query,
1383+
page_number=None,
1384+
limit=None,
1385+
include=include,
1386+
exclude=exclude,
1387+
parse_fun=GlobalRole.from_json)
13561388

13571389
def get_all(
13581390
self,
13591391
username: str = None,
1360-
filter: str | JsonMatcher = None,
1392+
include: str | JsonMatcher = None,
1393+
exclude: str | JsonMatcher = None,
13611394
page_size: int = 1000
13621395
) -> List[GlobalRole]:
13631396
"""Retrieve global roles.
13641397
13651398
Args:
13661399
username (str): Retrieve global roles assigned to a specified user
13671400
If omitted, all available global roles are returned
1368-
filter (str | JsonMatcher): Matcher/expression to filter the query
1369-
results (on client side). Uses JMESPath by default.
1401+
include (str | JsonMatcher): Matcher/expression to filter the query
1402+
results (on client side). The inclusion is applied first.
1403+
Creates a JMESPath matcher by default for strings.
1404+
exclude (str | JsonMatcher): Matcher/expression to filter the query
1405+
results (on client side). The exclusion is applied second.
1406+
Creates a JMESPath matcher by default for strings.
13701407
page_size (int): Maximum number of entries fetched per requests;
13711408
this is a performance setting
13721409
13731410
Return:
13741411
List of GlobalRole instances
13751412
"""
1376-
return list(self.select(username=username, filter=filter, page_size=page_size))
1413+
return list(self.select(username=username, include=include, exclude=exclude, page_size=page_size))
13771414

13781415
def assign_users(self, role_id: int | str, *usernames: str):
13791416
"""Add users to a global role.

c8y_api/model/alarms.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from typing import List, Generator
77

88
from c8y_api._base_api import CumulocityRestApi
9-
from c8y_api.model import JsonMatcher
9+
from c8y_api.model.matcher import JsonMatcher
1010
from c8y_api.model._base import CumulocityResource, SimpleObject, ComplexObject
1111
from c8y_api.model._parser import as_values as parse_as_values, ComplexObjectParser
1212
from c8y_api.model._util import _DateUtil
@@ -255,7 +255,7 @@ def select(self,
255255
min_age: timedelta = None, max_age: timedelta = None,
256256
with_source_assets: bool = None, with_source_devices: bool = None,
257257
reverse: bool = False, limit: int = None,
258-
filter: str | JsonMatcher = None,
258+
include: str | JsonMatcher = None, exclude: str | JsonMatcher = None,
259259
page_size: int = 1000, page_number: int = None,
260260
as_values: str | tuple | list[str | tuple] = None,
261261
**kwargs) -> Generator[Alarm]:
@@ -305,8 +305,12 @@ def select(self,
305305
reverse (bool): Invert the order of results, starting with the
306306
most recent one
307307
limit (int): Limit the number of results to this number.
308-
filter (str | JsonMatcher): Matcher/expression to filter the query
309-
results (on client side). Uses JMESPath by default.
308+
include (str | JsonMatcher): Matcher/expression to filter the query
309+
results (on client side). The inclusion is applied first.
310+
Creates a JMESPath matcher by default for strings.
311+
exclude (str | JsonMatcher): Matcher/expression to filter the query
312+
results (on client side). The exclusion is applied second.
313+
Creates a JMESPath matcher by default for strings.
310314
page_size (int): Define the number of alarms which are read (and
311315
parsed in one chunk). This is a performance related setting.
312316
page_number (int): Pull a specific page; this effectively disables
@@ -339,7 +343,8 @@ def select(self,
339343
base_query,
340344
page_number,
341345
limit,
342-
filter,
346+
include,
347+
exclude,
343348
Alarm.from_json if not as_values else
344349
lambda x: parse_as_values(x, as_values))
345350

@@ -357,7 +362,7 @@ def get_all(
357362
min_age: timedelta = None, max_age: timedelta = None,
358363
with_source_assets: bool = None, with_source_devices: bool = None,
359364
reverse: bool = False, limit: int = None,
360-
filter: str | JsonMatcher = None,
365+
include: str | JsonMatcher = None, exclude: str | JsonMatcher = None,
361366
page_size: int = 1000, page_number: int = None,
362367
as_values: str | tuple | list[str | tuple] = None,
363368
**kwargs) -> List[Alarm]:
@@ -383,7 +388,8 @@ def get_all(
383388
last_updated_from=last_updated_from, last_updated_to=last_updated_to,
384389
min_age=min_age, max_age=max_age, reverse=reverse,
385390
with_source_devices=with_source_devices, with_source_assets=with_source_assets,
386-
limit=limit, filter=filter, page_size=page_size, page_number=page_number,
391+
limit=limit, include=include, exclude=exclude,
392+
page_size=page_size, page_number=page_number,
387393
as_values=as_values,
388394
**kwargs))
389395

c8y_api/model/applications.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ def select(
319319
page_size=page_size,
320320
**kwargs
321321
)
322-
return super()._iterate(base_query, page_number, limit, None, Application.from_json)
322+
return super()._iterate(base_query, page_number, limit, None, None, Application.from_json)
323323

324324
def get_all(
325325
self,

0 commit comments

Comments
 (0)