plugwise 1.4.2a2__tar.gz → 1.4.4__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 (32) hide show
  1. {plugwise-1.4.2a2 → plugwise-1.4.4}/PKG-INFO +1 -1
  2. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise/__init__.py +11 -15
  3. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise/helper.py +3 -2
  4. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise/legacy/helper.py +3 -2
  5. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise/legacy/smile.py +15 -22
  6. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise/smile.py +9 -16
  7. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise.egg-info/PKG-INFO +1 -1
  8. {plugwise-1.4.2a2 → plugwise-1.4.4}/pyproject.toml +1 -1
  9. {plugwise-1.4.2a2 → plugwise-1.4.4}/tests/test_init.py +10 -5
  10. {plugwise-1.4.2a2 → plugwise-1.4.4}/LICENSE +0 -0
  11. {plugwise-1.4.2a2 → plugwise-1.4.4}/README.md +0 -0
  12. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise/common.py +0 -0
  13. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise/constants.py +0 -0
  14. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise/data.py +0 -0
  15. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise/exceptions.py +0 -0
  16. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise/legacy/data.py +0 -0
  17. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise/py.typed +0 -0
  18. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise/util.py +0 -0
  19. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise.egg-info/SOURCES.txt +0 -0
  20. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise.egg-info/dependency_links.txt +0 -0
  21. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise.egg-info/requires.txt +0 -0
  22. {plugwise-1.4.2a2 → plugwise-1.4.4}/plugwise.egg-info/top_level.txt +0 -0
  23. {plugwise-1.4.2a2 → plugwise-1.4.4}/setup.cfg +0 -0
  24. {plugwise-1.4.2a2 → plugwise-1.4.4}/setup.py +0 -0
  25. {plugwise-1.4.2a2 → plugwise-1.4.4}/tests/test_adam.py +0 -0
  26. {plugwise-1.4.2a2 → plugwise-1.4.4}/tests/test_anna.py +0 -0
  27. {plugwise-1.4.2a2 → plugwise-1.4.4}/tests/test_generic.py +0 -0
  28. {plugwise-1.4.2a2 → plugwise-1.4.4}/tests/test_legacy_anna.py +0 -0
  29. {plugwise-1.4.2a2 → plugwise-1.4.4}/tests/test_legacy_generic.py +0 -0
  30. {plugwise-1.4.2a2 → plugwise-1.4.4}/tests/test_legacy_p1.py +0 -0
  31. {plugwise-1.4.2a2 → plugwise-1.4.4}/tests/test_legacy_stretch.py +0 -0
  32. {plugwise-1.4.2a2 → plugwise-1.4.4}/tests/test_p1.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plugwise
3
- Version: 1.4.2a2
3
+ Version: 1.4.4
4
4
  Summary: Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3.
5
5
  Home-page: https://github.com/plugwise/python-plugwise
6
6
  Author: Plugwise device owners
@@ -6,7 +6,6 @@ from __future__ import annotations
6
6
 
7
7
  from plugwise.constants import (
8
8
  DEFAULT_PORT,
9
- DEFAULT_TIMEOUT,
10
9
  DEFAULT_USERNAME,
11
10
  DOMAIN_OBJECTS,
12
11
  LOGGER,
@@ -32,6 +31,7 @@ from plugwise.smile import SmileAPI
32
31
 
33
32
  import aiohttp
34
33
  from defusedxml import ElementTree as etree
34
+ from packaging.version import Version, parse
35
35
 
36
36
 
37
37
  class Smile(SmileComm):
@@ -61,7 +61,6 @@ class Smile(SmileComm):
61
61
  self._host = host
62
62
  self._passwd = password
63
63
  self._port = port
64
- self._timeout = timeout
65
64
  self._user = username
66
65
  self._websession = websession
67
66
 
@@ -77,7 +76,7 @@ class Smile(SmileComm):
77
76
  self._target_smile: str = NONE
78
77
  self.gateway_id: str = NONE
79
78
  self.loc_data: dict[str, ThermoLoc] = {}
80
- self.smile_fw_version: str | None = None
79
+ self.smile_fw_version: Version | None = None
81
80
  self.smile_hostname: str = NONE
82
81
  self.smile_hw_version: str | None = None
83
82
  self.smile_legacy = False
@@ -86,10 +85,10 @@ class Smile(SmileComm):
86
85
  self.smile_model_id: str | None = None
87
86
  self.smile_name: str = NONE
88
87
  self.smile_type: str = NONE
89
- self.smile_version: str = NONE
88
+ self.smile_version: Version | None = None
90
89
  self.smile_zigbee_mac_address: str | None = None
91
90
 
92
- async def connect(self) -> bool:
91
+ async def connect(self) -> Version | None:
93
92
  """Connect to Plugwise device and determine its name, type and version."""
94
93
  result = await self._request(DOMAIN_OBJECTS)
95
94
  # Work-around for Stretch fw 2.7.18
@@ -128,7 +127,7 @@ class Smile(SmileComm):
128
127
  self._smile_api = SmileAPI(
129
128
  self._host,
130
129
  self._passwd,
131
- self._timeout,
130
+ self._request,
132
131
  self._websession,
133
132
  self._cooling_present,
134
133
  self._elga,
@@ -152,7 +151,7 @@ class Smile(SmileComm):
152
151
  ) if not self.smile_legacy else SmileLegacyAPI(
153
152
  self._host,
154
153
  self._passwd,
155
- self._timeout,
154
+ self._request,
156
155
  self._websession,
157
156
  self._is_thermostat,
158
157
  self._on_off_device,
@@ -175,7 +174,7 @@ class Smile(SmileComm):
175
174
  # Update all endpoints on first connect
176
175
  await self._smile_api.full_update_device()
177
176
 
178
- return True
177
+ return self.smile_version
179
178
 
180
179
  async def _smile_detect(self, result: etree, dsmrmain: etree) -> None:
181
180
  """Helper-function for connect().
@@ -186,7 +185,7 @@ class Smile(SmileComm):
186
185
  if (gateway := result.find("./gateway")) is not None:
187
186
  if (v_model := gateway.find("vendor_model")) is not None:
188
187
  model = v_model.text
189
- self.smile_fw_version = gateway.find("firmware_version").text
188
+ self.smile_fw_version = parse(gateway.find("firmware_version").text)
190
189
  self.smile_hw_version = gateway.find("hardware_version").text
191
190
  self.smile_hostname = gateway.find("hostname").text
192
191
  self.smile_mac_address = gateway.find("mac_address").text
@@ -194,9 +193,6 @@ class Smile(SmileComm):
194
193
  else:
195
194
  model = await self._smile_detect_legacy(result, dsmrmain, model)
196
195
 
197
- if not self.smile_legacy:
198
- self._timeout = DEFAULT_TIMEOUT
199
-
200
196
  if model == "Unknown" or self.smile_fw_version is None: # pragma: no cover
201
197
  # Corner case check
202
198
  LOGGER.error(
@@ -205,7 +201,7 @@ class Smile(SmileComm):
205
201
  )
206
202
  raise UnsupportedDeviceError
207
203
 
208
- version_major: str = self.smile_fw_version.split(".", 1)[0]
204
+ version_major= str(self.smile_fw_version.major)
209
205
  self._target_smile = f"{model}_v{version_major}"
210
206
  LOGGER.debug("Plugwise identified as %s", self._target_smile)
211
207
  if self._target_smile not in SMILES:
@@ -272,7 +268,7 @@ class Smile(SmileComm):
272
268
  or network is not None
273
269
  ):
274
270
  system = await self._request(SYSTEM)
275
- self.smile_fw_version = system.find("./gateway/firmware").text
271
+ self.smile_fw_version = parse(system.find("./gateway/firmware").text)
276
272
  return_model = system.find("./gateway/product").text
277
273
  self.smile_hostname = system.find("./gateway/hostname").text
278
274
  # If wlan0 contains data it's active, so eth0 should be checked last
@@ -283,7 +279,7 @@ class Smile(SmileComm):
283
279
  # P1 legacy:
284
280
  elif dsmrmain is not None:
285
281
  status = await self._request(STATUS)
286
- self.smile_fw_version = status.find("./system/version").text
282
+ self.smile_fw_version = parse(status.find("./system/version").text)
287
283
  return_model = status.find("./system/product").text
288
284
  self.smile_hostname = status.find("./network/hostname").text
289
285
  self.smile_mac_address = status.find("./network/mac_address").text
@@ -62,6 +62,7 @@ from dateutil import tz
62
62
  from dateutil.parser import parse
63
63
  from defusedxml import ElementTree as etree
64
64
  from munch import Munch
65
+ from packaging.version import Version
65
66
 
66
67
 
67
68
  class SmileComm:
@@ -250,7 +251,7 @@ class SmileHelper(SmileCommon):
250
251
  self.gw_data: GatewayData = {}
251
252
  self.gw_devices: dict[str, DeviceData] = {}
252
253
  self.loc_data: dict[str, ThermoLoc]
253
- self.smile_fw_version: str | None
254
+ self.smile_fw_version: Version | None
254
255
  self.smile_hw_version: str | None
255
256
  self.smile_mac_address: str | None
256
257
  self.smile_model: str
@@ -425,7 +426,7 @@ class SmileHelper(SmileCommon):
425
426
  def _appl_gateway_info(self, appl: Munch, appliance: etree) -> Munch:
426
427
  """Helper-function for _appliance_info_finder()."""
427
428
  self.gateway_id = appliance.attrib["id"]
428
- appl.firmware = self.smile_fw_version
429
+ appl.firmware = str(self.smile_fw_version)
429
430
  appl.hardware = self.smile_hw_version
430
431
  appl.mac = self.smile_mac_address
431
432
  appl.model = self.smile_model
@@ -44,6 +44,7 @@ from plugwise.util import (
44
44
  # This way of importing aiohttp is because of patch/mocking in testing (aiohttp timeouts)
45
45
  from defusedxml import ElementTree as etree
46
46
  from munch import Munch
47
+ from packaging.version import Version
47
48
 
48
49
 
49
50
  def etree_to_dict(element: etree) -> dict[str, str]:
@@ -81,7 +82,7 @@ class SmileLegacyHelper(SmileCommon):
81
82
  self.gw_data: GatewayData = {}
82
83
  self.gw_devices: dict[str, DeviceData] = {}
83
84
  self.loc_data: dict[str, ThermoLoc]
84
- self.smile_fw_version: str | None
85
+ self.smile_fw_version: Version | None
85
86
  self.smile_hw_version: str | None
86
87
  self.smile_mac_address: str | None
87
88
  self.smile_model: str
@@ -194,7 +195,7 @@ class SmileLegacyHelper(SmileCommon):
194
195
  self.gw_devices[self.gateway_id] = {"dev_class": "gateway"}
195
196
  self._count += 1
196
197
  for key, value in {
197
- "firmware": self.smile_fw_version,
198
+ "firmware": str(self.smile_fw_version),
198
199
  "location": self._home_location,
199
200
  "mac_address": self.smile_mac_address,
200
201
  "model": self.smile_model,
@@ -4,6 +4,7 @@ Plugwise backend module for Home Assistant Core - covering the legacy P1, Anna,
4
4
  """
5
5
  from __future__ import annotations
6
6
 
7
+ from collections.abc import Awaitable, Callable
7
8
  import datetime as dt
8
9
  from typing import Any
9
10
 
@@ -24,14 +25,14 @@ from plugwise.constants import (
24
25
  ThermoLoc,
25
26
  )
26
27
  from plugwise.exceptions import ConnectionFailedError, PlugwiseError
27
- from plugwise.helper import SmileComm
28
28
  from plugwise.legacy.data import SmileLegacyData
29
29
 
30
30
  import aiohttp
31
31
  from munch import Munch
32
+ from packaging.version import Version
32
33
 
33
34
 
34
- class SmileLegacyAPI(SmileComm, SmileLegacyData):
35
+ class SmileLegacyAPI(SmileLegacyData):
35
36
  """The Plugwise SmileLegacyAPI class."""
36
37
 
37
38
  # pylint: disable=too-many-instance-attributes, too-many-public-methods
@@ -40,7 +41,7 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
40
41
  self,
41
42
  host: str,
42
43
  password: str,
43
- timeout: int,
44
+ request: Callable[..., Awaitable[Any]],
44
45
  websession: aiohttp.ClientSession,
45
46
  _is_thermostat: bool,
46
47
  _on_off_device: bool,
@@ -48,7 +49,7 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
48
49
  _stretch_v2: bool,
49
50
  _target_smile: str,
50
51
  loc_data: dict[str, ThermoLoc],
51
- smile_fw_version: str | None,
52
+ smile_fw_version: Version | None,
52
53
  smile_hostname: str,
53
54
  smile_hw_version: str | None,
54
55
  smile_mac_address: str | None,
@@ -60,14 +61,6 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
60
61
  username: str = DEFAULT_USERNAME,
61
62
  ) -> None:
62
63
  """Set the constructor for this class."""
63
- super().__init__(
64
- host,
65
- password,
66
- port,
67
- timeout,
68
- username,
69
- websession,
70
- )
71
64
  SmileLegacyData.__init__(self)
72
65
 
73
66
  self._cooling_present = False
@@ -76,8 +69,8 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
76
69
  self._opentherm_device = _opentherm_device
77
70
  self._stretch_v2 = _stretch_v2
78
71
  self._target_smile = _target_smile
79
- self._timeout = timeout
80
72
  self.loc_data = loc_data
73
+ self.request = request
81
74
  self.smile_fw_version = smile_fw_version
82
75
  self.smile_hostname = smile_hostname
83
76
  self.smile_hw_version = smile_hw_version
@@ -91,12 +84,12 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
91
84
 
92
85
  async def full_update_device(self) -> None:
93
86
  """Perform a first fetch of all XML data, needed for initialization."""
94
- self._domain_objects = await self._request(DOMAIN_OBJECTS)
95
- self._locations = await self._request(LOCATIONS)
96
- self._modules = await self._request(MODULES)
87
+ self._domain_objects = await self.request(DOMAIN_OBJECTS)
88
+ self._locations = await self.request(LOCATIONS)
89
+ self._modules = await self.request(MODULES)
97
90
  # P1 legacy has no appliances
98
91
  if self.smile_type != "power":
99
- self._appliances = await self._request(APPLIANCES)
92
+ self._appliances = await self.request(APPLIANCES)
100
93
 
101
94
  def get_all_devices(self) -> None:
102
95
  """Determine the evices present from the obtained XML-data.
@@ -131,12 +124,12 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
131
124
  self.get_all_devices()
132
125
  # Otherwise perform an incremental update
133
126
  else:
134
- self._domain_objects = await self._request(DOMAIN_OBJECTS)
127
+ self._domain_objects = await self.request(DOMAIN_OBJECTS)
135
128
  match self._target_smile:
136
129
  case "smile_v2":
137
- self._modules = await self._request(MODULES)
130
+ self._modules = await self.request(MODULES)
138
131
  case self._target_smile if self._target_smile in REQUIRE_APPLIANCES:
139
- self._appliances = await self._request(APPLIANCES)
132
+ self._appliances = await self.request(APPLIANCES)
140
133
 
141
134
  self._update_gw_devices()
142
135
 
@@ -291,10 +284,10 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
291
284
  await self.call_request(uri, method="put", data=data)
292
285
 
293
286
  async def call_request(self, uri: str, **kwargs: Any) -> None:
294
- """ConnectionFailedError wrapper for calling _request()."""
287
+ """ConnectionFailedError wrapper for calling request()."""
295
288
  method: str = kwargs["method"]
296
289
  data: str | None = kwargs.get("data")
297
290
  try:
298
- await self._request(uri, method=method, data=data)
291
+ await self.request(uri, method=method, data=data)
299
292
  except ConnectionFailedError as exc:
300
293
  raise ConnectionFailedError from exc
@@ -4,6 +4,7 @@ Plugwise backend module for Home Assistant Core.
4
4
  """
5
5
  from __future__ import annotations
6
6
 
7
+ from collections.abc import Awaitable, Callable
7
8
  import datetime as dt
8
9
  from typing import Any
9
10
 
@@ -28,16 +29,16 @@ from plugwise.constants import (
28
29
  )
29
30
  from plugwise.data import SmileData
30
31
  from plugwise.exceptions import ConnectionFailedError, DataMissingError, PlugwiseError
31
- from plugwise.helper import SmileComm
32
32
 
33
33
  import aiohttp
34
34
  from defusedxml import ElementTree as etree
35
35
 
36
36
  # Dict as class
37
37
  from munch import Munch
38
+ from packaging.version import Version
38
39
 
39
40
 
40
- class SmileAPI(SmileComm, SmileData):
41
+ class SmileAPI(SmileData):
41
42
  """The Plugwise SmileAPI helper class for actual Plugwise devices."""
42
43
 
43
44
  # pylint: disable=too-many-instance-attributes, too-many-public-methods
@@ -46,7 +47,7 @@ class SmileAPI(SmileComm, SmileData):
46
47
  self,
47
48
  host: str,
48
49
  password: str,
49
- timeout: int,
50
+ request: Callable[..., Awaitable[Any]],
50
51
  websession: aiohttp.ClientSession,
51
52
  _cooling_present: bool,
52
53
  _elga: bool,
@@ -57,7 +58,7 @@ class SmileAPI(SmileComm, SmileData):
57
58
  _schedule_old_states: dict[str, dict[str, str]],
58
59
  gateway_id: str,
59
60
  loc_data: dict[str, ThermoLoc],
60
- smile_fw_version: str | None,
61
+ smile_fw_version: Version | None,
61
62
  smile_hostname: str | None,
62
63
  smile_hw_version: str | None,
63
64
  smile_mac_address: str | None,
@@ -69,14 +70,6 @@ class SmileAPI(SmileComm, SmileData):
69
70
  username: str = DEFAULT_USERNAME,
70
71
  ) -> None:
71
72
  """Set the constructor for this class."""
72
- super().__init__(
73
- host,
74
- password,
75
- port,
76
- timeout,
77
- username,
78
- websession,
79
- )
80
73
  SmileData.__init__(self)
81
74
 
82
75
  self._cooling_present = _cooling_present
@@ -86,9 +79,9 @@ class SmileAPI(SmileComm, SmileData):
86
79
  self._on_off_device = _on_off_device
87
80
  self._opentherm_device = _opentherm_device
88
81
  self._schedule_old_states = _schedule_old_states
89
- self._timeout = timeout
90
82
  self.gateway_id = gateway_id
91
83
  self.loc_data = loc_data
84
+ self.request = request
92
85
  self.smile_fw_version = smile_fw_version
93
86
  self.smile_hostname = smile_hostname
94
87
  self.smile_hw_version = smile_hw_version
@@ -103,7 +96,7 @@ class SmileAPI(SmileComm, SmileData):
103
96
 
104
97
  async def full_update_device(self) -> None:
105
98
  """Perform a first fetch of all XML data, needed for initialization."""
106
- self._domain_objects = await self._request(DOMAIN_OBJECTS)
99
+ self._domain_objects = await self.request(DOMAIN_OBJECTS)
107
100
  self._get_plugwise_notifications()
108
101
 
109
102
  def get_all_devices(self) -> None:
@@ -461,10 +454,10 @@ class SmileAPI(SmileComm, SmileData):
461
454
  await self.call_request(uri, method="put", data=data)
462
455
 
463
456
  async def call_request(self, uri: str, **kwargs: Any) -> None:
464
- """ConnectionFailedError wrapper for calling _request()."""
457
+ """ConnectionFailedError wrapper for calling request()."""
465
458
  method: str = kwargs["method"]
466
459
  data: str | None = kwargs.get("data")
467
460
  try:
468
- await self._request(uri, method=method, data=data)
461
+ await self.request(uri, method=method, data=data)
469
462
  except ConnectionFailedError as exc:
470
463
  raise ConnectionFailedError from exc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plugwise
3
- Version: 1.4.2a2
3
+ Version: 1.4.4
4
4
  Summary: Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3.
5
5
  Home-page: https://github.com/plugwise/python-plugwise
6
6
  Author: Plugwise device owners
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "plugwise"
7
- version = "1.4.2a2"
7
+ version = "1.4.4"
8
8
  license = {file = "LICENSE"}
9
9
  description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3."
10
10
  readme = "README.md"
@@ -17,6 +17,7 @@ import pytest
17
17
  # Testing
18
18
  import aiohttp
19
19
  from freezegun import freeze_time
20
+ from packaging import version
20
21
 
21
22
  pw_constants = importlib.import_module("plugwise.constants")
22
23
  pw_exceptions = importlib.import_module("plugwise.exceptions")
@@ -344,15 +345,17 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
344
345
  assert smile._timeout == 10
345
346
 
346
347
  # Connect to the smile
348
+ version = None
347
349
  try:
348
- connection_state = await smile.connect()
349
- assert connection_state
350
+ version = await smile.connect()
351
+ assert version is not None
350
352
  return server, smile, client
351
353
  except (
352
354
  pw_exceptions.ConnectionFailedError,
353
355
  pw_exceptions.InvalidXMLError,
354
356
  pw_exceptions.InvalidAuthentication,
355
357
  ) as exception:
358
+ assert version is None
356
359
  await self.disconnect(server, client)
357
360
  raise exception
358
361
 
@@ -427,15 +430,17 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
427
430
  assert smile._timeout == 30
428
431
 
429
432
  # Connect to the smile
433
+ version = None
430
434
  try:
431
- connection_state = await smile.connect()
432
- assert connection_state
435
+ version = await smile.connect()
436
+ assert version is not None
433
437
  return server, smile, client
434
438
  except (
435
439
  pw_exceptions.ConnectionFailedError,
436
440
  pw_exceptions.InvalidXMLError,
437
441
  pw_exceptions.InvalidAuthentication,
438
442
  ) as exception:
443
+ assert version is None
439
444
  await self.disconnect(server, client)
440
445
  raise exception
441
446
 
@@ -1004,7 +1009,7 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
1004
1009
  if smile_version:
1005
1010
  log_msg = f" # Assert version matching '{smile_version}"
1006
1011
  parent_logger.info(log_msg)
1007
- assert smile.smile_version == smile_version
1012
+ assert smile.smile_version == version.parse(smile_version)
1008
1013
  log_msg = f" # Assert legacy {smile_legacy}"
1009
1014
  parent_logger.info(log_msg)
1010
1015
  if smile_legacy:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes