ihcsdk 2.8.7__py3-none-any.whl → 2.8.11__py3-none-any.whl

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.
ihcsdk/__init__.py CHANGED
@@ -1 +1 @@
1
- """Init file for ihcsdk """
1
+ """Init file for ihcsdk."""
ihcsdk/ihcclient.py CHANGED
@@ -1,12 +1,13 @@
1
- """
2
- Implements the connection to the ihc controller
3
- """
1
+ """Implements the connection to the ihc controller."""
4
2
 
5
3
  # pylint: disable=bare-except
6
4
  import base64
7
5
  import datetime
8
6
  import io
7
+ import xml.etree.ElementTree as ET
9
8
  import zlib
9
+ from typing import Any, ClassVar, Literal
10
+
10
11
  from ihcsdk.ihcconnection import IHCConnection
11
12
  from ihcsdk.ihcsslconnection import IHCSSLConnection
12
13
 
@@ -14,17 +15,17 @@ IHCSTATE_READY = "text.ctrl.state.ready"
14
15
 
15
16
 
16
17
  class IHCSoapClient:
17
- """Implements a limited set of the soap request for the IHC controller"""
18
+ """Implements a limited set of the soap request for the IHC controller."""
18
19
 
19
- ihcns = {
20
+ ihcns: ClassVar[dict[str, str]] = {
20
21
  "SOAP-ENV": "http://schemas.xmlsoap.org/soap/envelope/",
21
22
  "ns1": "utcs",
22
23
  "ns2": "utcs.values",
23
24
  "ns3": "utcs.values",
24
25
  }
25
26
 
26
- def __init__(self, url: str):
27
- """Initialize the IIHCSoapClient with a url for the controller"""
27
+ def __init__(self, url: str) -> None:
28
+ """Initialize the IIHCSoapClient with a url for the controller."""
28
29
  self.url = url
29
30
  self.username = ""
30
31
  self.password = ""
@@ -33,10 +34,17 @@ class IHCSoapClient:
33
34
  else:
34
35
  self.connection = IHCConnection(url)
35
36
 
37
+ def close(self) -> None:
38
+ """Close the connection."""
39
+ self.connection.close()
40
+ self.connection = None
41
+
36
42
  def authenticate(self, username: str, password: str) -> bool:
37
- """Do an Authentricate request and save the cookie returned to be used
38
- on the following requests.
39
- Return True if the request was successfull
43
+ """
44
+ Do an Authentricate request.
45
+
46
+ And save the cookie returned to be used on the following requests.
47
+ Return True if the request was successfull.
40
48
  """
41
49
  self.username = username
42
50
  self.password = password
@@ -61,7 +69,7 @@ class IHCSoapClient:
61
69
  return False
62
70
 
63
71
  def get_state(self) -> str:
64
- """Get the controller state"""
72
+ """Get the controller state."""
65
73
  xdoc = self.connection.soap_action("/ws/ControllerService", "getState", "")
66
74
  if xdoc is not False:
67
75
  return xdoc.find(
@@ -69,18 +77,16 @@ class IHCSoapClient:
69
77
  ).text
70
78
  return False
71
79
 
72
- def wait_for_state_change(self, state: str, waitsec) -> str:
73
- """Wait for controller state change and return state"""
74
- payload = """<ns1:waitForControllerStateChange1
80
+ def wait_for_state_change(self, state: str, waitsec: int) -> str:
81
+ """Wait for controller state change and return state."""
82
+ payload = f"""<ns1:waitForControllerStateChange1
75
83
  xmlns:ns1=\"utcs\" xsi:type=\"ns1:WSControllerState\">
76
84
  <ns1:state xsi:type=\"xsd:string\">{state}</ns1:state>
77
85
  </ns1:waitForControllerStateChange1>
78
86
  <ns2:waitForControllerStateChange2
79
87
  xmlns:ns2=\"utcs\" xsi:type=\"xsd:int\">
80
- {wait}</ns2:waitForControllerStateChange2>
81
- """.format(
82
- state=state, wait=waitsec
83
- )
88
+ {waitsec}</ns2:waitForControllerStateChange2>
89
+ """
84
90
  xdoc = self.connection.soap_action(
85
91
  "/ws/ControllerService", "waitForControllerStateChange", payload
86
92
  )
@@ -92,9 +98,11 @@ class IHCSoapClient:
92
98
  return False
93
99
 
94
100
  def get_project(self) -> str:
95
- """Get the ihc project in single SOAP action.
96
- You should use the get_project_in_segments to get the project in multiple segments.
97
- This will stress the IHC controller less
101
+ """
102
+ Get the ihc project in single SOAP action.
103
+
104
+ You should use the get_project_in_segments to get the project in multiple
105
+ segments. This will stress the IHC controller less.
98
106
  """
99
107
  xdoc = self.connection.soap_action("/ws/ControllerService", "getIHCProject", "")
100
108
  if xdoc is not False:
@@ -109,25 +117,31 @@ class IHCSoapClient:
109
117
  )
110
118
  return False
111
119
 
112
- def get_project_in_segments(self, info=None) -> str:
113
- """Get the ihc project per segments.
114
- Param: info .. reuse existing project info. If not provided, the get_project_info() is called internally.
120
+ def get_project_in_segments(self, info: dict[str, Any] | None = None) -> str:
121
+ """
122
+ Get the ihc project per segments.
123
+
124
+ Param: info .. reuse existing project info.
125
+ If not provided, the get_project_info() is called internally.
115
126
  """
116
127
  if info is None:
117
128
  info = self.get_project_info()
118
129
  if info:
119
- projectMajor = info.get("projectMajorRevision", 0)
120
- projectMinor = info.get("projectMinorRevision", 0)
130
+ project_major = info.get("projectMajorRevision", 0)
131
+ project_minor = info.get("projectMinorRevision", 0)
121
132
  buffer = io.BytesIO()
122
133
  for s in range(self.get_project_number_of_segments()):
123
- buffer.write(self.get_project_segment(s, projectMajor, projectMinor))
134
+ segment = self.get_project_segment(s, project_major, project_minor)
135
+ if segment is False:
136
+ return False
137
+ buffer.write(segment)
124
138
  return zlib.decompress(buffer.getvalue(), 16 + zlib.MAX_WBITS).decode(
125
139
  "ISO-8859-1"
126
140
  )
127
141
  return False
128
142
 
129
- def get_project_info(self) -> dict:
130
- """Returns dictionary of project info items."""
143
+ def get_project_info(self) -> dict[str, Any]:
144
+ """Return dictionary of project info items."""
131
145
  xdoc = self.connection.soap_action(
132
146
  "/ws/ControllerService", "getProjectInfo", ""
133
147
  )
@@ -142,7 +156,7 @@ class IHCSoapClient:
142
156
  return False
143
157
 
144
158
  def get_project_number_of_segments(self) -> int:
145
- """Returns the number of segments needed to fetch the current ihc-project."""
159
+ """Return the number of segments needed to fetch the current ihc-project."""
146
160
  xdoc = self.connection.soap_action(
147
161
  "/ws/ControllerService", "getIHCProjectNumberOfSegments", ""
148
162
  )
@@ -155,20 +169,23 @@ class IHCSoapClient:
155
169
  )
156
170
  return False
157
171
 
158
- def get_project_segment(self, segment: int, projectMajor: int, projectMinor: int):
159
- """Returns a segment of the ihc-project with the given number.
160
- Returns null if the segment number increases above the number of segments available.
161
- The segments are offset from 0.
162
- The project-versions given as parameters are used to indentify the project that should be fetched.
163
- That is, to make sure that you suddenly don't get segments belonging to another project.
172
+ def get_project_segment(
173
+ self, segment: int, project_major: int, project_minor: int
174
+ ) -> bytes:
175
+ """
176
+ Return a segment of the ihc-project with the given number.
177
+
178
+ Returns null if the segment number increases above the number of segments
179
+ available. The segments are offset from 0.
180
+ The project-versions given as parameters are used to indentify the project that
181
+ should be fetched. That is, to make sure that you suddenly don't get segments
182
+ belonging to another project.
164
183
  """
165
- payload = """
184
+ payload = f"""
166
185
  <getIHCProjectSegment1 xmlns="utcs">{segment}</getIHCProjectSegment1>
167
- <getIHCProjectSegment2 xmlns="utcs">{major}</getIHCProjectSegment2>
168
- <getIHCProjectSegment3 xmlns="utcs">{minor}</getIHCProjectSegment3>
169
- """.format(
170
- segment=segment, major=projectMajor, minor=projectMinor
171
- )
186
+ <getIHCProjectSegment2 xmlns="utcs">{project_major}</getIHCProjectSegment2>
187
+ <getIHCProjectSegment3 xmlns="utcs">{project_minor}</getIHCProjectSegment3>
188
+ """
172
189
  xdoc = self.connection.soap_action(
173
190
  "/ws/ControllerService", "getIHCProjectSegment", payload
174
191
  )
@@ -179,29 +196,22 @@ class IHCSoapClient:
179
196
  ).text
180
197
  if not base64:
181
198
  return False
182
- compresseddata = base64.b64decode(base64data)
183
- return compresseddata
199
+ return base64.b64decode(base64data)
184
200
  return False
185
201
 
186
202
  def set_runtime_value_bool(self, resourceid: int, value: bool) -> bool:
187
- """Set a boolean runtime value"""
188
- if value:
189
- boolvalue = "true"
190
- else:
191
- boolvalue = "false"
192
-
193
- payload = """
203
+ """Set a boolean runtime value."""
204
+ boolvalue = "true" if value else "false"
205
+ payload = f"""
194
206
  <setResourceValue1 xmlns=\"utcs\"
195
207
  xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
196
208
  <value i:type=\"a:WSBooleanValue\" xmlns:a=\"utcs.values\">
197
- <a:value>{value}</a:value></value>
209
+ <a:value>{boolvalue}</a:value></value>
198
210
  <typeString/>
199
- <resourceID>{id}</resourceID>
211
+ <resourceID>{resourceid}</resourceID>
200
212
  <isValueRuntime>true</isValueRuntime>
201
213
  </setResourceValue1>
202
- """.format(
203
- id=resourceid, value=boolvalue
204
- )
214
+ """
205
215
  xdoc = self.connection.soap_action(
206
216
  "/ws/ResourceInteractionService", "setResourceValue", payload
207
217
  )
@@ -212,20 +222,18 @@ class IHCSoapClient:
212
222
  return result == "true"
213
223
  return False
214
224
 
215
- def set_runtime_value_int(self, resourceid: int, intvalue: int):
216
- """Set a integer runtime value"""
217
- payload = """
225
+ def set_runtime_value_int(self, resourceid: int, intvalue: int) -> bool:
226
+ """Set a integer runtime value."""
227
+ payload = f"""
218
228
  <setResourceValue1 xmlns=\"utcs\"
219
229
  xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
220
230
  <value i:type=\"a:WSIntegerValue\" xmlns:a=\"utcs.values\">
221
- <a:integer>{value}</a:integer></value>
231
+ <a:integer>{intvalue}</a:integer></value>
222
232
  <typeString/>
223
- <resourceID>{id}</resourceID>
233
+ <resourceID>{resourceid}</resourceID>
224
234
  <isValueRuntime>true</isValueRuntime>
225
235
  </setResourceValue1>
226
- """.format(
227
- id=resourceid, value=intvalue
228
- )
236
+ """
229
237
  xdoc = self.connection.soap_action(
230
238
  "/ws/ResourceInteractionService", "setResourceValue", payload
231
239
  )
@@ -236,21 +244,19 @@ class IHCSoapClient:
236
244
  return result == "true"
237
245
  return False
238
246
 
239
- def set_runtime_value_float(self, resourceid: int, floatvalue: float):
240
- """Set a flot runtime value"""
241
- payload = """
247
+ def set_runtime_value_float(self, resourceid: int, floatvalue: float) -> bool:
248
+ """Set a flot runtime value."""
249
+ payload = f"""
242
250
  <setResourceValue1 xmlns=\"utcs\"
243
251
  xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
244
252
  <value i:type=\"a:WSFloatingPointValue\" xmlns:a=\"utcs.values\">
245
- <a:floatingPointValue>{value}</a:floatingPointValue></value>
253
+ <a:floatingPointValue>{floatvalue}</a:floatingPointValue></value>
246
254
  <typeString/>
247
- <resourceID>{id}</resourceID>
255
+ <resourceID>{resourceid}</resourceID>
248
256
  <isValueRuntime>true</isValueRuntime>
249
257
  </setResourceValue1>
250
258
  </s:Body>
251
- """.format(
252
- id=resourceid, value=floatvalue
253
- )
259
+ """
254
260
  xdoc = self.connection.soap_action(
255
261
  "/ws/ResourceInteractionService", "setResourceValue", payload
256
262
  )
@@ -261,21 +267,19 @@ class IHCSoapClient:
261
267
  return result == "true"
262
268
  return False
263
269
 
264
- def set_runtime_value_timer(self, resourceid: int, timer: int):
265
- """Set a timer runtime value in milliseconds"""
266
- payload = """
270
+ def set_runtime_value_timer(self, resourceid: int, timer: int) -> bool:
271
+ """Set a timer runtime value in milliseconds."""
272
+ payload = f"""
267
273
  <setResourceValue1 xmlns=\"utcs\"
268
274
  xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
269
275
  <value i:type=\"a:WSTimerValue\" xmlns:a=\"utcs.values\">
270
- <a:milliseconds>{value}</a:milliseconds></value>
276
+ <a:milliseconds>{timer}</a:milliseconds></value>
271
277
  <typeString/>
272
- <resourceID>{id}</resourceID>
278
+ <resourceID>{resourceid}</resourceID>
273
279
  <isValueRuntime>true</isValueRuntime>
274
280
  </setResourceValue1>
275
281
  </s:Body>
276
- """.format(
277
- id=resourceid, value=timer
278
- )
282
+ """
279
283
  xdoc = self.connection.soap_action(
280
284
  "/ws/ResourceInteractionService", "setResourceValue", payload
281
285
  )
@@ -288,8 +292,8 @@ class IHCSoapClient:
288
292
 
289
293
  def set_runtime_value_time(
290
294
  self, resourceid: int, hours: int, minutes: int, seconds: int
291
- ):
292
- """Set a time runtime value in hours:minutes:seconds"""
295
+ ) -> bool:
296
+ """Set a time runtime value in hours:minutes:seconds."""
293
297
  payload = f"""
294
298
  <setResourceValue1 xmlns=\"utcs\"
295
299
  xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
@@ -314,15 +318,16 @@ class IHCSoapClient:
314
318
  return result == "true"
315
319
  return False
316
320
 
317
- def get_time(resource_value):
318
-
321
+ @staticmethod
322
+ def _get_time(resource_value: ET.Element) -> datetime.time:
319
323
  hours = int(resource_value.find("./ns2:hours", IHCSoapClient.ihcns).text)
320
324
  minutes = int(resource_value.find("./ns2:minutes", IHCSoapClient.ihcns).text)
321
325
  seconds = int(resource_value.find("./ns2:seconds", IHCSoapClient.ihcns).text)
322
326
 
323
327
  return datetime.time(hours, minutes, seconds)
324
328
 
325
- def get_datetime(resource_value):
329
+ @staticmethod
330
+ def _get_datetime(resource_value: ET.Element) -> datetime.datetime:
326
331
  year = int(resource_value.find("./ns1:year", IHCSoapClient.ihcns).text)
327
332
  month = int(
328
333
  resource_value.find("./ns1:monthWithJanuaryAsOne", IHCSoapClient.ihcns).text
@@ -331,55 +336,74 @@ class IHCSoapClient:
331
336
  hours = int(resource_value.find("./ns1:hours", IHCSoapClient.ihcns).text)
332
337
  minutes = int(resource_value.find("./ns1:minutes", IHCSoapClient.ihcns).text)
333
338
  seconds = int(resource_value.find("./ns1:seconds", IHCSoapClient.ihcns).text)
334
- return datetime.datetime(year, month, day, hours, minutes, seconds)
339
+ return datetime.datetime(year, month, day, hours, minutes, seconds) # noqa: DTZ001
335
340
 
336
- def get_date(resource_value):
341
+ @staticmethod
342
+ def _get_date(resource_value: ET.Element) -> datetime.datetime:
337
343
  year = int(resource_value.find("./ns2:year", IHCSoapClient.ihcns).text)
338
344
  if year == 0:
339
- year = datetime.datetime.today().year
345
+ year = datetime.datetime.today().year # noqa: DTZ002
340
346
  month = int(resource_value.find("./ns2:month", IHCSoapClient.ihcns).text)
341
347
  day = int(resource_value.find("./ns2:day", IHCSoapClient.ihcns).text)
342
- return datetime.datetime(year, month, day)
343
-
344
- def __get_value(resource_value):
345
- """Get a runtime value from the xml base on the type in the xml"""
346
- if resource_value == None:
348
+ return datetime.datetime(year, month, day) # noqa: DTZ001
349
+
350
+ @staticmethod
351
+ def __get_value(
352
+ resource_value: ET.Element,
353
+ ) -> bool | int | float | str | datetime.datetime | None:
354
+ """Get a runtime value from the xml base on the type in the xml."""
355
+ if resource_value is None:
347
356
  return None
348
357
  valuetype = resource_value.attrib[
349
358
  "{http://www.w3.org/2001/XMLSchema-instance}type"
350
359
  ].split(":")[1]
351
- default_fn = lambda v: v.text
352
- result = {
353
- "WSBooleanValue": lambda v: (
354
- v.find("./ns2:value", IHCSoapClient.ihcns).text == "true"
355
- ),
356
- "WSIntegerValue": lambda v: int(
357
- v.find("./ns2:integer", IHCSoapClient.ihcns).text
358
- ),
359
- "WSFloatingPointValue": lambda v: round(
360
- float(v.find("./ns2:floatingPointValue", IHCSoapClient.ihcns).text), 2
361
- ),
362
- "WSEnumValue": lambda v: v.find("./ns2:enumName", IHCSoapClient.ihcns).text,
363
- "WSTimerValue": lambda v: int(
364
- v.find("./ns2:milliseconds", IHCSoapClient.ihcns).text
365
- ),
366
- "WSTimeValue": lambda v: IHCSoapClient.get_time(v),
367
- "WSDate": lambda v: IHCSoapClient.get_datetime(v),
368
- "WSDateValue": lambda v: IHCSoapClient.get_date(v),
369
- "int": lambda v: int(v.text),
370
- }.get(valuetype, default_fn)(resource_value)
371
-
360
+ result = resource_value.text
361
+ match valuetype:
362
+ case "WSBooleanValue":
363
+ result = (
364
+ resource_value.find("./ns2:value", IHCSoapClient.ihcns).text
365
+ == "true"
366
+ )
367
+ case "WSIntegerValue":
368
+ result = int(
369
+ resource_value.find("./ns2:integer", IHCSoapClient.ihcns).text
370
+ )
371
+ case "WSFloatingPointValue":
372
+ result = round(
373
+ float(
374
+ resource_value.find(
375
+ "./ns2:floatingPointValue", IHCSoapClient.ihcns
376
+ ).text
377
+ ),
378
+ 2,
379
+ )
380
+ case "WSEnumValue":
381
+ result = resource_value.find("./ns2:enumName", IHCSoapClient.ihcns).text
382
+ case "WSTimerValue":
383
+ return int(
384
+ resource_value.find("./ns2:milliseconds", IHCSoapClient.ihcns).text
385
+ )
386
+ case "WSTimeValue":
387
+ result = IHCSoapClient._get_time(resource_value)
388
+ case "WSDate":
389
+ result = IHCSoapClient._get_datetime(resource_value)
390
+ case "WSDateValue":
391
+ result = IHCSoapClient._get_date(resource_value)
392
+ case "int":
393
+ result = int(resource_value.text)
372
394
  return result
373
395
 
374
- def get_runtime_value(self, resourceid: int):
375
- """Get runtime value of specified resource it
396
+ def get_runtime_value(
397
+ self, resourceid: int
398
+ ) -> bool | int | float | str | datetime.datetime | None:
399
+ """
400
+ Get runtime value of specified resource id.
401
+
376
402
  The returned value will be boolean, integer or float
377
403
  Return None if resource cannot be found or on error
378
404
  """
379
- payload = """<getRuntimeValue1 xmlns="utcs">{id}</getRuntimeValue1>
380
- """.format(
381
- id=resourceid
382
- )
405
+ payload = f"""<getRuntimeValue1 xmlns="utcs">{resourceid}</getRuntimeValue1>
406
+ """
383
407
  xdoc = self.connection.soap_action(
384
408
  "/ws/ResourceInteractionService", "getResourceValue", payload
385
409
  )
@@ -390,14 +414,17 @@ class IHCSoapClient:
390
414
  )
391
415
  return IHCSoapClient.__get_value(value)
392
416
 
393
- def get_runtime_values(self, resourceids):
394
- """Get runtime values of specified resource ids
395
- Return None if resource cannot be found or on error
417
+ def get_runtime_values(
418
+ self, resourceids: list[int]
419
+ ) -> dict[int, Any] | Literal[False]:
396
420
  """
421
+ Get runtime values of specified resource ids.
397
422
 
423
+ Return None if resource cannot be found or on error
424
+ """
398
425
  idsarr = ""
399
426
  for ihcid in resourceids:
400
- idsarr += "<arrayItem>{id}</arrayItem>".format(id=ihcid)
427
+ idsarr += f"<arrayItem>{ihcid}</arrayItem>"
401
428
  payload = '<getRuntimeValues1 xmlns="utcs">' + idsarr + "</getRuntimeValues1>"
402
429
  xdoc = self.connection.soap_action(
403
430
  "/ws/ResourceInteractionService", "getResourceValues", payload
@@ -412,17 +439,19 @@ class IHCSoapClient:
412
439
  ihcid = item.find("ns1:resourceID", IHCSoapClient.ihcns)
413
440
  if ihcid is None:
414
441
  continue
415
- resourceValue = item.find("./ns1:value", IHCSoapClient.ihcns)
416
- itemValue = IHCSoapClient.__get_value(resourceValue)
417
- if itemValue is not None:
418
- changes[int(ihcid.text)] = itemValue
442
+ resource_value = item.find("./ns1:value", IHCSoapClient.ihcns)
443
+ item_value = IHCSoapClient.__get_value(resource_value)
444
+ if item_value is not None:
445
+ changes[int(ihcid.text)] = item_value
419
446
  return changes
420
447
 
421
- def cycle_bool_value(self, resourceid: int):
422
- """Turn a booelan resource On and back Off
448
+ def cycle_bool_value(self, resourceid: int) -> bool | None:
449
+ """
450
+ Turn a booelan resource On and back Off.
451
+
423
452
  Return None if resource cannot be found or on error
424
453
  """
425
- setBool = (
454
+ set_bool = (
426
455
  "<arrayItem>"
427
456
  '<value xsi:type="ns1:WSBooleanValue">'
428
457
  "<ns1:value>{value}</ns1:value>"
@@ -434,8 +463,8 @@ class IHCSoapClient:
434
463
  )
435
464
  payload = (
436
465
  '<setResourceValues1 xmlns="utcs" xmlns:ns1="utcs.values">'
437
- + setBool.format(value="true", id=resourceid)
438
- + setBool.format(value="false", id=resourceid)
466
+ + set_bool.format(value="true", id=resourceid)
467
+ + set_bool.format(value="false", id=resourceid)
439
468
  + "</setResourceValues1>"
440
469
  )
441
470
  xdoc = self.connection.soap_action(
@@ -445,53 +474,53 @@ class IHCSoapClient:
445
474
  return None
446
475
  return True
447
476
 
448
- def enable_runtime_notification(self, resourceid: int):
449
- """Enable notification for specified resource id"""
477
+ def enable_runtime_notification(self, resourceid: int) -> bool:
478
+ """Enable notification for specified resource id."""
450
479
  return self.enable_runtime_notifications([resourceid])
451
480
 
452
- def enable_runtime_notifications(self, resourceids):
453
- """Enable notification for specified resource ids"""
481
+ def enable_runtime_notifications(self, resourceids: list[int]) -> bool:
482
+ """Enable notification for specified resource ids."""
454
483
  idsarr = ""
455
484
  for ihcid in resourceids:
456
- idsarr += "<a:arrayItem>{id}</a:arrayItem>".format(id=ihcid)
485
+ idsarr += f"<a:arrayItem>{ihcid}</a:arrayItem>"
457
486
 
458
- payload = """<enableRuntimeValueNotifications1 xmlns=\"utcs\"
487
+ payload = f"""<enableRuntimeValueNotifications1 xmlns=\"utcs\"
459
488
  xmlns:a=\"http://www.w3.org/2001/XMLSchema\"
460
489
  xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">
461
- {arr}
490
+ {idsarr}
462
491
  </enableRuntimeValueNotifications1>
463
- """.format(
464
- arr=idsarr
465
- )
492
+ """
466
493
  xdoc = self.connection.soap_action(
467
494
  "/ws/ResourceInteractionService", "enableRuntimeValueNotifications", payload
468
495
  )
469
496
  return xdoc is not False
470
497
 
471
- def wait_for_resource_value_changes(self, wait: int = 10):
498
+ def wait_for_resource_value_changes(
499
+ self, wait: int = 10
500
+ ) -> dict[int, str] | Literal[False]:
472
501
  """
473
- Long polling for changes and return a dictionary with resource:value
474
- for changes (Only last change)
502
+ Long polling for changes.
503
+
504
+ And return a dictionary with resource:value for changes (Only last change)
475
505
  """
476
506
  change_list = self.wait_for_resource_value_change_list(wait)
477
507
  if change_list is False:
478
508
  return False
479
- last_changes = {}
480
- for id, value in change_list:
481
- last_changes[id] = value
482
- return last_changes
509
+ return dict(change_list)
483
510
 
484
- def wait_for_resource_value_change_list(self, wait: int = 10):
511
+ def wait_for_resource_value_change_list(
512
+ self, wait: int = 10
513
+ ) -> list[(int, Any)] | Literal[False]:
485
514
  """
486
- Long polling for changes and return a resource id dictionary with a list of all changes since last poll.
515
+ Long polling for changes.
516
+
517
+ And return a resource id dictionary with a list of all changes since last poll.
487
518
  Return a list of tuples with the id,value
488
519
  """
489
520
  changes = []
490
- payload = """<waitForResourceValueChanges1
491
- xmlns=\"utcs\">{timeout}</waitForResourceValueChanges1>
492
- """.format(
493
- timeout=wait
494
- )
521
+ payload = f"""<waitForResourceValueChanges1
522
+ xmlns=\"utcs\">{wait}</waitForResourceValueChanges1>
523
+ """
495
524
  xdoc = self.connection.soap_action(
496
525
  "/ws/ResourceInteractionService", "getResourceValue", payload
497
526
  )
@@ -512,14 +541,12 @@ class IHCSoapClient:
512
541
  changes.append((int(ihcid.text), value))
513
542
  return changes
514
543
 
515
- def get_user_log(self, language="da"):
516
- """Get the controller state"""
517
- payload = """<getUserLog1 xmlns="utcs" />
544
+ def get_user_log(self, language: str = "da") -> str | Literal[False]:
545
+ """Get the controller state."""
546
+ payload = f"""<getUserLog1 xmlns="utcs" />
518
547
  <getUserLog2 xmlns="utcs">0</getUserLog2>
519
548
  <getUserLog3 xmlns="utcs">{language}</getUserLog3>
520
- """.format(
521
- language=language
522
- )
549
+ """
523
550
  xdoc = self.connection.soap_action(
524
551
  "/ws/ConfigurationService", "getUserLog", payload
525
552
  )
@@ -532,52 +559,47 @@ class IHCSoapClient:
532
559
  return base64.b64decode(base64data).decode("UTF-8")
533
560
  return False
534
561
 
535
- def clear_user_log(self):
536
- """Clear the user log in the controller"""
537
- xdoc = self.connection.soap_action(
538
- "/ws/ConfigurationService", "clearUserLog", ""
539
- )
540
- return
562
+ def clear_user_log(self) -> None:
563
+ """Clear the user log in the controller."""
564
+ self.connection.soap_action("/ws/ConfigurationService", "clearUserLog", "")
541
565
 
542
- def get_system_info(self):
543
- """Get controller system info"""
566
+ def get_system_info(self) -> dict[str, str] | bool:
567
+ """Get controller system info."""
544
568
  xdoc = self.connection.soap_action(
545
569
  "/ws/ConfigurationService", "getSystemInfo", ""
546
570
  )
547
571
  if xdoc is False:
548
572
  return False
549
- info = {
550
- "uptime": IHCSoapClient.__extract_sysinfo(xdoc, "uptime"),
551
- "realtimeclock": IHCSoapClient.__extract_sysinfo(xdoc, "realtimeclock"),
552
- "serial_number": IHCSoapClient.__extract_sysinfo(xdoc, "serialNumber"),
553
- "production_date": IHCSoapClient.__extract_sysinfo(xdoc, "productionDate"),
554
- "brand": IHCSoapClient.__extract_sysinfo(xdoc, "brand"),
555
- "version": IHCSoapClient.__extract_sysinfo(xdoc, "version"),
556
- "hw_revision": IHCSoapClient.__extract_sysinfo(xdoc, "hwRevision"),
557
- "sw_date": IHCSoapClient.__extract_sysinfo(xdoc, "swDate"),
558
- "dataline_version": IHCSoapClient.__extract_sysinfo(
559
- xdoc, "datalineVersion"
560
- ),
561
- "rf_module_software_version": IHCSoapClient.__extract_sysinfo(
573
+ return {
574
+ "uptime": IHCSoapClient._extract_sysinfo(xdoc, "uptime"),
575
+ "realtimeclock": IHCSoapClient._extract_sysinfo(xdoc, "realtimeclock"),
576
+ "serial_number": IHCSoapClient._extract_sysinfo(xdoc, "serialNumber"),
577
+ "production_date": IHCSoapClient._extract_sysinfo(xdoc, "productionDate"),
578
+ "brand": IHCSoapClient._extract_sysinfo(xdoc, "brand"),
579
+ "version": IHCSoapClient._extract_sysinfo(xdoc, "version"),
580
+ "hw_revision": IHCSoapClient._extract_sysinfo(xdoc, "hwRevision"),
581
+ "sw_date": IHCSoapClient._extract_sysinfo(xdoc, "swDate"),
582
+ "dataline_version": IHCSoapClient._extract_sysinfo(xdoc, "datalineVersion"),
583
+ "rf_module_software_version": IHCSoapClient._extract_sysinfo(
562
584
  xdoc, "rfModuleSoftwareVersion"
563
585
  ),
564
- "rf_module_serial_number": IHCSoapClient.__extract_sysinfo(
586
+ "rf_module_serial_number": IHCSoapClient._extract_sysinfo(
565
587
  xdoc, "rfModuleSerialNumber"
566
588
  ),
567
- "application_is_without_viewer": IHCSoapClient.__extract_sysinfo(
589
+ "application_is_without_viewer": IHCSoapClient._extract_sysinfo(
568
590
  xdoc, "applicationIsWithoutViewer"
569
591
  ),
570
- "sms_modem_software_version": IHCSoapClient.__extract_sysinfo(
592
+ "sms_modem_software_version": IHCSoapClient._extract_sysinfo(
571
593
  xdoc, "smsModemSoftwareVersion"
572
594
  ),
573
- "led_dimmer_software_version": IHCSoapClient.__extract_sysinfo(
595
+ "led_dimmer_software_version": IHCSoapClient._extract_sysinfo(
574
596
  xdoc, "ledDimmerSoftwareVersion"
575
597
  ),
576
598
  }
577
- return info
578
599
 
579
- def __extract_sysinfo(xdoc, param) -> str:
580
- """Internal function to extrach a parameter from system info"""
600
+ @staticmethod
601
+ def _extract_sysinfo(xdoc: ET.Element, param: str) -> str:
602
+ """Extract a parameter from system info."""
581
603
  element = xdoc.find(
582
604
  f"./SOAP-ENV:Body/ns1:getSystemInfo1/ns1:{param}", IHCSoapClient.ihcns
583
605
  )
ihcsdk/ihcconnection.py CHANGED
@@ -1,15 +1,21 @@
1
- """Implements soap reqeust using the "requests" module"""
2
- # pylint: disable=too-few-public-methods
3
- import logging
4
- import requests
5
- import xml.etree.ElementTree
1
+ """Implements soap reqeust using the "requests" module."""
6
2
 
3
+ import logging
4
+ import time
5
+ import xml.etree.ElementTree as ET
6
+ from http import HTTPStatus
7
+ from typing import Literal
7
8
  from urllib.parse import urlparse
8
9
 
10
+ import requests
11
+ from requests.adapters import HTTPAdapter
12
+ from urllib3.util import Retry
13
+
9
14
  _LOGGER = logging.getLogger(__name__)
10
15
 
11
- class IHCConnection(object):
12
- """Implements a http connection to the controller"""
16
+
17
+ class IHCConnection:
18
+ """Implements a http connection to the controller."""
13
19
 
14
20
  soapenvelope = """<?xml version=\"1.0\" encoding=\"UTF-8\"?>
15
21
  <s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"
@@ -17,18 +23,37 @@ class IHCConnection(object):
17
23
  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">
18
24
  <s:Body>{body}</s:Body></s:Envelope>"""
19
25
 
20
- def __init__(self, url: str):
21
- """Initialize the IHCConnection with a url for the controller"""
26
+ def __init__(self, url: str) -> None:
27
+ """Initialize the IHCConnection with a url for the controller."""
22
28
  self.url = url
23
29
  self.verify = False
24
30
  self.last_exception = None
25
31
  self.last_response = None
26
32
  self.session = requests.Session()
33
+ self.retries = Retry(
34
+ total=3,
35
+ backoff_factor=0.2,
36
+ status_forcelist=[502, 503, 504],
37
+ allowed_methods={"POST"},
38
+ )
39
+ self.session.mount("http://", HTTPAdapter(max_retries=self.retries))
40
+ # default minimum time between calls in seconds (0 will not rate limit)
41
+ self.min_interval: float = 0.0
42
+ self.last_call_time: float = 0
43
+ self.logtiming = False
27
44
 
28
- def cert_verify(self):
45
+ def close(self) -> None:
46
+ """Close the connection."""
47
+ self.session.close()
48
+ self.session = None
49
+
50
+ def cert_verify(self) -> str | None:
51
+ """Validate the certificate and return the cert file."""
29
52
  return None
30
53
 
31
- def soap_action(self, service, action, payloadbody):
54
+ def soap_action(
55
+ self, service: str, action: str, payloadbody: str
56
+ ) -> ET.Element | Literal[False]:
32
57
  """Do a soap request."""
33
58
  payload = self.soapenvelope.format(body=payloadbody).encode("utf-8")
34
59
  headers = {
@@ -39,7 +64,8 @@ class IHCConnection(object):
39
64
  "SOAPAction": action,
40
65
  }
41
66
  try:
42
- _LOGGER.debug( "soap payload %s",payload)
67
+ self.rate_limit()
68
+ _LOGGER.debug("soap payload %s", payload)
43
69
  self.last_exception = None
44
70
  response = self.session.post(
45
71
  url=self.url + service,
@@ -47,22 +73,35 @@ class IHCConnection(object):
47
73
  data=payload,
48
74
  verify=self.cert_verify(),
49
75
  )
50
- _LOGGER.debug( "soap request response status %d",response.status_code)
51
- except requests.exceptions.RequestException as exp:
52
- _LOGGER.error( "soap request exception %s",exp)
53
- self.last_exception = exp
54
- return False
55
- if response.status_code != 200:
56
- self.last_response = response
57
- return False
58
- try:
59
- _LOGGER.debug( "soap request response %s",response.text)
60
- xdoc = xml.etree.ElementTree.fromstring(response.text)
76
+ _LOGGER.debug("soap request response status %d", response.status_code)
77
+ if response.status_code != HTTPStatus.OK:
78
+ self.last_response = response
79
+ return False
80
+ _LOGGER.debug("soap request response %s", response.text)
81
+ xdoc = ET.fromstring(response.text) # noqa: S314
61
82
  if xdoc is None:
62
83
  return False
63
- except xml.etree.ElementTree.ParseError as exp:
64
- _LOGGER.error( "soap request xml parse error %s",exp)
84
+ except requests.exceptions.RequestException as exp:
85
+ _LOGGER.exception("soap request exception")
86
+ self.last_exception = exp
87
+ except ET.ParseError as exp:
88
+ _LOGGER.exception("soap request xml parse erro")
65
89
  self.last_exception = exp
66
90
  self.last_response = response
67
- return False
68
- return xdoc
91
+ else:
92
+ return xdoc
93
+ return False
94
+
95
+ def rate_limit(self) -> None:
96
+ """Rate limit the calls to this function."""
97
+ current_time: float = time.time()
98
+ time_since_last_call: float = current_time - self.last_call_time
99
+ if self.logtiming:
100
+ _LOGGER.warning("time since last call %f sec", time_since_last_call)
101
+ # If not enough time has passed, sleep for the remaining time
102
+ if time_since_last_call < self.min_interval:
103
+ sleep_time: float = self.min_interval - time_since_last_call
104
+ _LOGGER.debug("Ratelimiting for %f sec", sleep_time)
105
+ time.sleep(sleep_time)
106
+ # Update the last call time and call the function
107
+ self.last_call_time = time.time()
ihcsdk/ihccontroller.py CHANGED
@@ -1,28 +1,36 @@
1
1
  """
2
- Wraps the ihcclient in a more user friendly interface to handle lost connection
2
+ Wraps the ihcclient in a more user friendly interface to handle lost connection.
3
+
3
4
  Notify thread to handle change notifications
4
5
  """
5
6
 
6
7
  # pylint: disable=invalid-name, bare-except, too-many-instance-attributes
7
- from datetime import datetime, timedelta
8
8
  import logging
9
- import requests
10
9
  import threading
11
10
  import time
12
- from ihcsdk.ihcclient import IHCSoapClient, IHCSTATE_READY
11
+ from collections.abc import Callable
12
+ from datetime import datetime, timedelta
13
+ from http import HTTPStatus
14
+ from typing import Any, Literal
15
+
16
+ import requests
17
+
18
+ from ihcsdk.ihcclient import IHCSTATE_READY, IHCSoapClient
13
19
 
14
20
  _LOGGER = logging.getLogger(__name__)
15
21
 
16
22
 
17
23
  class IHCController:
18
24
  """
19
- Implements the notification thread and
25
+ Implements the notification thread.
26
+
20
27
  will re-authenticate if needed.
21
28
  """
22
29
 
23
30
  _mutex = threading.Lock()
24
31
 
25
- def __init__(self, url: str, username: str, password: str):
32
+ def __init__(self, url: str, username: str, password: str) -> None:
33
+ """Initialize the IHC controller with connection data."""
26
34
  self.client = IHCSoapClient(url)
27
35
  self.reauthenticatetimeout = 30
28
36
  self.retryinterval = 10
@@ -35,26 +43,26 @@ class IHCController:
35
43
  self._newnotifyids = []
36
44
  self._project = None
37
45
 
46
+ @staticmethod
38
47
  def is_ihc_controller(url: str) -> bool:
39
48
  """Will return True if the url respods like an IHC controller."""
40
49
  try:
41
50
  client = IHCSoapClient(url)
42
51
  response = client.connection.session.get(
43
- f"{url}/wsdl/controller.wsdl", verify=client.connection.cert_verify()
52
+ f"{url}/wsdl/controller.wsdl", verify=False
44
53
  )
45
- if response.status_code != 200:
54
+ client.close()
55
+ if response.status_code != HTTPStatus.OK:
46
56
  return False
47
57
  if not response.headers["content-type"].startswith("text/xml"):
48
58
  return False
49
- if response.text.find("getIHCProject") < 0:
50
- return False
51
- return True
59
+ return not response.text.find("getIHCProject") < 0
52
60
  except requests.exceptions.RequestException as exp:
53
61
  _LOGGER.warning("is_ihc_controller %s", exp)
54
62
  return False
55
63
 
56
64
  def authenticate(self) -> bool:
57
- """Authenticate and enable the registered notifications"""
65
+ """Authenticate and enable the registered notifications."""
58
66
  with IHCController._mutex:
59
67
  _LOGGER.debug("Authenticating login on ihc controller")
60
68
  if not self.client.authenticate(self._username, self._password):
@@ -65,30 +73,34 @@ class IHCController:
65
73
  self.client.enable_runtime_notifications(self._ihcevents.keys())
66
74
  return True
67
75
 
68
- def disconnect(self):
69
- """Disconnect by stopping the notification thread
70
- TODO call disconnect on ihcclient
71
- """
76
+ def disconnect(self) -> None:
77
+ """Disconnect by stopping the notification thread. And closing the client."""
72
78
  self._notifyrunning = False
73
-
74
- def get_runtime_value(self, ihcid: int):
75
- """Get runtime value with re-authenticate if needed"""
79
+ # wait for notify thread to finish
80
+ while self._notifythread.is_alive():
81
+ time.sleep(0.1) # Optional sleep to prevent busy waiting
82
+ self.client.close()
83
+
84
+ def get_runtime_value(
85
+ self, ihcid: int
86
+ ) -> bool | int | float | str | datetime | None:
87
+ """Get runtime value with re-authenticate if needed."""
76
88
  value = self.client.get_runtime_value(ihcid)
77
89
  if value is not None:
78
90
  return value
79
91
  self.re_authenticate()
80
92
  return self.client.get_runtime_value(ihcid)
81
93
 
82
- def get_runtime_values(self, ihcids):
83
- """Get runtime value with re-authenticate if needed"""
94
+ def get_runtime_values(self, ihcids: list[int]) -> dict[int, Any] | Literal[False]:
95
+ """Get runtime value with re-authenticate if needed."""
84
96
  value = self.client.get_runtime_values(ihcids)
85
97
  if value is not None:
86
98
  return value
87
99
  self.re_authenticate()
88
100
  return self.client.get_runtime_values(ihcids)
89
101
 
90
- def cycle_bool_value(self, resourceid: int):
91
- """Turn a booelan resource On and back Off"""
102
+ def cycle_bool_value(self, resourceid: int) -> bool | None:
103
+ """Turn a booelan resource On and back Off."""
92
104
  value = self.client.cycle_bool_value(resourceid)
93
105
  if value is not None:
94
106
  return value
@@ -96,28 +108,28 @@ class IHCController:
96
108
  return self.client.cycle_bool_value(resourceid)
97
109
 
98
110
  def set_runtime_value_bool(self, ihcid: int, value: bool) -> bool:
99
- """Set bool runtime value with re-authenticate if needed"""
111
+ """Set bool runtime value with re-authenticate if needed."""
100
112
  if self.client.set_runtime_value_bool(ihcid, value):
101
113
  return True
102
114
  self.re_authenticate()
103
115
  return self.client.set_runtime_value_bool(ihcid, value)
104
116
 
105
117
  def set_runtime_value_int(self, ihcid: int, value: int) -> bool:
106
- """Set integer runtime value with re-authenticate if needed"""
118
+ """Set integer runtime value with re-authenticate if needed."""
107
119
  if self.client.set_runtime_value_int(ihcid, value):
108
120
  return True
109
121
  self.re_authenticate()
110
122
  return self.client.set_runtime_value_int(ihcid, value)
111
123
 
112
124
  def set_runtime_value_float(self, ihcid: int, value: float) -> bool:
113
- """Set float runtime value with re-authenticate if needed"""
125
+ """Set float runtime value with re-authenticate if needed."""
114
126
  if self.client.set_runtime_value_float(ihcid, value):
115
127
  return True
116
128
  self.re_authenticate()
117
129
  return self.client.set_runtime_value_float(ihcid, value)
118
130
 
119
131
  def set_runtime_value_timer(self, ihcid: int, value: int) -> bool:
120
- """Set timer runtime value with re-authenticate if needed"""
132
+ """Set timer runtime value with re-authenticate if needed."""
121
133
  if self.client.set_runtime_value_timer(ihcid, value):
122
134
  return True
123
135
  self.re_authenticate()
@@ -126,14 +138,14 @@ class IHCController:
126
138
  def set_runtime_value_time(
127
139
  self, ihcid: int, hours: int, minutes: int, seconds: int
128
140
  ) -> bool:
129
- """Set time runtime value with re-authenticate if needed"""
141
+ """Set time runtime value with re-authenticate if needed."""
130
142
  if self.client.set_runtime_value_time(ihcid, hours, minutes):
131
143
  return True
132
144
  self.re_authenticate()
133
145
  return self.client.set_runtime_value_time(ihcid, hours, minutes, seconds)
134
146
 
135
147
  def get_project(self, insegments: bool = True) -> str:
136
- """Get the ihc project and make sure controller is ready before"""
148
+ """Get the ihc project and make sure controller is ready before."""
137
149
  with IHCController._mutex:
138
150
  if self._project is None:
139
151
  if self.client.get_state() != IHCSTATE_READY:
@@ -146,8 +158,15 @@ class IHCController:
146
158
  self._project = self.client.get_project()
147
159
  return self._project
148
160
 
149
- def add_notify_event(self, resourceid: int, callback, delayed=False):
150
- """Add a notify callback for a specified resource id
161
+ def add_notify_event(
162
+ self,
163
+ resourceid: int,
164
+ callback: Callable[[int, bool | float | str | datetime], None],
165
+ delayed: bool = False,
166
+ ) -> bool:
167
+ """
168
+ Add a notify callback for a specified resource id.
169
+
151
170
  If delayed is set to true the enable request will be send from the
152
171
  notofication thread
153
172
  """
@@ -158,17 +177,16 @@ class IHCController:
158
177
  self._ihcevents[resourceid] = [callback]
159
178
  if delayed:
160
179
  self._newnotifyids.append(resourceid)
161
- else:
162
- if not self.client.enable_runtime_notification(resourceid):
163
- return False
180
+ elif not self.client.enable_runtime_notification(resourceid):
181
+ return False
164
182
  if not self._notifyrunning:
165
183
  self._notifyrunning = True
166
184
  self._notifythread.start()
167
185
 
168
186
  return True
169
187
 
170
- def _notify_fn(self):
171
- """The notify thread function."""
188
+ def _notify_fn(self) -> None:
189
+ """Notify thread function."""
172
190
  _LOGGER.debug("Starting notify thread")
173
191
  while self._notifyrunning:
174
192
  try:
@@ -180,7 +198,7 @@ class IHCController:
180
198
 
181
199
  changes = self.client.wait_for_resource_value_change_list()
182
200
  if changes is False:
183
- self.re_authenticate(True)
201
+ self.re_authenticate(notify=True)
184
202
  continue
185
203
  for ihcid, value in changes:
186
204
  if ihcid in self._ihcevents:
@@ -191,32 +209,33 @@ class IHCController:
191
209
  ):
192
210
  callback(ihcid, value)
193
211
  self._ihcvalues[ihcid] = value
194
- except Exception as exp:
195
- _LOGGER.error("Exception in notify thread %s", exp)
196
- self.re_authenticate(True)
212
+ except Exception:
213
+ _LOGGER.exception("Exception in notify thread")
214
+ self.re_authenticate(notify=True)
197
215
 
198
216
  def re_authenticate(self, notify: bool = False) -> bool:
199
- """Authenticate again after failure.
217
+ """
218
+ Authenticate again after failure.
219
+
200
220
  Keep trying with 10 sec interval. If called from the notify thread
201
221
  we will not have a timeout, but will end if the notify thread has
202
222
  been cancled.
203
223
  Will return True if authentication was successful.
204
224
  """
205
- timeout = datetime.now() + timedelta(seconds=self.reauthenticatetimeout)
225
+ timeout = datetime.now() + timedelta(seconds=self.reauthenticatetimeout) # noqa: DTZ005
206
226
  while True:
207
227
  _LOGGER.debug("Reauthenticating login on ihc controller")
208
228
  if self.authenticate():
209
229
  return True
210
230
  _LOGGER.debug(
211
- "Authenticate failed - Reauthenticating login on ihc controller in 10 sec"
231
+ "Authenticate failed, reauthenticating login on ihc controller in 10s"
212
232
  )
213
233
 
214
234
  # if called from the notify and notify a cancled we do not want to retry
215
235
  if notify:
216
236
  if not self._notifyrunning:
217
237
  return False
218
- else:
219
- if timeout and datetime.now() > timeout:
220
- return False
238
+ elif timeout and datetime.now() > timeout: # noqa: DTZ005
239
+ return False
221
240
  # wait before we try to authenticate again
222
241
  time.sleep(self.retryinterval)
@@ -1,47 +1,50 @@
1
- """Implements soap reqeust using the "requests" module"""
1
+ """Implements soap reqeust using the "requests" module."""
2
+
2
3
  # pylint: disable=too-few-public-methods
3
4
  import os
4
- import requests
5
5
 
6
- from cryptography.x509 import load_pem_x509_certificate
6
+ import requests
7
7
  from cryptography.hazmat.backends import default_backend
8
8
  from cryptography.hazmat.primitives import hashes
9
- from requests.adapters import HTTPAdapter
9
+ from cryptography.x509 import load_pem_x509_certificate
10
10
  from requests.packages.urllib3.util.ssl_ import create_urllib3_context
11
11
 
12
12
  from ihcsdk.ihcconnection import IHCConnection
13
13
 
14
14
 
15
15
  class IHCSSLConnection(IHCConnection):
16
- """Implements a https connection to the controller"""
16
+ """Implements a https connection to the controller."""
17
17
 
18
- def __init__(self, url: str):
19
- """Initialize the IHCSSLConnection with a url for the controller"""
20
- super(IHCSSLConnection, self).__init__(url)
18
+ def __init__(self, url: str) -> None:
19
+ """Initialize the IHCSSLConnection with a url for the controller."""
20
+ super().__init__(url)
21
21
  self.cert_file = os.path.dirname(__file__) + "/certs/ihc3.crt"
22
- self.session.mount("https://", CertAdapter(self.get_fingerprint_from_cert()))
22
+ self.session.mount(
23
+ "https://",
24
+ CertAdapter(self.get_fingerprint_from_cert(), max_retries=self.retries),
25
+ )
23
26
 
24
- def get_fingerprint_from_cert(self):
25
- """Get the fingerprint from the certificate"""
27
+ def get_fingerprint_from_cert(self) -> str:
28
+ """Get the fingerprint from the certificate."""
26
29
  pem = open(self.cert_file, "rb").read()
27
30
  cert = load_pem_x509_certificate(pem, default_backend())
28
31
  f = cert.fingerprint(hashes.SHA1())
29
32
  return "".join("{:02x}".format(x) for x in f)
30
33
 
31
- def cert_verify(self):
34
+ def cert_verify(self) -> str:
32
35
  return self.cert_file
33
36
 
34
37
 
35
38
  class CertAdapter(requests.adapters.HTTPAdapter):
36
- """A adapter for a specific certificate"""
39
+ """A adapter for a specific certificate."""
37
40
 
38
- def __init__(self, fingerprint, **kwargs):
39
- """Constructor. Store the fingerprint for use when creating the poolmanager."""
41
+ def __init__(self, fingerprint, **kwargs) -> None:
42
+ """Store the fingerprint for use when creating the poolmanager."""
40
43
  self.fingerprint = fingerprint
41
- super(CertAdapter, self).__init__(**kwargs)
44
+ super().__init__(**kwargs)
42
45
 
43
46
  def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
44
- """Create a custom poolmanager"""
47
+ """Create a custom poolmanager."""
45
48
  CIPHERS = "DEFAULT:!DH"
46
49
  context = create_urllib3_context(ciphers=CIPHERS)
47
50
  pool_kwargs["ssl_context"] = context
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: ihcsdk
3
- Version: 2.8.7
3
+ Version: 2.8.11
4
4
  Summary: IHC Python SDK
5
5
  Home-page: https://github.com/dingusdk/PythonIhcSdk
6
6
  Author: Jens Nielsen
@@ -8,5 +8,12 @@ License: GPL-3.0
8
8
  License-File: license.txt
9
9
  Requires-Dist: requests
10
10
  Requires-Dist: cryptography
11
+ Dynamic: author
12
+ Dynamic: description
13
+ Dynamic: home-page
14
+ Dynamic: license
15
+ Dynamic: license-file
16
+ Dynamic: requires-dist
17
+ Dynamic: summary
11
18
 
12
19
  SDK for connection to the LK IHC Controller. Made for interfacing to home assistant
@@ -0,0 +1,12 @@
1
+ ihcsdk/__init__.py,sha256=IFv4tTt5vulfQlTqcok2IJA8ms3rE7XR7WIsRM2az9g,28
2
+ ihcsdk/ihcclient.py,sha256=NyTKFaF_jz5x1_fz1bHna22ke7KRm4DukMqn3aE70nM,24970
3
+ ihcsdk/ihcconnection.py,sha256=ISruHcCqusn5KCVSBwE8LNJS-v0aIw89WPCxAQuIk4w,4133
4
+ ihcsdk/ihccontroller.py,sha256=wb-7y5J0arNrlCMi35I6CLYwtKgcJvLNmzaABHqW3Aw,9767
5
+ ihcsdk/ihcsslconnection.py,sha256=OjGo8eJGHFnhVc9nz0Krf6mCCVrOOWj9BpTDOTtxKqk,2063
6
+ ihcsdk/certs/ihc.crt,sha256=VYY_DiHrctlXBTNXGdJ2FN4TYuVnwnpVZ1i8t2_0cec,1002
7
+ ihcsdk/certs/ihc3.crt,sha256=Ka2L8zQ06A76W6fZc0ckScdrj1Cn-mhgqdhc61-cCrk,1398
8
+ ihcsdk-2.8.11.dist-info/licenses/license.txt,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
9
+ ihcsdk-2.8.11.dist-info/METADATA,sha256=qUKqMaLYOBUsmAhn7jIvlGydjIqFz6Ohu_63qUiaG9E,482
10
+ ihcsdk-2.8.11.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
+ ihcsdk-2.8.11.dist-info/top_level.txt,sha256=QgKE7TWblC-uXe5-7MVO5JtzvrPiMoVmGgKKsVR43hU,7
12
+ ihcsdk-2.8.11.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,12 +0,0 @@
1
- ihcsdk/__init__.py,sha256=ocAoa28ZKxGgMlDEHWEsd7n00OtGB0R9N7XQfRlih9Q,28
2
- ihcsdk/ihcclient.py,sha256=KCgat4CnR69Vrb-GdA9tfduS7tQl9ymwqEBf-EPDM1U,24117
3
- ihcsdk/ihcconnection.py,sha256=qnOEEsgKulIUGRS223o9YSy9qmEVR1D6jNt2XxGFGho,2577
4
- ihcsdk/ihccontroller.py,sha256=9D97RX8DrFMLMxDBcIrhoYY-QEeKNjbVv_PiRW0wNyU,9180
5
- ihcsdk/ihcsslconnection.py,sha256=4IFVg1bpdJ25yN76tnFLndvL5Rl9-qbXrbENUjyTfzw,2056
6
- ihcsdk/certs/ihc.crt,sha256=VYY_DiHrctlXBTNXGdJ2FN4TYuVnwnpVZ1i8t2_0cec,1002
7
- ihcsdk/certs/ihc3.crt,sha256=Ka2L8zQ06A76W6fZc0ckScdrj1Cn-mhgqdhc61-cCrk,1398
8
- ihcsdk-2.8.7.dist-info/METADATA,sha256=q8VRbllNSE39vfayX1ui00DurTtIACN24dL7WJ0XVCY,339
9
- ihcsdk-2.8.7.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
10
- ihcsdk-2.8.7.dist-info/license.txt,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
11
- ihcsdk-2.8.7.dist-info/top_level.txt,sha256=QgKE7TWblC-uXe5-7MVO5JtzvrPiMoVmGgKKsVR43hU,7
12
- ihcsdk-2.8.7.dist-info/RECORD,,