python-openevse-http 1.0.1__tar.gz → 1.1.0__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 (71) hide show
  1. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/.github/workflows/test.yml +1 -1
  2. {python_openevse_http-1.0.1/python_openevse_http.egg-info → python_openevse_http-1.1.0}/PKG-INFO +1 -1
  3. python_openevse_http-1.1.0/openevsehttp/utils.py +57 -0
  4. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0/python_openevse_http.egg-info}/PKG-INFO +1 -1
  5. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/requirements_lint.txt +2 -2
  6. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/requirements_test.txt +1 -1
  7. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/v4_json/config-unknown-semver.json +1 -1
  8. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/test_client.py +69 -2
  9. python_openevse_http-1.0.1/openevsehttp/utils.py +0 -29
  10. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/.github/dependabot.yml +0 -0
  11. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/.github/release-drafter.yml +0 -0
  12. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/.github/workflows/autolabeler.yml +0 -0
  13. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/.github/workflows/links.yml +0 -0
  14. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/.github/workflows/publish-to-pypi.yml +0 -0
  15. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/.github/workflows/release-drafter.yml +0 -0
  16. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/.gitignore +0 -0
  17. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/.pre-commit-config.yaml +0 -0
  18. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/.yamllint +0 -0
  19. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/EXTERNAL_SESSION.md +0 -0
  20. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/LICENSE +0 -0
  21. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/README.md +0 -0
  22. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/codecov.yml +0 -0
  23. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/example_external_session.py +0 -0
  24. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/openevsehttp/__init__.py +0 -0
  25. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/openevsehttp/__main__.py +0 -0
  26. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/openevsehttp/client.py +0 -0
  27. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/openevsehttp/commands.py +0 -0
  28. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/openevsehttp/const.py +0 -0
  29. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/openevsehttp/exceptions.py +0 -0
  30. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/openevsehttp/managers.py +0 -0
  31. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/openevsehttp/properties.py +0 -0
  32. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/openevsehttp/py.typed +0 -0
  33. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/openevsehttp/sensors.py +0 -0
  34. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/openevsehttp/websocket.py +0 -0
  35. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/pyproject.toml +0 -0
  36. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/python_openevse_http.egg-info/SOURCES.txt +0 -0
  37. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/python_openevse_http.egg-info/dependency_links.txt +0 -0
  38. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/python_openevse_http.egg-info/not-zip-safe +0 -0
  39. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/python_openevse_http.egg-info/requires.txt +0 -0
  40. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/python_openevse_http.egg-info/top_level.txt +0 -0
  41. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/requirements.txt +0 -0
  42. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/setup.cfg +0 -0
  43. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/setup.py +0 -0
  44. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/__init__.py +0 -0
  45. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/common.py +0 -0
  46. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/conftest.py +0 -0
  47. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/github_v2.json +0 -0
  48. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/github_v4.json +0 -0
  49. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/v2_json/config.json +0 -0
  50. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/v2_json/status.json +0 -0
  51. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/v4_json/config-broken-semver.json +0 -0
  52. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/v4_json/config-broken.json +0 -0
  53. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/v4_json/config-dev.json +0 -0
  54. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/v4_json/config-extra-version.json +0 -0
  55. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/v4_json/config-new.json +0 -0
  56. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/v4_json/config.json +0 -0
  57. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/v4_json/schedule.json +0 -0
  58. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/v4_json/status-broken.json +0 -0
  59. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/v4_json/status-new.json +0 -0
  60. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/v4_json/status.json +0 -0
  61. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/fixtures/websocket.json +0 -0
  62. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/test_commands.py +0 -0
  63. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/test_external_session.py +0 -0
  64. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/test_main_edge_cases.py +0 -0
  65. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/test_managers.py +0 -0
  66. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/test_mixins.py +0 -0
  67. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/test_properties.py +0 -0
  68. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/test_sensors.py +0 -0
  69. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/test_shaper.py +0 -0
  70. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tests/test_websocket.py +0 -0
  71. {python_openevse_http-1.0.1 → python_openevse_http-1.1.0}/tox.ini +0 -0
@@ -82,6 +82,6 @@ jobs:
82
82
  with:
83
83
  name: coverage-data
84
84
  - name: Upload coverage report
85
- uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6
85
+ uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v6
86
86
  with:
87
87
  token: ${{ secrets.CODECOV_TOKEN }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_openevse_http
3
- Version: 1.0.1
3
+ Version: 1.1.0
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,57 @@
1
+ """Utility functions for python-openevse-http."""
2
+
3
+ import logging
4
+ import re
5
+
6
+ from awesomeversion import AwesomeVersion
7
+
8
+ _LOGGER = logging.getLogger(__name__)
9
+
10
+
11
+ def normalize_version(version: str) -> str:
12
+ """Normalize the version string to strip 'dev' tag."""
13
+ if "dev" in version:
14
+ _LOGGER.debug("Stripping 'dev' from version.")
15
+ value = version.split(".")
16
+ return ".".join(value[0:3])
17
+ return version
18
+
19
+
20
+ def get_awesome_version(version: str) -> AwesomeVersion:
21
+ """Parse and normalize the version string, returning an AwesomeVersion.
22
+
23
+ Development and custom firmware versions (e.g. branch builds ending in a commit hash,
24
+ or standalone 'main'/'master' builds) are normalized to 'dev' to bypass feature flags
25
+ and version-checks.
26
+
27
+ Non-dev version strings are normalized by extracting the first stable 'X.Y.Z' match,
28
+ meaning pre-release, build, or other trailing tags (such as '_rc1' or '_alpha') are
29
+ removed and not preserved in the returned version.
30
+ """
31
+ # Match non-numeric git branch names (e.g. 'main', 'master', 'develop', or 'feature-x_abc123456')
32
+ # We use custom word boundary checks to avoid false positives like 'domain' matching 'main'
33
+ # or 'webmaster' matching 'master'.
34
+ is_dev = False
35
+ if re.search(r"[^/]+/[^_]+_[a-fA-F0-9]{6,40}_modified$", version, re.IGNORECASE):
36
+ is_dev = True
37
+ elif re.search(
38
+ r"(?:^|[^a-zA-Z0-9])(master|main)(?:[^a-zA-Z0-9]|$)", version, re.IGNORECASE
39
+ ):
40
+ is_dev = True
41
+ elif re.search(r"_[a-fA-F0-9]{7,40}$", version):
42
+ is_dev = True
43
+ elif re.search(
44
+ r"(?:^|[^a-zA-Z0-9])(dev|feature|fix|main|master)(?:[^a-zA-Z0-9].*?)?_[a-fA-F0-9]{6}$",
45
+ version,
46
+ re.IGNORECASE,
47
+ ):
48
+ is_dev = True
49
+
50
+ if is_dev:
51
+ version = "dev"
52
+ value = normalize_version(version)
53
+ if "dev" not in version:
54
+ firmware_search = re.search(r"\d+\.\d+\.\d+", value)
55
+ if firmware_search:
56
+ value = firmware_search.group(0)
57
+ return AwesomeVersion(value)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_openevse_http
3
- Version: 1.0.1
3
+ Version: 1.1.0
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
@@ -1,4 +1,4 @@
1
1
  -r requirements.txt
2
- prek==0.4.3
3
- ruff==0.15.15
2
+ prek==0.4.4
3
+ ruff==0.15.16
4
4
  mypy==2.1.0
@@ -1,5 +1,5 @@
1
1
  -r requirements.txt
2
- prek==0.4.3
2
+ prek==0.4.4
3
3
  pytest==9.0.3
4
4
  pytest-cov==7.1.0
5
5
  pytest-timeout==2.4.0
@@ -3,7 +3,7 @@
3
3
  "protocol": "-",
4
4
  "espflash": 4194304,
5
5
  "wifi_serial": "1234567890AB",
6
- "version": "random_a4f11e",
6
+ "version": "random_g4f11e",
7
7
  "diodet": 0,
8
8
  "gfcit": 0,
9
9
  "groundt": 0,
@@ -424,7 +424,8 @@ async def test_firmware_check(
424
424
  assert firmware["latest_version"] == "4.1.4"
425
425
 
426
426
  await test_charger_unknown_semver.update()
427
- assert test_charger_unknown_semver.wifi_firmware == "random_a4f11e"
427
+ # Changed from 'random_a4f11e' to 'random_g4f11e' to avoid false positive matches on the 6-character hex hash regex
428
+ assert test_charger_unknown_semver.wifi_firmware == "random_g4f11e"
428
429
  mock_aioclient.get(
429
430
  TEST_URL_GITHUB_v4,
430
431
  status=200,
@@ -432,7 +433,7 @@ async def test_firmware_check(
432
433
  )
433
434
  with caplog.at_level(logging.DEBUG):
434
435
  firmware = await test_charger_unknown_semver.firmware_check()
435
- assert "Using version: random_a4f11e" in caplog.text
436
+ assert "Using version: random_g4f11e" in caplog.text
436
437
  assert "Non-semver firmware version detected" in caplog.text
437
438
  assert firmware is None
438
439
 
@@ -634,6 +635,72 @@ async def test_version_check_limit():
634
635
  assert charger.version_check("2.0.0") is True
635
636
 
636
637
 
638
+ async def test_version_check_dev_branches():
639
+ """Test _version_check with dev branches.
640
+
641
+ Test that dev branch version strings bypass version checks and are correctly
642
+ treated as development builds. Also tests false-positive avoidance and
643
+ pre-release handling.
644
+ """
645
+ charger = OpenEVSE(SERVER_URL, session=MagicMock())
646
+
647
+ # Standalone dev branches - treated as dev, returns True
648
+ charger._config = {"version": "main"}
649
+ assert charger._version_check("2.0.0") is True
650
+
651
+ charger._config = {"version": "master"}
652
+ assert charger._version_check("2.0.0") is True
653
+
654
+ # 'main' branch with hash - treated as dev, returns True
655
+ charger._config = {"version": "main_abc1234"}
656
+ assert charger._version_check("2.0.0") is True
657
+
658
+ # 'master' branch with hash - treated as dev, returns True
659
+ charger._config = {"version": "master_abc1234"}
660
+ assert charger._version_check("2.0.0") is True
661
+
662
+ # Custom branch with 7-char hash - treated as dev, returns True
663
+ charger._config = {"version": "feature-ui_2b4ad2c"}
664
+ assert charger._version_check("2.0.0") is True
665
+
666
+ # Custom branch with 6-char hash - treated as dev, returns True
667
+ charger._config = {"version": "feature-ui_2b4ad2"}
668
+ assert charger._version_check("2.0.0") is True
669
+
670
+ # Case-insensitive hex hash matching (uppercase) - treated as dev, returns True
671
+ charger._config = {"version": "feature_1A2B3C"}
672
+ assert charger._version_check("2.0.0") is True
673
+
674
+ # Local dev build templates (category/branch_hash_modified) - treated as dev, returns True
675
+ charger._config = {
676
+ "version": "local_feature/gui-nightshift-default_2bcdf1d0_modified"
677
+ }
678
+ assert charger._version_check("2.0.0") is True
679
+
680
+ charger._config = {"version": "custom_branch/my-feature_abc123_modified"}
681
+ assert charger._version_check("2.0.0") is True
682
+
683
+ # False positives containing 'main' or 'master' but not at word boundaries,
684
+ # ending in 6-char hashes without dev keywords, or other non-matching modified strings
685
+ # — should fail version check
686
+ charger._config = {"version": "domain_abc123"}
687
+ assert charger._version_check("2.0.0") is False
688
+
689
+ charger._config = {"version": "webmaster_def456"}
690
+ assert charger._version_check("2.0.0") is False
691
+
692
+ charger._config = {"version": "unmodified"}
693
+ assert charger._version_check("2.0.0") is False
694
+
695
+ # Pre-release (rc) version — should fail when checking against a newer target
696
+ charger._config = {"version": "4.1.2_rc1"}
697
+ assert charger._version_check("5.0.0") is False
698
+
699
+ # Pre-release (alpha) version — should also fail when checking against a newer target
700
+ charger._config = {"version": "4.1.0_alpha"}
701
+ assert charger._version_check("5.0.0") is False
702
+
703
+
637
704
  # ── websocket lifecycle ──────────────────────────────────────────────
638
705
 
639
706
 
@@ -1,29 +0,0 @@
1
- """Utility functions for python-openevse-http."""
2
-
3
- import logging
4
- import re
5
-
6
- from awesomeversion import AwesomeVersion
7
-
8
- _LOGGER = logging.getLogger(__name__)
9
-
10
-
11
- def normalize_version(version: str) -> str:
12
- """Normalize the version string to strip 'dev' tag."""
13
- if "dev" in version:
14
- _LOGGER.debug("Stripping 'dev' from version.")
15
- value = version.split(".")
16
- return ".".join(value[0:3])
17
- return version
18
-
19
-
20
- def get_awesome_version(version: str) -> AwesomeVersion:
21
- """Parse and normalize the version string, returning an AwesomeVersion."""
22
- if "master" in version:
23
- version = "dev"
24
- value = normalize_version(version)
25
- if "dev" not in version:
26
- firmware_search = re.search(r"\d+\.\d+\.\d+", value)
27
- if firmware_search:
28
- value = firmware_search.group(0)
29
- return AwesomeVersion(value)