python-openevse-http 1.7.0__tar.gz → 1.7.1__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.
- {python_openevse_http-1.7.0/python_openevse_http.egg-info → python_openevse_http-1.7.1}/PKG-INFO +1 -1
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/client.py +6 -4
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands_core.py +19 -5
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1/python_openevse_http.egg-info}/PKG-INFO +1 -1
- python_openevse_http-1.7.1/python_openevse_http.egg-info/scm_version.json +8 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_client.py +26 -9
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_commands.py +11 -0
- python_openevse_http-1.7.0/python_openevse_http.egg-info/scm_version.json +0 -8
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.agents/skills/openevse-api-guide/SKILL.md +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.agents/skills/openevse-api-guide/references/endpoints_matrix.md +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.agents/skills/openevse-dev-workflow/SKILL.md +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/dependabot.yml +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/pull_request_template.md +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/release-drafter.yml +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/workflows/autolabeler.yml +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/workflows/links.yml +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/workflows/publish-to-pypi.yml +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/workflows/release-drafter.yml +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/workflows/test.yml +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.gitignore +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.pre-commit-config.yaml +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.yamllint +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/AGENTS.md +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/EXTERNAL_SESSION.md +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/LICENSE +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/README.md +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/codecov.yml +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/example_external_session.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/__init__.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/__main__.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands_base.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands_diagnostics.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands_firmware.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands_schedule.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands_security.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands_time.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/const.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/exceptions.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/managers.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/properties.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/py.typed +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/sensors.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/utils.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/websocket.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/pyproject.toml +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/SOURCES.txt +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/dependency_links.txt +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/not-zip-safe +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/requires.txt +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/scm_file_list.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/top_level.txt +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/requirements.txt +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/requirements_lint.txt +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/requirements_test.txt +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/setup.cfg +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/setup.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/__init__.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/common.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/conftest.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/github_v2.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/github_v4.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v2_json/config.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v2_json/status.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-broken-semver.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-broken.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-dev.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-extra-version.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-new.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-unknown-semver.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/schedule.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/status-broken.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/status-new.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/status.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/websocket.json +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_external_session.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_main_edge_cases.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_managers.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_mixins.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_properties.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_sensors.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_shaper.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_websocket.py +0 -0
- {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tox.ini +0 -0
|
@@ -26,6 +26,7 @@ from .const import (
|
|
|
26
26
|
from .exceptions import (
|
|
27
27
|
AlreadyListening,
|
|
28
28
|
AuthenticationError,
|
|
29
|
+
CommandFailedError,
|
|
29
30
|
MissingMethod,
|
|
30
31
|
MissingSerial,
|
|
31
32
|
ParseJSONError,
|
|
@@ -186,15 +187,16 @@ class OpenEVSE(CommandsMixin, ManagersMixin, SensorsMixin, PropertiesMixin):
|
|
|
186
187
|
|
|
187
188
|
if resp.status == 400:
|
|
188
189
|
if isinstance(response_content, dict) and "msg" in response_content:
|
|
189
|
-
|
|
190
|
+
msg = str(response_content["msg"])
|
|
190
191
|
elif (
|
|
191
192
|
isinstance(response_content, dict)
|
|
192
193
|
and "error" in response_content
|
|
193
194
|
):
|
|
194
|
-
|
|
195
|
+
msg = str(response_content["error"])
|
|
195
196
|
else:
|
|
196
|
-
|
|
197
|
-
|
|
197
|
+
msg = str(response_content)
|
|
198
|
+
_LOGGER.error("Error 400: %s", msg)
|
|
199
|
+
raise CommandFailedError(msg)
|
|
198
200
|
if resp.status == 401:
|
|
199
201
|
_LOGGER.error("Authentication error: %s", response_content)
|
|
200
202
|
raise AuthenticationError
|
|
@@ -271,17 +271,31 @@ class CoreCommandsMixin(BaseCommandMixin):
|
|
|
271
271
|
|
|
272
272
|
# Strict success check:
|
|
273
273
|
# 1. Must be a Mapping
|
|
274
|
-
# 2. Must have
|
|
275
|
-
# 3. Must
|
|
276
|
-
#
|
|
274
|
+
# 2. Must NOT have an "error" key
|
|
275
|
+
# 3. Must have "result" in ("OK", "ok", True) OR "success" is True
|
|
276
|
+
# OR "msg" indicating restart acknowledgment ("restart gateway", "ok")
|
|
277
277
|
success = (
|
|
278
278
|
isinstance(response, Mapping)
|
|
279
|
-
and (response.get("result") == "OK" or response.get("success") is True)
|
|
280
279
|
and not response.get("error")
|
|
280
|
+
and (
|
|
281
|
+
response.get("result") in ("OK", "ok", True)
|
|
282
|
+
or response.get("success") is True
|
|
283
|
+
or (
|
|
284
|
+
isinstance(response.get("msg"), str)
|
|
285
|
+
and any(
|
|
286
|
+
val in response["msg"].lower()
|
|
287
|
+
for val in ("ok", "restart gateway")
|
|
288
|
+
)
|
|
289
|
+
)
|
|
290
|
+
)
|
|
281
291
|
)
|
|
282
292
|
if success and isinstance(response, Mapping) and "msg" in response:
|
|
283
293
|
msg_val = str(response["msg"]).lower()
|
|
284
|
-
if
|
|
294
|
+
if (
|
|
295
|
+
msg_val != "ok"
|
|
296
|
+
and "ok" not in msg_val
|
|
297
|
+
and "restart gateway" not in msg_val
|
|
298
|
+
):
|
|
285
299
|
success = False
|
|
286
300
|
|
|
287
301
|
if not success:
|
|
@@ -284,25 +284,25 @@ async def test_send_command_auth(test_charger_auth, mock_aioclient):
|
|
|
284
284
|
|
|
285
285
|
|
|
286
286
|
async def test_send_command_parse_err(test_charger_auth, mock_aioclient):
|
|
287
|
-
"""Test RAPI command with
|
|
287
|
+
"""Test RAPI command with 400 error response."""
|
|
288
288
|
mock_aioclient.post(
|
|
289
289
|
TEST_URL_RAPI, status=400, body='{"msg": "Could not parse JSON"}'
|
|
290
290
|
)
|
|
291
|
-
with pytest.raises(main.
|
|
291
|
+
with pytest.raises(main.CommandFailedError):
|
|
292
292
|
await test_charger_auth.send_command("test")
|
|
293
293
|
|
|
294
294
|
mock_aioclient.post(
|
|
295
295
|
TEST_URL_RAPI, status=400, body='{"error": "Could not parse JSON"}'
|
|
296
296
|
)
|
|
297
|
-
with pytest.raises(main.
|
|
297
|
+
with pytest.raises(main.CommandFailedError):
|
|
298
298
|
await test_charger_auth.send_command("test")
|
|
299
299
|
|
|
300
300
|
mock_aioclient.post(TEST_URL_RAPI, status=400, body='{"other": "Something else"}')
|
|
301
|
-
with pytest.raises(main.
|
|
301
|
+
with pytest.raises(main.CommandFailedError):
|
|
302
302
|
await test_charger_auth.send_command("test")
|
|
303
303
|
|
|
304
304
|
mock_aioclient.post(TEST_URL_RAPI, status=400, body='"Just a string response"')
|
|
305
|
-
with pytest.raises(main.
|
|
305
|
+
with pytest.raises(main.CommandFailedError):
|
|
306
306
|
await test_charger_auth.send_command("test")
|
|
307
307
|
|
|
308
308
|
|
|
@@ -1243,7 +1243,7 @@ async def test_process_request_400_error_with_msg(mock_aioclient, charger_factor
|
|
|
1243
1243
|
|
|
1244
1244
|
charger = charger_factory(SERVER_URL)
|
|
1245
1245
|
|
|
1246
|
-
with pytest.raises(
|
|
1246
|
+
with pytest.raises(CommandFailedError, match="Bad request"):
|
|
1247
1247
|
await charger.process_request(TEST_URL_STATUS, method="get")
|
|
1248
1248
|
|
|
1249
1249
|
|
|
@@ -1260,7 +1260,24 @@ async def test_process_request_400_error_with_error_field(
|
|
|
1260
1260
|
|
|
1261
1261
|
charger = charger_factory(SERVER_URL)
|
|
1262
1262
|
|
|
1263
|
-
with pytest.raises(
|
|
1263
|
+
with pytest.raises(CommandFailedError, match="Invalid input"):
|
|
1264
|
+
await charger.process_request(TEST_URL_STATUS, method="get")
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
async def test_process_request_400_error_with_string_body(
|
|
1268
|
+
mock_aioclient, charger_factory
|
|
1269
|
+
):
|
|
1270
|
+
"""Test process_request handles 400 error with plain string body."""
|
|
1271
|
+
|
|
1272
|
+
mock_aioclient.get(
|
|
1273
|
+
TEST_URL_STATUS,
|
|
1274
|
+
status=400,
|
|
1275
|
+
body="Plain text error",
|
|
1276
|
+
)
|
|
1277
|
+
|
|
1278
|
+
charger = charger_factory(SERVER_URL)
|
|
1279
|
+
|
|
1280
|
+
with pytest.raises(CommandFailedError, match="Plain text error"):
|
|
1264
1281
|
await charger.process_request(TEST_URL_STATUS, method="get")
|
|
1265
1282
|
|
|
1266
1283
|
|
|
@@ -1566,7 +1583,7 @@ async def test_external_session_400_error_with_msg():
|
|
|
1566
1583
|
async with aiohttp.ClientSession() as session:
|
|
1567
1584
|
charger = OpenEVSE(SERVER_URL, session=session)
|
|
1568
1585
|
|
|
1569
|
-
with pytest.raises(
|
|
1586
|
+
with pytest.raises(CommandFailedError, match="Bad request"):
|
|
1570
1587
|
await charger.process_request(TEST_URL_STATUS, method="get")
|
|
1571
1588
|
|
|
1572
1589
|
|
|
@@ -1582,7 +1599,7 @@ async def test_external_session_400_error_with_error_field():
|
|
|
1582
1599
|
async with aiohttp.ClientSession() as session:
|
|
1583
1600
|
charger = OpenEVSE(SERVER_URL, session=session)
|
|
1584
1601
|
|
|
1585
|
-
with pytest.raises(
|
|
1602
|
+
with pytest.raises(CommandFailedError, match="Invalid input"):
|
|
1586
1603
|
await charger.process_request(TEST_URL_STATUS, method="get")
|
|
1587
1604
|
|
|
1588
1605
|
|
|
@@ -673,6 +673,17 @@ async def test_restart_wifi(test_charger_modified_ver, mock_aioclient, caplog):
|
|
|
673
673
|
await test_charger_modified_ver.restart_wifi()
|
|
674
674
|
assert "Restart response: restart gateway" in caplog.text
|
|
675
675
|
|
|
676
|
+
# Also verify standard ESP32 gateway response `{"msg": "restart gateway"}`
|
|
677
|
+
caplog.clear()
|
|
678
|
+
mock_aioclient.post(
|
|
679
|
+
TEST_URL_RESTART,
|
|
680
|
+
status=200,
|
|
681
|
+
body='{"msg": "restart gateway"}',
|
|
682
|
+
)
|
|
683
|
+
with caplog.at_level(logging.DEBUG):
|
|
684
|
+
await test_charger_modified_ver.restart_wifi()
|
|
685
|
+
assert "WiFi Restart response: restart gateway" in caplog.text
|
|
686
|
+
|
|
676
687
|
|
|
677
688
|
async def test_restart_wifi_fail(test_charger, mock_aioclient, caplog):
|
|
678
689
|
"""Test restart_wifi failure."""
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.agents/skills/openevse-api-guide/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/ISSUE_TEMPLATE/bug_report.yml
RENAMED
|
File without changes
|
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/ISSUE_TEMPLATE/feature_request.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/workflows/publish-to-pypi.yml
RENAMED
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/workflows/release-drafter.yml
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands_diagnostics.py
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/requires.txt
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v2_json/config.json
RENAMED
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v2_json/status.json
RENAMED
|
File without changes
|
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-broken.json
RENAMED
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-dev.json
RENAMED
|
File without changes
|
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-new.json
RENAMED
|
File without changes
|
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config.json
RENAMED
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/schedule.json
RENAMED
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/status-broken.json
RENAMED
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/status-new.json
RENAMED
|
File without changes
|
{python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/status.json
RENAMED
|
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
|