python-openevse-http 0.2.0__tar.gz → 0.2.2__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-0.2.0/python_openevse_http.egg-info → python_openevse_http-0.2.2}/PKG-INFO +4 -2
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/openevsehttp/__main__.py +16 -6
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2/python_openevse_http.egg-info}/PKG-INFO +4 -2
- python_openevse_http-0.2.2/python_openevse_http.egg-info/requires.txt +1 -0
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/setup.py +4 -2
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/tests/test_main.py +49 -12
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/tests/test_main_edge_cases.py +6 -4
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/tests/test_websocket.py +4 -3
- python_openevse_http-0.2.0/python_openevse_http.egg-info/requires.txt +0 -2
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/LICENSE +0 -0
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/README.md +0 -0
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/openevsehttp/__init__.py +0 -0
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/openevsehttp/const.py +0 -0
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/openevsehttp/exceptions.py +0 -0
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/openevsehttp/websocket.py +0 -0
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/python_openevse_http.egg-info/SOURCES.txt +0 -0
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/python_openevse_http.egg-info/dependency_links.txt +0 -0
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/python_openevse_http.egg-info/not-zip-safe +0 -0
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/python_openevse_http.egg-info/top_level.txt +0 -0
- {python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/setup.cfg +0 -0
{python_openevse_http-0.2.0/python_openevse_http.egg-info → python_openevse_http-0.2.2}/PKG-INFO
RENAMED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python_openevse_http
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
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
|
|
7
7
|
Author: firstof9
|
|
8
8
|
Author-email: firstof9@gmail.com
|
|
9
|
+
License: Apache-2.0
|
|
9
10
|
Classifier: Development Status :: 4 - Beta
|
|
10
11
|
Classifier: Intended Audience :: Developers
|
|
11
12
|
Classifier: Natural Language :: English
|
|
@@ -14,11 +15,11 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
14
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
19
|
Requires-Python: >=3.10
|
|
18
20
|
Description-Content-Type: text/markdown
|
|
19
21
|
License-File: LICENSE
|
|
20
22
|
Requires-Dist: aiohttp
|
|
21
|
-
Requires-Dist: requests
|
|
22
23
|
Dynamic: author
|
|
23
24
|
Dynamic: author-email
|
|
24
25
|
Dynamic: classifier
|
|
@@ -26,6 +27,7 @@ Dynamic: description
|
|
|
26
27
|
Dynamic: description-content-type
|
|
27
28
|
Dynamic: download-url
|
|
28
29
|
Dynamic: home-page
|
|
30
|
+
Dynamic: license
|
|
29
31
|
Dynamic: license-file
|
|
30
32
|
Dynamic: requires-dist
|
|
31
33
|
Dynamic: requires-python
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
-
import datetime
|
|
6
|
+
from datetime import datetime, timedelta, timezone
|
|
7
7
|
import json
|
|
8
8
|
import logging
|
|
9
9
|
import re
|
|
@@ -1144,9 +1144,16 @@ class OpenEVSE:
|
|
|
1144
1144
|
return None
|
|
1145
1145
|
|
|
1146
1146
|
@property
|
|
1147
|
-
def time(self) -> datetime
|
|
1147
|
+
def time(self) -> datetime | None:
|
|
1148
1148
|
"""Get the RTC time."""
|
|
1149
|
-
|
|
1149
|
+
value = self._status.get("time")
|
|
1150
|
+
|
|
1151
|
+
if value:
|
|
1152
|
+
try:
|
|
1153
|
+
return datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
1154
|
+
except (ValueError, AttributeError):
|
|
1155
|
+
return None
|
|
1156
|
+
return None
|
|
1150
1157
|
|
|
1151
1158
|
@property
|
|
1152
1159
|
def usage_session(self) -> float:
|
|
@@ -1308,11 +1315,14 @@ class OpenEVSE:
|
|
|
1308
1315
|
)
|
|
1309
1316
|
|
|
1310
1317
|
@property
|
|
1311
|
-
def vehicle_eta(self) ->
|
|
1318
|
+
def vehicle_eta(self) -> datetime | None:
|
|
1312
1319
|
"""Return time to full charge."""
|
|
1313
|
-
|
|
1314
|
-
"
|
|
1320
|
+
value = self._status.get(
|
|
1321
|
+
"time_to_full_charge", self._status.get("vehicle_eta", None)
|
|
1315
1322
|
)
|
|
1323
|
+
if value is not None:
|
|
1324
|
+
return datetime.now(timezone.utc) + timedelta(seconds=value)
|
|
1325
|
+
return value
|
|
1316
1326
|
|
|
1317
1327
|
# There is currently no min/max amps JSON data
|
|
1318
1328
|
# available via HTTP API methods
|
{python_openevse_http-0.2.0 → python_openevse_http-0.2.2/python_openevse_http.egg-info}/PKG-INFO
RENAMED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python_openevse_http
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
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
|
|
7
7
|
Author: firstof9
|
|
8
8
|
Author-email: firstof9@gmail.com
|
|
9
|
+
License: Apache-2.0
|
|
9
10
|
Classifier: Development Status :: 4 - Beta
|
|
10
11
|
Classifier: Intended Audience :: Developers
|
|
11
12
|
Classifier: Natural Language :: English
|
|
@@ -14,11 +15,11 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
14
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
19
|
Requires-Python: >=3.10
|
|
18
20
|
Description-Content-Type: text/markdown
|
|
19
21
|
License-File: LICENSE
|
|
20
22
|
Requires-Dist: aiohttp
|
|
21
|
-
Requires-Dist: requests
|
|
22
23
|
Dynamic: author
|
|
23
24
|
Dynamic: author-email
|
|
24
25
|
Dynamic: classifier
|
|
@@ -26,6 +27,7 @@ Dynamic: description
|
|
|
26
27
|
Dynamic: description-content-type
|
|
27
28
|
Dynamic: download-url
|
|
28
29
|
Dynamic: home-page
|
|
30
|
+
Dynamic: license
|
|
29
31
|
Dynamic: license-file
|
|
30
32
|
Dynamic: requires-dist
|
|
31
33
|
Dynamic: requires-python
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aiohttp
|
|
@@ -6,7 +6,7 @@ from setuptools import find_packages, setup
|
|
|
6
6
|
|
|
7
7
|
PROJECT_DIR = Path(__file__).parent.resolve()
|
|
8
8
|
README_FILE = PROJECT_DIR / "README.md"
|
|
9
|
-
VERSION = "0.2.
|
|
9
|
+
VERSION = "0.2.2"
|
|
10
10
|
|
|
11
11
|
setup(
|
|
12
12
|
name="python_openevse_http",
|
|
@@ -20,7 +20,8 @@ setup(
|
|
|
20
20
|
long_description_content_type="text/markdown",
|
|
21
21
|
packages=find_packages(exclude=["test.*", "tests"]),
|
|
22
22
|
python_requires=">=3.10",
|
|
23
|
-
install_requires=["aiohttp"
|
|
23
|
+
install_requires=["aiohttp"],
|
|
24
|
+
license="Apache-2.0",
|
|
24
25
|
entry_points={},
|
|
25
26
|
include_package_data=True,
|
|
26
27
|
zip_safe=False,
|
|
@@ -33,5 +34,6 @@ setup(
|
|
|
33
34
|
"Programming Language :: Python :: 3.11",
|
|
34
35
|
"Programming Language :: Python :: 3.12",
|
|
35
36
|
"Programming Language :: Python :: 3.13",
|
|
37
|
+
"License :: OSI Approved :: Apache Software License",
|
|
36
38
|
],
|
|
37
39
|
)
|
|
@@ -8,6 +8,8 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
8
8
|
|
|
9
9
|
import aiohttp
|
|
10
10
|
import pytest
|
|
11
|
+
from datetime import datetime, timezone, timedelta
|
|
12
|
+
from freezegun import freeze_time
|
|
11
13
|
from aiohttp.client_exceptions import ContentTypeError, ServerTimeoutError
|
|
12
14
|
from aiohttp.client_reqrep import ConnectionKey
|
|
13
15
|
from awesomeversion.exceptions import AwesomeVersionCompareException
|
|
@@ -488,18 +490,42 @@ async def test_get_esp_temperature(fixture, expected, request):
|
|
|
488
490
|
|
|
489
491
|
|
|
490
492
|
@pytest.mark.parametrize(
|
|
491
|
-
"fixture,
|
|
493
|
+
"fixture, expected_str",
|
|
492
494
|
[("test_charger", "2021-08-10T23:00:11Z"), ("test_charger_v2", None)],
|
|
493
495
|
)
|
|
494
|
-
async def test_get_time(fixture,
|
|
496
|
+
async def test_get_time(fixture, expected_str, request):
|
|
495
497
|
"""Test v4 Status reply."""
|
|
496
498
|
charger = request.getfixturevalue(fixture)
|
|
497
499
|
await charger.update()
|
|
498
|
-
|
|
499
|
-
|
|
500
|
+
|
|
501
|
+
result = charger.time
|
|
502
|
+
|
|
503
|
+
if expected_str:
|
|
504
|
+
expected_dt = datetime(2021, 8, 10, 23, 0, 11, tzinfo=timezone.utc)
|
|
505
|
+
assert result == expected_dt
|
|
506
|
+
assert isinstance(result, datetime)
|
|
507
|
+
else:
|
|
508
|
+
assert result is None
|
|
509
|
+
|
|
500
510
|
await charger.ws_disconnect()
|
|
501
511
|
|
|
502
512
|
|
|
513
|
+
@pytest.mark.parametrize(
|
|
514
|
+
"bad_value",
|
|
515
|
+
[
|
|
516
|
+
"not-a-timestamp",
|
|
517
|
+
123456789,
|
|
518
|
+
True,
|
|
519
|
+
{"some": "dict"},
|
|
520
|
+
],
|
|
521
|
+
)
|
|
522
|
+
async def test_time_parsing_errors(test_charger, bad_value):
|
|
523
|
+
"""Test that ValueError and AttributeError are caught and return None."""
|
|
524
|
+
test_charger._status["time"] = bad_value
|
|
525
|
+
result = test_charger.time
|
|
526
|
+
assert result is None
|
|
527
|
+
|
|
528
|
+
|
|
503
529
|
@pytest.mark.parametrize(
|
|
504
530
|
"fixture, expected",
|
|
505
531
|
[
|
|
@@ -1266,14 +1292,25 @@ async def test_vehicle_range(fixture, expected, request):
|
|
|
1266
1292
|
|
|
1267
1293
|
|
|
1268
1294
|
@pytest.mark.parametrize(
|
|
1269
|
-
"fixture,
|
|
1295
|
+
"fixture, expected_seconds", [("test_charger", 18000), ("test_charger_v2", None)]
|
|
1270
1296
|
)
|
|
1271
|
-
|
|
1297
|
+
@freeze_time("2026-01-09 12:00:00+00:00")
|
|
1298
|
+
async def test_vehicle_eta(fixture, expected_seconds, request):
|
|
1272
1299
|
"""Test vehicle_eta reply."""
|
|
1273
1300
|
charger = request.getfixturevalue(fixture)
|
|
1274
1301
|
await charger.update()
|
|
1275
|
-
|
|
1276
|
-
|
|
1302
|
+
|
|
1303
|
+
result = charger.vehicle_eta
|
|
1304
|
+
|
|
1305
|
+
if expected_seconds is not None:
|
|
1306
|
+
# Calculate what the expected datetime should be based on our frozen time
|
|
1307
|
+
expected_datetime = datetime(
|
|
1308
|
+
2026, 1, 9, 12, 0, 0, tzinfo=timezone.utc
|
|
1309
|
+
) + timedelta(seconds=expected_seconds)
|
|
1310
|
+
assert result == expected_datetime
|
|
1311
|
+
else:
|
|
1312
|
+
assert result is None
|
|
1313
|
+
|
|
1277
1314
|
await charger.ws_disconnect()
|
|
1278
1315
|
|
|
1279
1316
|
|
|
@@ -2235,10 +2272,10 @@ async def test_main_auth_instantiation():
|
|
|
2235
2272
|
# Ensure session.get() returns the request context
|
|
2236
2273
|
mock_session.get.return_value = mock_request_ctx
|
|
2237
2274
|
|
|
2238
|
-
with
|
|
2239
|
-
"aiohttp.
|
|
2240
|
-
|
|
2241
|
-
|
|
2275
|
+
with (
|
|
2276
|
+
patch("aiohttp.ClientSession", return_value=mock_session),
|
|
2277
|
+
patch("aiohttp.BasicAuth") as mock_basic_auth,
|
|
2278
|
+
):
|
|
2242
2279
|
await charger.update()
|
|
2243
2280
|
|
|
2244
2281
|
# Verify BasicAuth was instantiated
|
|
@@ -33,8 +33,9 @@ async def test_process_request_decode_error(charger, caplog):
|
|
|
33
33
|
mock_resp.__aenter__.return_value = mock_resp
|
|
34
34
|
mock_resp.__aexit__.return_value = None
|
|
35
35
|
|
|
36
|
-
with
|
|
37
|
-
|
|
36
|
+
with (
|
|
37
|
+
patch("aiohttp.ClientSession.get", return_value=mock_resp),
|
|
38
|
+
caplog.at_level(logging.DEBUG),
|
|
38
39
|
):
|
|
39
40
|
data = await charger.process_request("http://url", method="get")
|
|
40
41
|
|
|
@@ -53,8 +54,9 @@ async def test_process_request_http_warnings(charger, caplog):
|
|
|
53
54
|
mock_resp.__aenter__.return_value = mock_resp
|
|
54
55
|
mock_resp.__aexit__.return_value = None
|
|
55
56
|
|
|
56
|
-
with
|
|
57
|
-
|
|
57
|
+
with (
|
|
58
|
+
patch("aiohttp.ClientSession.get", return_value=mock_resp),
|
|
59
|
+
caplog.at_level(logging.WARNING),
|
|
58
60
|
):
|
|
59
61
|
await charger.process_request("http://url", method="get")
|
|
60
62
|
# Verify the 404 response body was logged as a warning
|
|
@@ -97,9 +97,10 @@ async def test_connection_error_retry(ws_client, mock_callback):
|
|
|
97
97
|
"""Test connection retry logic."""
|
|
98
98
|
error = aiohttp.ClientConnectionError("Connection lost")
|
|
99
99
|
|
|
100
|
-
with
|
|
101
|
-
"
|
|
102
|
-
|
|
100
|
+
with (
|
|
101
|
+
patch("aiohttp.ClientSession.ws_connect", side_effect=error),
|
|
102
|
+
patch("asyncio.sleep", new_callable=AsyncMock) as mock_sleep,
|
|
103
|
+
):
|
|
103
104
|
# Simulate one run of 'running' which catches the error and triggers sleep
|
|
104
105
|
await ws_client.running()
|
|
105
106
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/python_openevse_http.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{python_openevse_http-0.2.0 → python_openevse_http-0.2.2}/python_openevse_http.egg-info/not-zip-safe
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|