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.
Files changed (88) hide show
  1. {python_openevse_http-1.7.0/python_openevse_http.egg-info → python_openevse_http-1.7.1}/PKG-INFO +1 -1
  2. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/client.py +6 -4
  3. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands_core.py +19 -5
  4. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1/python_openevse_http.egg-info}/PKG-INFO +1 -1
  5. python_openevse_http-1.7.1/python_openevse_http.egg-info/scm_version.json +8 -0
  6. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_client.py +26 -9
  7. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_commands.py +11 -0
  8. python_openevse_http-1.7.0/python_openevse_http.egg-info/scm_version.json +0 -8
  9. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.agents/skills/openevse-api-guide/SKILL.md +0 -0
  10. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.agents/skills/openevse-api-guide/references/endpoints_matrix.md +0 -0
  11. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.agents/skills/openevse-dev-workflow/SKILL.md +0 -0
  12. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  13. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  14. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  15. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/dependabot.yml +0 -0
  16. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/pull_request_template.md +0 -0
  17. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/release-drafter.yml +0 -0
  18. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/workflows/autolabeler.yml +0 -0
  19. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/workflows/links.yml +0 -0
  20. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/workflows/publish-to-pypi.yml +0 -0
  21. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/workflows/release-drafter.yml +0 -0
  22. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.github/workflows/test.yml +0 -0
  23. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.gitignore +0 -0
  24. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.pre-commit-config.yaml +0 -0
  25. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/.yamllint +0 -0
  26. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/AGENTS.md +0 -0
  27. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/EXTERNAL_SESSION.md +0 -0
  28. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/LICENSE +0 -0
  29. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/README.md +0 -0
  30. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/codecov.yml +0 -0
  31. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/example_external_session.py +0 -0
  32. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/__init__.py +0 -0
  33. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/__main__.py +0 -0
  34. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands.py +0 -0
  35. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands_base.py +0 -0
  36. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands_diagnostics.py +0 -0
  37. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands_firmware.py +0 -0
  38. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands_schedule.py +0 -0
  39. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands_security.py +0 -0
  40. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/commands_time.py +0 -0
  41. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/const.py +0 -0
  42. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/exceptions.py +0 -0
  43. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/managers.py +0 -0
  44. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/properties.py +0 -0
  45. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/py.typed +0 -0
  46. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/sensors.py +0 -0
  47. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/utils.py +0 -0
  48. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/openevsehttp/websocket.py +0 -0
  49. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/pyproject.toml +0 -0
  50. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/SOURCES.txt +0 -0
  51. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/dependency_links.txt +0 -0
  52. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/not-zip-safe +0 -0
  53. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/requires.txt +0 -0
  54. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/scm_file_list.json +0 -0
  55. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/python_openevse_http.egg-info/top_level.txt +0 -0
  56. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/requirements.txt +0 -0
  57. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/requirements_lint.txt +0 -0
  58. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/requirements_test.txt +0 -0
  59. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/setup.cfg +0 -0
  60. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/setup.py +0 -0
  61. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/__init__.py +0 -0
  62. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/common.py +0 -0
  63. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/conftest.py +0 -0
  64. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/github_v2.json +0 -0
  65. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/github_v4.json +0 -0
  66. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v2_json/config.json +0 -0
  67. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v2_json/status.json +0 -0
  68. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-broken-semver.json +0 -0
  69. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-broken.json +0 -0
  70. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-dev.json +0 -0
  71. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-extra-version.json +0 -0
  72. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-new.json +0 -0
  73. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config-unknown-semver.json +0 -0
  74. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/config.json +0 -0
  75. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/schedule.json +0 -0
  76. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/status-broken.json +0 -0
  77. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/status-new.json +0 -0
  78. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/v4_json/status.json +0 -0
  79. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/fixtures/websocket.json +0 -0
  80. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_external_session.py +0 -0
  81. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_main_edge_cases.py +0 -0
  82. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_managers.py +0 -0
  83. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_mixins.py +0 -0
  84. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_properties.py +0 -0
  85. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_sensors.py +0 -0
  86. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_shaper.py +0 -0
  87. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tests/test_websocket.py +0 -0
  88. {python_openevse_http-1.7.0 → python_openevse_http-1.7.1}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_openevse_http
3
- Version: 1.7.0
3
+ Version: 1.7.1
4
4
  Summary: Python wrapper for OpenEVSE HTTP API
5
5
  Home-page: https://github.com/firstof9/python-openevse-http
6
6
  Download-URL: https://github.com/firstof9/python-openevse-http
@@ -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
- _LOGGER.error("Error 400: %s", response_content["msg"])
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
- _LOGGER.error("Error 400: %s", response_content["error"])
195
+ msg = str(response_content["error"])
195
196
  else:
196
- _LOGGER.error("Error 400: %s", response_content)
197
- raise ParseJSONError
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 "result" == "OK" OR "success" is True
275
- # 3. Must NOT have an "error" key
276
- # 4. If "msg" is present, it must be "OK" or contain "ok" (case-insensitive)
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 msg_val != "ok" and "ok" not in msg_val:
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_openevse_http
3
- Version: 1.7.0
3
+ Version: 1.7.1
4
4
  Summary: Python wrapper for OpenEVSE HTTP API
5
5
  Home-page: https://github.com/firstof9/python-openevse-http
6
6
  Download-URL: https://github.com/firstof9/python-openevse-http
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "1.7.1",
3
+ "distance": 0,
4
+ "node": "g5bd22bb9dfe02ab480daf3c8d674dc07ee9dc59e",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-09-29"
8
+ }
@@ -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 JSON parse error."""
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.ParseJSONError):
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.ParseJSONError):
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.ParseJSONError):
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.ParseJSONError):
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(ParseJSONError):
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(ParseJSONError):
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(ParseJSONError):
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(ParseJSONError):
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."""
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "1.7.0",
3
- "distance": 0,
4
- "node": "g386a44e84fa6e86a88a75dabe86d19c1afb693c6",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-09-28"
8
- }