pyGuardPoint 2.1.0__tar.gz → 2.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/PKG-INFO +1 -1
  2. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/CustomWebsocketTransport.py +15 -3
  3. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_cardholders.py +10 -22
  4. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_cards.py +5 -2
  5. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_departments.py +4 -1
  6. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_readers.py +4 -1
  7. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_odata_filter.py +13 -6
  8. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_str_match_algo.py +11 -2
  9. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/guardpoint_connection_asyncio.py +60 -42
  10. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/guardpoint_connection.py +4 -3
  11. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/guardpoint_dataclasses.py +25 -18
  12. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/guardpoint_utils.py +17 -7
  13. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint.egg-info/PKG-INFO +1 -1
  14. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/setup.py +1 -1
  15. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/LICENSE.txt +0 -0
  16. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/README.rst +0 -0
  17. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/CustomWebsocketTransportOld.py +0 -0
  18. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/__init__.py +0 -0
  19. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_accessgroups.py +0 -0
  20. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_alarmstates.py +0 -0
  21. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_alarmzones.py +0 -0
  22. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_areas.py +0 -0
  23. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_cardholdertypes.py +0 -0
  24. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_controllers.py +0 -0
  25. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_customizedfields.py +0 -0
  26. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_diagnostic.py +0 -0
  27. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_events.py +0 -0
  28. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_genericinformation.py +0 -0
  29. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_inputs.py +0 -0
  30. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_manualevents.py +0 -0
  31. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_ouputs.py +0 -0
  32. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_personaldetails.py +0 -0
  33. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_scheduledmags.py +0 -0
  34. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_securitygroups.py +0 -0
  35. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_sites.py +0 -0
  36. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/_guardpoint_weeklyprograms.py +0 -0
  37. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/__init__.py +0 -0
  38. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_accessgroups.py +0 -0
  39. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_alarmstates.py +0 -0
  40. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_alarmzones.py +0 -0
  41. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_areas.py +0 -0
  42. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_cardholders.py +0 -0
  43. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_cardholdertypes.py +0 -0
  44. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_cards.py +0 -0
  45. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_controllers.py +0 -0
  46. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_customizedfields.py +0 -0
  47. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_departments.py +0 -0
  48. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_diagnostic.py +0 -0
  49. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_events.py +0 -0
  50. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_genericinformation.py +0 -0
  51. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_manualevents.py +0 -0
  52. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_ouputs.py +0 -0
  53. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_personaldetails.py +0 -0
  54. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_readers.py +0 -0
  55. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_scheduledmags.py +0 -0
  56. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_securitygroups.py +0 -0
  57. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_sites.py +0 -0
  58. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/gp_asyncio/_async_guardpoint_weeklyprograms.py +0 -0
  59. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/guardpoint.py +0 -0
  60. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/guardpoint_asyncio.py +0 -0
  61. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/guardpoint_error.py +0 -0
  62. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint/guardpoint_threaded.py +0 -0
  63. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint.egg-info/SOURCES.txt +0 -0
  64. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint.egg-info/dependency_links.txt +0 -0
  65. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint.egg-info/not-zip-safe +0 -0
  66. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint.egg-info/requires.txt +0 -0
  67. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/pyGuardPoint.egg-info/top_level.txt +0 -0
  68. {pyguardpoint-2.1.0 → pyguardpoint-2.1.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyGuardPoint
3
- Version: 2.1.0
3
+ Version: 2.1.2
4
4
  Summary: Python wrapper for GuardPoint 10 Access Control System
5
5
  Author: John Owen
6
6
  Maintainer-email: sales@sensoraccess.co.uk
@@ -8,10 +8,22 @@ from typing import TYPE_CHECKING
8
8
  from aiohttp import ClientSession, TCPConnector
9
9
  from aiohttp import ClientTimeout
10
10
  from aiohttp import ServerConnectionError
11
- from websockets.asyncio.client import ClientConnection
12
- from websockets.asyncio.client import connect
11
+
12
+ # Handle websockets version compatibility
13
+ try:
14
+ from websockets.asyncio.client import ClientConnection, connect
15
+ except ImportError:
16
+ # websockets<13.0 compatibility
17
+ from websockets.client import WebSocketClientProtocol as ClientConnection
18
+ from websockets.client import connect
19
+
13
20
  from websockets.exceptions import ConnectionClosed
14
- from websockets.protocol import State
21
+
22
+ try:
23
+ from websockets.protocol import State
24
+ except ImportError:
25
+ # websockets<13.0 compatibility
26
+ State = None
15
27
 
16
28
  import pysignalr.exceptions as exceptions
17
29
  from pysignalr.messages import CompletionMessage
@@ -298,9 +298,6 @@ class CardholdersAPI:
298
298
  return None
299
299
  else:
300
300
  return None
301
- # Part of the Cards_API
302
- # (Broken API Call)
303
- #return self.get_cardholder_by_card_code(card_code)
304
301
  else:
305
302
  return self._get_card_holder(uid)
306
303
 
@@ -355,12 +352,6 @@ class CardholdersAPI:
355
352
  "securityGroup," \
356
353
  "insideArea"
357
354
 
358
- # Do not apply site filter, when looking for individuals
359
- #if self.site_uid is not None:
360
- # match_args = {'ownerSiteUID': self.site_uid}
361
- # filter_str = _compose_filter(exact_match=match_args)
362
- # url_query_params += ("&" + filter_str)
363
-
364
355
  code, json_body = self.gp_json_query("GET", url=(url + url_query_params))
365
356
 
366
357
  if code == 404: # Not Found
@@ -428,14 +419,14 @@ class CardholdersAPI:
428
419
  return []
429
420
  if limit > 50 and count is False:
430
421
  i_offset = offset
431
- offset = 0
422
+ current_offset = 0
432
423
  batch_limit = 50
433
424
  card_holders = []
434
- while len(card_holders) == offset:
435
- if offset + batch_limit > limit:
436
- batch_limit = limit - offset
425
+ while len(card_holders) < limit:
426
+ if current_offset + batch_limit > limit:
427
+ batch_limit = limit - current_offset
437
428
  if batch_limit > 0:
438
- batch = self._split_get_card_holders_query(offset=offset + i_offset, limit=batch_limit,
429
+ batch = self._split_get_card_holders_query(offset=current_offset + i_offset, limit=batch_limit,
439
430
  search_terms=search_terms,
440
431
  areas=areas,
441
432
  filter_expired=filter_expired,
@@ -447,11 +438,11 @@ class CardholdersAPI:
447
438
  **cardholder_kwargs)
448
439
  if isinstance(batch, list):
449
440
  card_holders.extend(batch)
450
-
451
- if (offset + batch_limit) >= limit:
452
- break
453
- elif len(card_holders) > offset:
454
- offset = len(card_holders)
441
+ if len(batch) < batch_limit:
442
+ break
443
+ else:
444
+ break
445
+ current_offset = len(card_holders)
455
446
  else:
456
447
  break
457
448
 
@@ -571,9 +562,6 @@ class CardholdersAPI:
571
562
  url_query_params += "$top=" + str(limit) + "&$skip=" + str(offset)
572
563
 
573
564
  code, json_body = self.gp_json_query("GET", url=(url + url_query_params))
574
- # Check response body is formatted correctly
575
- # if json_body:
576
- # GuardPointResponse.check_odata_body_structure(json_body)
577
565
 
578
566
  if code != 200:
579
567
  error_msg = GuardPointResponse.extract_error_msg(json_body)
@@ -72,7 +72,7 @@ class CardsAPI:
72
72
  if code == 401:
73
73
  raise GuardPointUnauthorized(f"Unauthorized - ({error_msg})")
74
74
  elif code == 404: # Not Found
75
- raise GuardPointError(f"Cardholder Not Found")
75
+ raise GuardPointError(f"Cards Not Found")
76
76
  else:
77
77
  raise GuardPointError(f"{error_msg}")
78
78
 
@@ -244,7 +244,10 @@ class CardsAPI:
244
244
  if 'value' not in json_body:
245
245
  raise GuardPointError("Badly formatted response.")
246
246
 
247
- return Card(json_body['value'])
247
+ value = json_body['value']
248
+ if isinstance(value, list):
249
+ return Card(value[0]) if value else None
250
+ return Card(value)
248
251
 
249
252
  def get_cardholder_by_card_code(self, card_code):
250
253
  """
@@ -33,7 +33,10 @@ class DepartmentsAPI:
33
33
  if 'value' not in json_body:
34
34
  raise GuardPointError("Badly formatted response.")
35
35
 
36
- return Department(json_body['value'])
36
+ value = json_body['value']
37
+ if isinstance(value, list):
38
+ return Department(value[0]) if value else None
39
+ return Department(value)
37
40
 
38
41
  def get_departments(self):
39
42
  url = "/odata/API_Departments"
@@ -141,5 +141,8 @@ class ReadersAPI:
141
141
  if 'value' not in json_body:
142
142
  raise GuardPointError("Badly formatted response.")
143
143
 
144
- return Reader(json_body['value'])
144
+ value = json_body['value']
145
+ if isinstance(value, list):
146
+ return Reader(value[0]) if value else None
147
+ return Reader(value)
145
148
 
@@ -93,29 +93,36 @@ def _compose_filter(search_words=None,
93
93
  if isinstance(v, str):
94
94
  v = v.replace("'", "''")
95
95
  if validators.uuid(v):
96
- filter_phrases.append(f"({k}%20eq%20{quote(v)})")
96
+ filter_phrases.append(f"({k}%20eq%20{v})")
97
97
  else:
98
98
  filter_phrases.append(f"({k}%20eq%20'{quote(v)}')")
99
99
  if isinstance(v, (bool, int)):
100
- filter_phrases.append(f"({k}%20eq%20{quote(v)})")
100
+ filter_phrases.append(f"({k}%20eq%20{v})")
101
101
  if k == "cardholderPersonalDetail" and v.__class__.__name__ == "CardholderPersonalDetail":
102
102
  details = v.dict(changed_only=True)
103
103
  for dk, dv in details.items():
104
104
  if isinstance(dv, str):
105
105
  dv = dv.replace("'", "''")
106
- filter_phrases.append(f"(CardholderPersonalDetail/{dk}%20eq%20'{quote(dv)}')")
106
+ filter_phrases.append(f"(CardholderPersonalDetail/{dk}%20eq%20'{quote(dv)}')")
107
+ elif isinstance(dv, (bool, int)):
108
+ filter_phrases.append(f"(CardholderPersonalDetail/{dk}%20eq%20{dv})")
109
+ elif isinstance(dv, type(None)):
110
+ filter_phrases.append(f"(CardholderPersonalDetail/{dk}%20eq%20null)")
111
+ else:
112
+ dv_str = str(dv).replace("'", "''")
113
+ filter_phrases.append(f"(CardholderPersonalDetail/{dk}%20eq%20'{quote(dv_str)}')")
107
114
  if k == "cardholderCustomizedField" and v.__class__.__name__ == "CardholderCustomizedField":
108
115
  details = v.dict(changed_only=True)
109
116
  for dk, dv in details.items():
110
117
  if isinstance(dv, type(None)):
111
- filter_phrases.append(f"(CardholderCustomizedField/{dk}%20eq%20{quote('null')})")
118
+ filter_phrases.append(f"(CardholderCustomizedField/{dk}%20eq%20null)")
112
119
  elif isinstance(dv, (bool, int)):
113
120
  filter_phrases.append(f"(CardholderCustomizedField/{dk}%20eq%20{str(dv).lower()})")
114
121
  elif isinstance(dv, str):
115
122
  dv = dv.replace("'", "''")
116
123
  filter_phrases.append(f"(CardholderCustomizedField/{dk}%20eq%20'{quote(dv)}')")
117
124
  else:
118
- filter_phrases.append(f"(CardholderCustomizedField/{dk}%20eq%20'{quote(dv)}')")
125
+ filter_phrases.append(f"(CardholderCustomizedField/{dk}%20eq%20'{str(dv)}')")
119
126
 
120
127
  if earliest_last_pass:
121
128
  if isinstance(earliest_last_pass, datetime):
@@ -136,7 +143,7 @@ def _compose_filter(search_words=None,
136
143
  if areas:
137
144
  if isinstance(areas, Area):
138
145
  filter_phrases.append(f"(insideAreaUID%20eq%20{areas.uid})")
139
- if isinstance(areas, list):
146
+ elif isinstance(areas, list):
140
147
  if len(areas) > 0:
141
148
  area_phrases = []
142
149
  for area in areas:
@@ -12,11 +12,20 @@ def fuzzy_match(search_words: str, cardholders: list, threshold: int = 75):
12
12
 
13
13
  match_ratios = process.extract(search_words, cardholder_patterns, scorer=fuzz.WRatio, limit=20)
14
14
 
15
+ pattern_to_indices = {}
16
+ for idx, pattern in enumerate(cardholder_patterns):
17
+ if pattern not in pattern_to_indices:
18
+ pattern_to_indices[pattern] = []
19
+ pattern_to_indices[pattern].append(idx)
20
+
15
21
  sorted_cardholders = []
22
+ seen = set()
16
23
  for match in match_ratios:
17
24
  if match[1] >= threshold:
18
- pos = cardholder_patterns.index(match[0])
19
- sorted_cardholders.append(cardholders[pos])
25
+ for pos in pattern_to_indices.get(match[0], []):
26
+ if pos not in seen:
27
+ sorted_cardholders.append(cardholders[pos])
28
+ seen.add(pos)
20
29
 
21
30
  return sorted_cardholders
22
31
 
@@ -59,9 +59,10 @@ class GuardPointConnection:
59
59
  if self.session:
60
60
  await self.session.close()
61
61
 
62
- async def reopen(self):
62
+ def reopen(self):
63
63
  conn = aiohttp.TCPConnector(ssl_context=self.ssl_context)
64
64
  self.session = aiohttp.ClientSession(connector=conn)
65
+
65
66
  def open(self, url_components, auth, user, pwd, key, token=None,
66
67
  cert_file=None, key_file=None, key_pwd="", ca_file=None, p12_file=None, p12_pwd="", timeout=5):
67
68
  self.ssl_context = None
@@ -116,7 +117,8 @@ class GuardPointConnection:
116
117
 
117
118
  if cert_file and key_file:
118
119
  # Loading of client certificate
119
- self.ssl_context.load_cert_chain(certfile=cert_file, keyfile=key_file, password=key_pwd)
120
+ pwd_bytes = key_pwd.encode() if isinstance(key_pwd, str) else key_pwd
121
+ self.ssl_context.load_cert_chain(certfile=cert_file, keyfile=key_file, password=pwd_bytes if pwd_bytes else None)
120
122
 
121
123
  if ca_file:
122
124
  # Loading of CA certificate.
@@ -172,11 +174,11 @@ class GuardPointConnection:
172
174
  if code != 200:
173
175
  return code, auth_body
174
176
  if self.auto_renew:
175
- if self.token_expiry < (time.time() - (20 * 60)): # If Token will expire within 20 minutes
177
+ if self.token_expiry < (time.time() + (20 * 60)) and self.token_expiry > time.time():
176
178
  code, auth_body = await self._renew_token()
177
179
  if code != 200:
178
180
  return code, auth_body
179
- if self.token_expiry < time.time():
181
+ elif self.token_expiry < time.time():
180
182
  code, auth_body = await self._new_token()
181
183
  if code != 200:
182
184
  return code, auth_body
@@ -220,54 +222,70 @@ class GuardPointConnection:
220
222
  json_body = None
221
223
  except InvalidURL as e:
222
224
  log.error(str(e))
223
- return
225
+ return 400, {"error": str(e)}
224
226
  except Exception as e:
225
227
  log.error(str(e))
226
- return
228
+ return 500, {"error": str(e)}
227
229
 
228
230
  elif method.lower() == "post":
229
- async with self.session.post(url, data=raw_body, headers=headers) as response:
230
- body = await response.text()
231
- try:
232
- json_body = json.loads(body)
233
- except JSONDecodeError:
234
- json_body = None
235
- except Exception as e:
236
- log.error(e)
237
- json_body = None
231
+ try:
232
+ async with self.session.post(url, data=raw_body, headers=headers) as response:
233
+ body = await response.text()
234
+ try:
235
+ json_body = json.loads(body)
236
+ except JSONDecodeError:
237
+ json_body = None
238
+ except Exception as e:
239
+ log.error(e)
240
+ json_body = None
241
+ except Exception as e:
242
+ log.error(str(e))
243
+ return 500, {"error": str(e)}
238
244
 
239
245
  elif method.lower() == "patch":
240
- async with self.session.patch(url, data=raw_body, headers=headers) as response:
241
- body = await response.text()
242
- try:
243
- json_body = json.loads(body)
244
- except JSONDecodeError:
245
- json_body = None
246
- except Exception as e:
247
- log.error(e)
248
- json_body = None
246
+ try:
247
+ async with self.session.patch(url, data=raw_body, headers=headers) as response:
248
+ body = await response.text()
249
+ try:
250
+ json_body = json.loads(body)
251
+ except JSONDecodeError:
252
+ json_body = None
253
+ except Exception as e:
254
+ log.error(e)
255
+ json_body = None
256
+ except Exception as e:
257
+ log.error(str(e))
258
+ return 500, {"error": str(e)}
249
259
 
250
260
  elif method.lower() == "delete":
251
- async with self.session.delete(url, data=raw_body, headers=headers) as response:
252
- body = await response.text()
253
- try:
254
- json_body = json.loads(body)
255
- except JSONDecodeError:
256
- json_body = None
257
- except Exception as e:
258
- log.error(e)
259
- json_body = None
261
+ try:
262
+ async with self.session.delete(url, data=raw_body, headers=headers) as response:
263
+ body = await response.text()
264
+ try:
265
+ json_body = json.loads(body)
266
+ except JSONDecodeError:
267
+ json_body = None
268
+ except Exception as e:
269
+ log.error(e)
270
+ json_body = None
271
+ except Exception as e:
272
+ log.error(str(e))
273
+ return 500, {"error": str(e)}
260
274
 
261
275
  elif method.lower() == "put":
262
- async with self.session.put(url, data=raw_body, headers=headers) as response:
263
- body = await response.text()
264
- try:
265
- json_body = json.loads(body)
266
- except JSONDecodeError:
267
- json_body = None
268
- except Exception as e:
269
- log.error(e)
270
- json_body = None
276
+ try:
277
+ async with self.session.put(url, data=raw_body, headers=headers) as response:
278
+ body = await response.text()
279
+ try:
280
+ json_body = json.loads(body)
281
+ except JSONDecodeError:
282
+ json_body = None
283
+ except Exception as e:
284
+ log.error(e)
285
+ json_body = None
286
+ except Exception as e:
287
+ log.error(str(e))
288
+ return 500, {"error": str(e)}
271
289
 
272
290
  else:
273
291
  raise ValueError("Method Not Supported")
@@ -108,7 +108,8 @@ class GuardPointConnection:
108
108
 
109
109
  if cert_file and key_file:
110
110
  # Loading of client certificate
111
- self.ssl_context.load_cert_chain(certfile=cert_file, keyfile=key_file, password=p12_pwd)
111
+ pwd_bytes = p12_pwd.encode() if isinstance(p12_pwd, str) else p12_pwd
112
+ self.ssl_context.load_cert_chain(certfile=cert_file, keyfile=key_file, password=pwd_bytes if pwd_bytes else None)
112
113
 
113
114
  if ca_file:
114
115
  # Loading of CA certificate.
@@ -185,11 +186,11 @@ class GuardPointConnection:
185
186
  if code != 200:
186
187
  return code, auth_body
187
188
  if self.auto_renew:
188
- if self.token_expiry < (time.time() - (20 * 60)): # If Token will expire within 20 minutes
189
+ if self.token_expiry < (time.time() + (20 * 60)) and self.token_expiry > time.time():
189
190
  code, auth_body = self._renew_token()
190
191
  if code != 200:
191
192
  return code, auth_body
192
- if self.token_expiry < time.time():
193
+ elif self.token_expiry < time.time():
193
194
  code, auth_body = self._new_token()
194
195
  if code != 200:
195
196
  return code, auth_body
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import threading
2
3
  from collections import defaultdict
3
4
  from dataclasses import dataclass, asdict, field
4
5
  from enum import Enum
@@ -18,20 +19,21 @@ class EventOrder(Enum):
18
19
 
19
20
 
20
21
  class Observable:
21
- # A set of all attributes which get changed
22
- changed_attributes = set()
23
-
24
22
  def __init__(self):
25
- self.observed = defaultdict(list)
23
+ super().__setattr__('changed_attributes', set())
24
+ super().__setattr__('observed', defaultdict(list))
25
+ super().__setattr__('_observer_lock', threading.RLock())
26
26
 
27
27
  def __setattr__(self, name, value):
28
28
  super().__setattr__(name, value)
29
29
 
30
- for observer in self.observed.get(name, []):
31
- observer(name)
30
+ with self._observer_lock:
31
+ for observer in self.observed.get(name, []):
32
+ observer(name)
32
33
 
33
34
  def add_observer(self, name):
34
- self.observed[name].append(lambda name: self.changed_attributes.add(name))
35
+ with self._observer_lock:
36
+ self.observed[name].append(lambda attr_name: self.changed_attributes.add(attr_name))
35
37
 
36
38
 
37
39
  def sanitise_args(obj: Observable, args, kwargs):
@@ -54,12 +56,12 @@ def sanitise_args(obj: Observable, args, kwargs):
54
56
 
55
57
 
56
58
  class SortAlgorithm(Enum):
57
- SERVER_DEFAULT = 0,
59
+ SERVER_DEFAULT = 0
58
60
  FUZZY_MATCH = 1
59
61
 
60
62
 
61
63
  class CardholderOrderBy(Enum):
62
- fromDateValid_DESC = 0,
64
+ fromDateValid_DESC = 0
63
65
  lastPassDate_DESC = 1
64
66
 
65
67
 
@@ -97,6 +99,7 @@ class CustomizedField:
97
99
  cf_dict[k] = None
98
100
  else:
99
101
  cf_dict[k] = str(v)
102
+ return cf_dict
100
103
 
101
104
  @dataclass
102
105
  class WeeklyProgram:
@@ -122,6 +125,7 @@ class WeeklyProgram:
122
125
  me_dict[k] = None
123
126
  else:
124
127
  me_dict[k] = str(v)
128
+ return me_dict
125
129
 
126
130
 
127
131
  @dataclass
@@ -148,6 +152,7 @@ class ManualEvent:
148
152
  me_dict[k] = None
149
153
  else:
150
154
  me_dict[k] = str(v)
155
+ return me_dict
151
156
 
152
157
 
153
158
  @dataclass
@@ -156,12 +161,12 @@ class Input:
156
161
  logicalStatus: str = ""
157
162
  isUnderAlarm: bool = False
158
163
  uid: str = ""
159
- number: int = 0,
164
+ number: int = 0
160
165
  name: str = ""
161
166
  descriprion: any = None
162
167
  weeklyProgramUID: any = None
163
168
  delayType: str = ""
164
- delayTime: int = 0,
169
+ delayTime: int = 0
165
170
  inputType: str = ""
166
171
  statusType: str = ""
167
172
  controllerUID: str = ""
@@ -170,12 +175,12 @@ class Input:
170
175
  lastEventType: any = None
171
176
  latestAction: any = None
172
177
  inputGroupUID: any = None
173
- alarmPriority: int = 0,
178
+ alarmPriority: int = 0
174
179
  isArm: bool = False
175
180
  isBypassed: bool = False
176
181
  instructions: any = None
177
182
  isGalaxy: bool = False
178
- omitted: int = 0,
183
+ omitted: int = 0
179
184
  apiKey: any = None
180
185
 
181
186
  def __init__(self, *args, **kwargs):
@@ -195,6 +200,7 @@ class Input:
195
200
  input_dict[k] = None
196
201
  else:
197
202
  input_dict[k] = str(v)
203
+ return input_dict
198
204
 
199
205
 
200
206
  @dataclass
@@ -231,6 +237,7 @@ class AlarmState:
231
237
  alarm_state_dict[k] = None
232
238
  else:
233
239
  alarm_state_dict[k] = str(v)
240
+ return alarm_state_dict
234
241
 
235
242
 
236
243
  @dataclass
@@ -542,7 +549,7 @@ class AccessEvent:
542
549
  accessDeniedCode: str = ""
543
550
  cardCode: str = ""
544
551
  cardholderFirstName: any = None
545
- cardholderIdNumber: str = None
552
+ cardholderIdNumber: any = None
546
553
  cardholderLastName: any = None
547
554
  cardholderTypeName: any = None
548
555
  cardholderTypeUID: any = None
@@ -866,7 +873,7 @@ class ScheduledMag(Observable):
866
873
  @dataclass
867
874
  class CardholderCustomizedField(Observable):
868
875
  uid: str = ""
869
- cF_BoolField_1: bool = False,
876
+ cF_BoolField_1: bool = False
870
877
  cF_BoolField_2: bool = False
871
878
  cF_BoolField_3: bool = False
872
879
  cF_BoolField_4: bool = False
@@ -1181,8 +1188,8 @@ class Cardholder(Observable):
1181
1188
 
1182
1189
  for property_name in cardholder_dict:
1183
1190
  if isinstance(cardholder_dict[property_name], list):
1184
- if property_name == "accessGroupUIDs":
1185
- setattr(self, property_name, ";".join(cardholder_dict[property_name]))
1191
+ if property_name == "accessGroupUIDs" or property_name == "liftAccessGroupUIDs":
1192
+ setattr(self, property_name, cardholder_dict[property_name])
1186
1193
  elif property_name == "cards":
1187
1194
  setattr(self, property_name, [])
1188
1195
  for card_entry in cardholder_dict[property_name]:
@@ -1219,7 +1226,7 @@ class Cardholder(Observable):
1219
1226
  try:
1220
1227
  if isinstance(getattr(self, property_name), str):
1221
1228
  value = str(value)
1222
- except:
1229
+ except AttributeError:
1223
1230
  pass
1224
1231
 
1225
1232
 
@@ -6,19 +6,29 @@ from .guardpoint_error import GuardPointError
6
6
 
7
7
 
8
8
  def url_parser(url):
9
+ if not url or not isinstance(url, str):
10
+ raise GuardPointError("Invalid URL: must be a non-empty string")
11
+
9
12
  parts = urlparse(url)
10
- directories = parts.path.strip('/').split('/')
11
- queries = parts.query.strip('&').split('&')
13
+
14
+ if not parts.netloc:
15
+ raise GuardPointError("Invalid URL: missing host")
16
+
17
+ directories = parts.path.strip('/').split('/') if parts.path else ['']
18
+ queries = parts.query.strip('&').split('&') if parts.query else []
12
19
  host = parts.netloc.strip(':').split(':')[0]
13
20
 
21
+ if not host:
22
+ raise GuardPointError("Invalid URL: empty host")
23
+
14
24
  elements = {
15
- 'scheme': parts.scheme,
25
+ 'scheme': parts.scheme or '',
16
26
  'host': host,
17
- 'path': parts.path,
18
- 'params': parts.params,
19
- 'query': parts.query,
27
+ 'path': parts.path or '',
28
+ 'params': parts.params or '',
29
+ 'query': parts.query or '',
20
30
  'port': parts.port,
21
- 'fragment': parts.fragment,
31
+ 'fragment': parts.fragment or '',
22
32
  'directories': directories,
23
33
  'queries': queries,
24
34
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyGuardPoint
3
- Version: 2.1.0
3
+ Version: 2.1.2
4
4
  Summary: Python wrapper for GuardPoint 10 Access Control System
5
5
  Author: John Owen
6
6
  Maintainer-email: sales@sensoraccess.co.uk
@@ -5,7 +5,7 @@ long_description = open('README.rst').read()
5
5
  setup(name="pyGuardPoint",
6
6
  python_requires='>3.9.0',
7
7
  packages=find_packages(),
8
- version="2.1.0",
8
+ version="2.1.2",
9
9
  author="John Owen",
10
10
  description="Python wrapper for GuardPoint 10 Access Control System",
11
11
  long_description_content_type='text/markdown',
File without changes
File without changes
File without changes