PyPlumIO 0.5.25__py3-none-any.whl → 0.5.27__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.
Files changed (37) hide show
  1. {PyPlumIO-0.5.25.dist-info → PyPlumIO-0.5.27.dist-info}/METADATA +7 -7
  2. PyPlumIO-0.5.27.dist-info/RECORD +60 -0
  3. {PyPlumIO-0.5.25.dist-info → PyPlumIO-0.5.27.dist-info}/WHEEL +1 -1
  4. pyplumio/_version.py +2 -2
  5. pyplumio/devices/__init__.py +27 -28
  6. pyplumio/devices/ecomax.py +51 -57
  7. pyplumio/devices/ecoster.py +3 -5
  8. pyplumio/devices/mixer.py +5 -8
  9. pyplumio/devices/thermostat.py +3 -3
  10. pyplumio/filters.py +6 -6
  11. pyplumio/frames/__init__.py +6 -6
  12. pyplumio/frames/messages.py +3 -3
  13. pyplumio/frames/requests.py +18 -18
  14. pyplumio/frames/responses.py +15 -15
  15. pyplumio/helpers/data_types.py +104 -68
  16. pyplumio/helpers/event_manager.py +6 -6
  17. pyplumio/helpers/factory.py +5 -6
  18. pyplumio/helpers/parameter.py +17 -15
  19. pyplumio/helpers/schedule.py +50 -46
  20. pyplumio/helpers/timeout.py +1 -1
  21. pyplumio/protocol.py +6 -7
  22. pyplumio/structures/alerts.py +8 -6
  23. pyplumio/structures/ecomax_parameters.py +30 -26
  24. pyplumio/structures/frame_versions.py +2 -3
  25. pyplumio/structures/mixer_parameters.py +9 -6
  26. pyplumio/structures/mixer_sensors.py +10 -8
  27. pyplumio/structures/modules.py +9 -7
  28. pyplumio/structures/network_info.py +16 -16
  29. pyplumio/structures/program_version.py +3 -0
  30. pyplumio/structures/regulator_data.py +2 -4
  31. pyplumio/structures/regulator_data_schema.py +2 -3
  32. pyplumio/structures/schedules.py +33 -35
  33. pyplumio/structures/thermostat_parameters.py +6 -4
  34. pyplumio/structures/thermostat_sensors.py +13 -10
  35. PyPlumIO-0.5.25.dist-info/RECORD +0 -60
  36. {PyPlumIO-0.5.25.dist-info → PyPlumIO-0.5.27.dist-info}/LICENSE +0 -0
  37. {PyPlumIO-0.5.25.dist-info → PyPlumIO-0.5.27.dist-info}/top_level.txt +0 -0
@@ -66,23 +66,23 @@ class NetworkInfoStructure(Structure):
66
66
 
67
67
  def encode(self, data: dict[str, Any]) -> bytearray:
68
68
  """Encode data to the bytearray message."""
69
- message = bytearray(b"\x01")
70
69
  network_info: NetworkInfo = data.get(ATTR_NETWORK, NetworkInfo())
71
- message += IPv4(network_info.eth.ip).to_bytes()
72
- message += IPv4(network_info.eth.netmask).to_bytes()
73
- message += IPv4(network_info.eth.gateway).to_bytes()
74
- message.append(network_info.eth.status)
75
- message += IPv4(network_info.wlan.ip).to_bytes()
76
- message += IPv4(network_info.wlan.netmask).to_bytes()
77
- message += IPv4(network_info.wlan.gateway).to_bytes()
78
- message.append(network_info.server_status)
79
- message.append(network_info.wlan.encryption)
80
- message.append(network_info.wlan.signal_quality)
81
- message.append(network_info.wlan.status)
82
- message += b"\x00" * 4
83
- message += VarString(network_info.wlan.ssid).to_bytes()
84
-
85
- return message
70
+ return bytearray(
71
+ b"\1"
72
+ + IPv4(network_info.eth.ip).to_bytes()
73
+ + IPv4(network_info.eth.netmask).to_bytes()
74
+ + IPv4(network_info.eth.gateway).to_bytes()
75
+ + network_info.eth.status.to_bytes(length=1, byteorder="little")
76
+ + IPv4(network_info.wlan.ip).to_bytes()
77
+ + IPv4(network_info.wlan.netmask).to_bytes()
78
+ + IPv4(network_info.wlan.gateway).to_bytes()
79
+ + network_info.server_status.to_bytes(length=1, byteorder="little")
80
+ + network_info.wlan.encryption.to_bytes(length=1, byteorder="little")
81
+ + network_info.wlan.signal_quality.to_bytes(length=1, byteorder="little")
82
+ + network_info.wlan.status.to_bytes(length=1, byteorder="little")
83
+ + b"\0" * 4
84
+ + VarString(network_info.wlan.ssid).to_bytes()
85
+ )
86
86
 
87
87
  def decode(
88
88
  self, message: bytearray, offset: int = 0, data: dict[str, Any] | None = None
@@ -6,6 +6,8 @@ from dataclasses import dataclass
6
6
  import struct
7
7
  from typing import Any, Final
8
8
 
9
+ from dataslots import dataslots
10
+
9
11
  from pyplumio._version import __version_tuple__
10
12
  from pyplumio.structures import Structure
11
13
  from pyplumio.utils import ensure_dict
@@ -19,6 +21,7 @@ SOFTWARE_VERSION: Final = ".".join(str(x) for x in __version_tuple__[0:3])
19
21
  struct_program_version = struct.Struct("<2sB2s3s3HB")
20
22
 
21
23
 
24
+ @dataslots
22
25
  @dataclass
23
26
  class VersionInfo:
24
27
  """Represents a version info provided in program version response."""
@@ -36,10 +36,8 @@ class RegulatorDataStructure(StructureDecoder):
36
36
  if isinstance(data_type, BitArray):
37
37
  self._bitarray_index = data_type.next(self._bitarray_index)
38
38
 
39
- try:
40
- return data_type.value
41
- finally:
42
- self._offset += data_type.size
39
+ self._offset += data_type.size
40
+ return data_type.value
43
41
 
44
42
  def decode(
45
43
  self, message: bytearray, offset: int = 0, data: dict[str, Any] | None = None
@@ -21,9 +21,8 @@ class RegulatorDataSchemaStructure(StructureDecoder):
21
21
  def _unpack_block(self, message: bytearray) -> tuple[int, DataType]:
22
22
  """Unpack a block."""
23
23
  param_type = message[self._offset]
24
- self._offset += 1
25
- param_id = UnsignedShort.from_bytes(message, self._offset)
26
- self._offset += param_id.size
24
+ param_id = UnsignedShort.from_bytes(message, self._offset + 1)
25
+ self._offset += param_id.size + 1
27
26
  return param_id.value, DATA_TYPES[param_type]()
28
27
 
29
28
  def decode(
@@ -5,7 +5,6 @@ from __future__ import annotations
5
5
  from collections.abc import Sequence
6
6
  from dataclasses import dataclass
7
7
  from functools import reduce
8
- from itertools import chain
9
8
  from typing import Any, Final
10
9
 
11
10
  from dataslots import dataslots
@@ -17,7 +16,7 @@ from pyplumio.const import (
17
16
  ATTR_TYPE,
18
17
  FrameType,
19
18
  )
20
- from pyplumio.devices import AddressableDevice, Device
19
+ from pyplumio.devices import Device, PhysicalDevice
21
20
  from pyplumio.exceptions import FrameDataError
22
21
  from pyplumio.frames import Request
23
22
  from pyplumio.helpers.parameter import (
@@ -96,7 +95,7 @@ class ScheduleParameter(Parameter):
96
95
 
97
96
  __slots__ = ()
98
97
 
99
- device: AddressableDevice
98
+ device: PhysicalDevice
100
99
  description: ScheduleParameterDescription
101
100
 
102
101
  async def create_request(self) -> Request:
@@ -137,17 +136,14 @@ class ScheduleSwitch(ScheduleParameter, Switch):
137
136
  description: ScheduleSwitchDescription
138
137
 
139
138
 
140
- SCHEDULE_PARAMETERS: list[ScheduleParameterDescription] = list(
141
- chain.from_iterable(
142
- [
143
- [
144
- ScheduleSwitchDescription(name=f"{name}_{ATTR_SCHEDULE_SWITCH}"),
145
- ScheduleNumberDescription(name=f"{name}_{ATTR_SCHEDULE_PARAMETER}"),
146
- ]
147
- for name in SCHEDULES
148
- ]
149
- )
150
- )
139
+ SCHEDULE_PARAMETERS: list[ScheduleParameterDescription] = [
140
+ description
141
+ for name in SCHEDULES
142
+ for description in [
143
+ ScheduleSwitchDescription(name=f"{name}_{ATTR_SCHEDULE_SWITCH}"),
144
+ ScheduleNumberDescription(name=f"{name}_{ATTR_SCHEDULE_PARAMETER}"),
145
+ ]
146
+ ]
151
147
 
152
148
 
153
149
  def collect_schedule_data(name: str, device: Device) -> dict[str, Any]:
@@ -179,31 +175,35 @@ class SchedulesStructure(Structure):
179
175
 
180
176
  def encode(self, data: dict[str, Any]) -> bytearray:
181
177
  """Encode data to the bytearray message."""
182
- message = bytearray([1])
183
178
  try:
184
- message.append(SCHEDULES.index(data[ATTR_TYPE]))
185
- message.append(int(data[ATTR_SWITCH]))
186
- message.append(int(data[ATTR_PARAMETER]))
179
+ header = bytearray(
180
+ b"\1"
181
+ + SCHEDULES.index(data[ATTR_TYPE]).to_bytes(
182
+ length=1, byteorder="little"
183
+ )
184
+ + int(data[ATTR_SWITCH]).to_bytes(length=1, byteorder="little")
185
+ + int(data[ATTR_PARAMETER]).to_bytes(length=1, byteorder="little")
186
+ )
187
187
  schedule = data[ATTR_SCHEDULE]
188
188
  except (KeyError, ValueError) as e:
189
189
  raise FrameDataError from e
190
190
 
191
- return message + bytearray(
192
- chain.from_iterable(
193
- [_join_bits(day[i : i + 8]) for i in range(0, len(day), 8)]
194
- for day in list(schedule)
195
- )
191
+ return header + bytearray(
192
+ _join_bits(day[i : i + 8])
193
+ for day in schedule
194
+ for i in range(0, len(day), 8)
196
195
  )
197
196
 
198
197
  def _unpack_schedule(self, message: bytearray) -> list[list[bool]]:
199
198
  """Unpack a schedule."""
200
- schedule: list[bool] = []
201
- last_offset = self._offset + SCHEDULE_SIZE
202
- while self._offset < last_offset:
203
- schedule += _split_byte(message[self._offset])
204
- self._offset += 1
205
-
206
- # Split schedule. Each day consists of 48 half-hour intervals.
199
+ offset = self._offset
200
+ schedule = [
201
+ bit
202
+ for i in range(offset, offset + SCHEDULE_SIZE)
203
+ for bit in _split_byte(message[i])
204
+ ]
205
+ self._offset = offset + SCHEDULE_SIZE
206
+ # Split the schedule. Each day consists of 48 half-hour intervals.
207
207
  return [schedule[i : i + 48] for i in range(0, len(schedule), 48)]
208
208
 
209
209
  def decode(
@@ -211,17 +211,15 @@ class SchedulesStructure(Structure):
211
211
  ) -> tuple[dict[str, Any], int]:
212
212
  """Decode bytes and return message data and offset."""
213
213
  try:
214
- offset += 1
215
- start = message[offset]
216
- offset += 1
217
- end = message[offset]
214
+ start = message[offset + 1]
215
+ end = message[offset + 2]
218
216
  except IndexError:
219
217
  return ensure_dict(data, {ATTR_SCHEDULES: []}), offset
220
218
 
221
- self._offset = offset + 1
222
219
  schedules: list[tuple[int, list[list[bool]]]] = []
223
220
  parameters: list[tuple[int, ParameterValues]] = []
224
221
 
222
+ self._offset = offset + 3
225
223
  for _ in range(start, start + end):
226
224
  index = message[self._offset]
227
225
  switch = ParameterValues(
@@ -18,7 +18,6 @@ from pyplumio.const import (
18
18
  )
19
19
  from pyplumio.frames import Request
20
20
  from pyplumio.helpers.parameter import (
21
- SET_TIMEOUT,
22
21
  Number,
23
22
  NumberDescription,
24
23
  Parameter,
@@ -48,7 +47,6 @@ class ThermostatParameterDescription(ParameterDescription):
48
47
 
49
48
  __slots__ = ()
50
49
 
51
- multiplier: float = 1.0
52
50
  size: int = 1
53
51
 
54
52
 
@@ -94,6 +92,8 @@ class ThermostatParameter(Parameter):
94
92
  class ThermostatNumberDescription(ThermostatParameterDescription, NumberDescription):
95
93
  """Represent a thermostat number description."""
96
94
 
95
+ multiplier: float = 1.0
96
+
97
97
 
98
98
  class ThermostatNumber(ThermostatParameter, Number):
99
99
  """Represents a thermostat number."""
@@ -102,10 +102,12 @@ class ThermostatNumber(ThermostatParameter, Number):
102
102
 
103
103
  description: ThermostatNumberDescription
104
104
 
105
- async def set(self, value: int | float, retries: int = SET_TIMEOUT) -> bool:
105
+ async def set(
106
+ self, value: int | float, retries: int = 5, timeout: float = 5.0
107
+ ) -> bool:
106
108
  """Set a parameter value."""
107
109
  value = value / self.description.multiplier
108
- return await super().set(value, retries)
110
+ return await super().set(value, retries, timeout)
109
111
 
110
112
  @property
111
113
  def value(self) -> float:
@@ -36,25 +36,28 @@ class ThermostatSensorsStructure(StructureDecoder):
36
36
  self, message: bytearray, contacts: int
37
37
  ) -> dict[str, Any] | None:
38
38
  """Unpack sensors for a thermostat."""
39
- state = message[self._offset]
40
- self._offset += 1
41
- current_temp = Float.from_bytes(message, self._offset)
42
- self._offset += current_temp.size
43
- target_temp = Float.from_bytes(message, self._offset)
44
- self._offset += target_temp.size
39
+ offset = self._offset
40
+ state = message[offset]
41
+ offset += 1
42
+ current_temp = Float.from_bytes(message, offset)
43
+ offset += current_temp.size
44
+ target_temp = Float.from_bytes(message, offset)
45
+ offset += target_temp.size
45
46
 
46
47
  try:
47
- if not math.isnan(current_temp.value) and target_temp.value > 0:
48
- return {
48
+ return (
49
+ {
49
50
  ATTR_STATE: state,
50
51
  ATTR_CURRENT_TEMP: current_temp.value,
51
52
  ATTR_TARGET_TEMP: target_temp.value,
52
53
  ATTR_CONTACTS: bool(contacts & self._contact_mask),
53
54
  ATTR_SCHEDULE: bool(contacts & self._schedule_mask),
54
55
  }
55
-
56
- return None
56
+ if not math.isnan(current_temp.value) and target_temp.value > 0
57
+ else None
58
+ )
57
59
  finally:
60
+ self._offset = offset
58
61
  self._contact_mask <<= 1
59
62
  self._schedule_mask <<= 1
60
63
 
@@ -1,60 +0,0 @@
1
- pyplumio/__init__.py,sha256=ditJTIOFGJDg60atHzOpiggdUrZHpSynno7MtpZUGVk,3299
2
- pyplumio/__main__.py,sha256=3IwHHSq-iay5FaeMc95klobe-xv82yydSKcBE7BFZ6M,500
3
- pyplumio/_version.py,sha256=NdqvDMFnl0hh-C5MLFStVcNIuOZiaethGZ5YrUJK1Bg,413
4
- pyplumio/connection.py,sha256=6mUbcjGxxEhMVIbzZgCqH-Ez-fcYoRj7ZbVSzpikpNA,5949
5
- pyplumio/const.py,sha256=8rpiVbVb5R_6Rm6J2sgCnaVrkD-2Fzhd1RYMz0MBgwo,3915
6
- pyplumio/exceptions.py,sha256=Wn-y5AJ5xfaBlHhTUVKB27_0Us8_OVHqh-sicnr9sYA,700
7
- pyplumio/filters.py,sha256=IZkvrRAHdv6s3CplK73mHomRHpo3rnoyX2u26FVr9XU,11386
8
- pyplumio/protocol.py,sha256=e6-ns5i5DWlFD5tKot-kQtmZt3CJMFK6zFa5RMIKshw,8140
9
- pyplumio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- pyplumio/stream.py,sha256=IVCQFKBtRafRgUkr93p_wN5mXZAD3Jw1d091dfEIK20,4479
11
- pyplumio/utils.py,sha256=TnBzRopinyp92wruguijxcIYmaeyNVTFX0dygI5FCMU,823
12
- pyplumio/devices/__init__.py,sha256=sb4wFJSM-j1rwJGQ0G7RSCF1t9-ZEwKzJl9mFrSr0EI,6564
13
- pyplumio/devices/ecomax.py,sha256=jaIX0LySnryPrwFsYH4ES7U52P3ZaAxTdMKJAJ_XY5Q,16949
14
- pyplumio/devices/ecoster.py,sha256=J4YtPmFmFwaq4LzYf28aMmB97cRAbMsVyUdBLGki42g,313
15
- pyplumio/devices/mixer.py,sha256=pxZXoAOrZ52OxMirZdoCtbJ4b2yHg1A2Kn_QPZZqynQ,3271
16
- pyplumio/devices/thermostat.py,sha256=9jI_cacIQoFq_ImAqAYhcnAx7PVO3FWZz4WdfatGNiw,2607
17
- pyplumio/frames/__init__.py,sha256=uMjLWY0rCbCTBfXafA_TSfLORYBT0wLyhHSZEePsRxw,7504
18
- pyplumio/frames/messages.py,sha256=7vyOjcxGDnaRlyB4jPsCt00yCc3Axme8NN7uK922DS8,3622
19
- pyplumio/frames/requests.py,sha256=Ra8xH5oKYhkEUtadN-9ZsJKkt5xZkz5O7edQVsDhNsM,7221
20
- pyplumio/frames/responses.py,sha256=j4awA2-MfsoPdENC4Fvae4_Oa70rDhH19ebmEoAqhh8,6532
21
- pyplumio/helpers/__init__.py,sha256=H2xxdkF-9uADLwEbfBUoxNTdwru3L5Z2cfJjgsuRsn0,31
22
- pyplumio/helpers/data_types.py,sha256=ADizMys0jq_uKIJvN8KZcL41qBgkkiqy6wSBOrr8mTE,8273
23
- pyplumio/helpers/event_manager.py,sha256=yH_VjEvIkuYwk31qb3rXtFDfKAStbA-YWWc4ED17jVI,6443
24
- pyplumio/helpers/factory.py,sha256=eiTkYUCernUn0VNDDdEN4IyjNPrXK8vnJESXyLaqFzE,1017
25
- pyplumio/helpers/parameter.py,sha256=SSAzIMmx0g5VMurzT-9Ys5ZaMlNbcKxcGfbzZWreXeM,11404
26
- pyplumio/helpers/schedule.py,sha256=-IZJ-CU4PhFlsE586wTw--ovDrTo2Hs4JneCHhc0e-Y,5013
27
- pyplumio/helpers/task_manager.py,sha256=HAd69yGTRL0zQsu-ywnbLu1UXiJzgHWuhYWA--vs4lQ,1181
28
- pyplumio/helpers/timeout.py,sha256=XM58yaz93cNsxW7Ok6hfBw8i_92HdsGFQVBhpqbCZ70,770
29
- pyplumio/helpers/uid.py,sha256=J7gN8i8LE0g6tfL66BJbwsQQqzBBxWx7giyvqaJh4BM,976
30
- pyplumio/structures/__init__.py,sha256=EjK-5qJZ0F7lpP2b6epvTMg9cIBl4Kn91nqNkEcLwTc,1299
31
- pyplumio/structures/alerts.py,sha256=wX58xWr1dJgZiQtEEMLRu8bcu6dTcc-aqEIY69gYGu0,3640
32
- pyplumio/structures/boiler_load.py,sha256=p3mOzZUU-g7A2tG_yp8podEqpI81hlsOZmHELyPNRY8,838
33
- pyplumio/structures/boiler_power.py,sha256=72qsvccg49FdRdXv2f2K5sGpjT7wAOLFjlIGWpO-DVg,901
34
- pyplumio/structures/ecomax_parameters.py,sha256=a7sq2yhUSt6XiZWv48KkClWyjxsGAEJbES13-sx6G78,27710
35
- pyplumio/structures/fan_power.py,sha256=Q5fv-7_2NVuLeQPIVIylvgN7M8-a9D8rRUE0QGjyS3w,871
36
- pyplumio/structures/frame_versions.py,sha256=OMWU8tjnsrRWQsMSbmCJCmiKDwBmA75BcPZ6CqvKMLc,1566
37
- pyplumio/structures/fuel_consumption.py,sha256=_p2dI4H67Eopn7IF0Gj77A8c_8lNKhhDDAtmugxLd4s,976
38
- pyplumio/structures/fuel_level.py,sha256=mJpp1dnRD1wXi_6EyNX7TNXosjcr905rSHOnuZ5VD74,1069
39
- pyplumio/structures/lambda_sensor.py,sha256=JNSCiBJoM8Uk3OGbmFIigaLOntQST5U_UrmCpaQBlM0,1595
40
- pyplumio/structures/mixer_parameters.py,sha256=BQXC6vHx_b0OL-T6IU3bi2trjarLmlcMU1mmM5Q3AbI,8871
41
- pyplumio/structures/mixer_sensors.py,sha256=O91929Ts1YXFmKdPRc1r_BYDgrqkv5QVtE1nGzLpuAI,2260
42
- pyplumio/structures/modules.py,sha256=ukju4TQmRRJfgl94QU4zytZLU5px8nw3sgfSLn9JysU,2520
43
- pyplumio/structures/network_info.py,sha256=rxGoTdjlUmgEzR4BjOh9XQgEqKI6OSIbhOJ8tsXocts,4063
44
- pyplumio/structures/output_flags.py,sha256=07N0kxlvR5WZAURuChk_BqSiXR8eaQrtI5qlkgCf4Yc,1345
45
- pyplumio/structures/outputs.py,sha256=1xsJPkjN643-aFawqVoupGatUIUJfQG_g252n051Qi0,1916
46
- pyplumio/structures/pending_alerts.py,sha256=Uq9WpB4MW9AhDkqmDhk-g0J0h4pVq0Q50z12dYEv6kY,739
47
- pyplumio/structures/product_info.py,sha256=uiEN6DFQlzmBvQByTirFzXQShoex0YGdFS9WI-MAxPc,2405
48
- pyplumio/structures/program_version.py,sha256=p3Hzn1igxGyZ99jJjPswNGCAAQdJ5_-sgZPIy-MGISI,2506
49
- pyplumio/structures/regulator_data.py,sha256=Dun3RjfHHoV2W5RTSQcAimBL0Or3O957vYQj7Pbi7CM,2309
50
- pyplumio/structures/regulator_data_schema.py,sha256=BMshEpiP-lwTgSkbTuow9KlxCwKwQXV0nFPcBpW0SJg,1505
51
- pyplumio/structures/schedules.py,sha256=tm5DVCUnJ3FTUMl8wPGgM5_DsL_9R_UQqx5bfPAuvUM,6717
52
- pyplumio/structures/statuses.py,sha256=wkoynyMRr1VREwfBC6vU48kPA8ZQ83pcXuciy2xHJrk,1166
53
- pyplumio/structures/temperatures.py,sha256=1CDzehNmbALz1Jyt_9gZNIk52q6Wv-xQXjijVDCVYec,2337
54
- pyplumio/structures/thermostat_parameters.py,sha256=ybsab49teETR8pMsSCmPidNe5IclTq8tj7uxtHXGEAY,8045
55
- pyplumio/structures/thermostat_sensors.py,sha256=ZmjWgYtTZ5M8Lnz_Q5N4JD8G3MvEmByPFjYsy6XZOmo,3177
56
- PyPlumIO-0.5.25.dist-info/LICENSE,sha256=m-UuZFjXJ22uPTGm9kSHS8bqjsf5T8k2wL9bJn1Y04o,1088
57
- PyPlumIO-0.5.25.dist-info/METADATA,sha256=Nj3fV0EWvpmH1HJQN1SOfjNl2_llPNjsUbZLkJUmkME,5490
58
- PyPlumIO-0.5.25.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
59
- PyPlumIO-0.5.25.dist-info/top_level.txt,sha256=kNBz9UPPkPD9teDn3U_sEy5LjzwLm9KfADCXtBlbw8A,9
60
- PyPlumIO-0.5.25.dist-info/RECORD,,