From d81c746d0cd230300ef46af6788652a6c6ae81c3 Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Tue, 3 Mar 2026 17:58:28 -0700 Subject: [PATCH 1/4] chore: Adopt ruff for linting and formatting, add Python 3.14 support, and update development dependencies. --- .github/workflows/test.yml | 5 +++ .pre-commit-config.yaml | 52 +++++------------------ EXTERNAL_SESSION.md | 12 +++--- example_external_session.py | 6 +-- openevsehttp/__main__.py | 47 +++++++------------- openevsehttp/websocket.py | 2 +- pylintrc | 41 ------------------ pyproject.toml | 32 ++++++++++++++ requirements_lint.txt | 9 ++-- requirements_test.txt | 2 +- setup.py | 1 + tests/fixtures/github_v2.json | 1 - tests/fixtures/github_v4.json | 1 - tests/fixtures/v4_json/config-new.json | 2 +- tests/fixtures/v4_json/status-broken.json | 4 +- tests/fixtures/v4_json/status-new.json | 2 +- tests/fixtures/v4_json/status.json | 4 +- tests/fixtures/websocket.json | 2 +- tests/test_main.py | 6 +-- tests/test_websocket.py | 1 - tox.ini | 14 +++--- 21 files changed, 93 insertions(+), 153 deletions(-) delete mode 100644 pylintrc create mode 100644 pyproject.toml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2a1a619..720c908 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,11 @@ jobs: strategy: matrix: python-version: + - "3.10" + - "3.11" + - "3.12" - "3.13" + - "3.14" steps: - uses: actions/checkout@v4 @@ -35,6 +39,7 @@ jobs: - name: Test with tox run: tox - name: Upload coverage data + if: ${{ matrix.python-version == '3.14' }} uses: "actions/upload-artifact@v4" with: name: coverage-data diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7aaaa67..249c70b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,20 +1,14 @@ +default_install_hook_types: [pre-commit, pre-push] repos: - - repo: https://github.com/asottile/pyupgrade - rev: v2.31.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.9.7 hooks: - - id: pyupgrade - args: ["--py39-plus"] - - repo: https://github.com/ambv/black - rev: 22.3.0 - hooks: - - id: black - language_version: python3 + - id: ruff args: - - --safe - - --quiet - files: ^(openevsehttp/.+)?[^/]+\.py$ + - --fix + - id: ruff-format - repo: https://github.com/codespell-project/codespell - rev: v1.17.1 + rev: v2.4.1 hooks: - id: codespell args: @@ -22,30 +16,8 @@ repos: - --skip="./.*,*.csv,*.json" - --quiet-level=2 exclude_types: [csv, json] - - repo: https://gitlab.com/pycqa/flake8 - rev: 4.0.1 - hooks: - - id: flake8 - additional_dependencies: - - flake8-docstrings==1.5.0 - files: ^openevsehttp/.+\.py$ - args: - - --max-line-length=500 - - --ignore=E203,E266,E501,W503 - - --max-complexity=18 - - --select=B,C,E,F,W,T4,B9 - - repo: https://github.com/pre-commit/mirrors-isort - rev: v5.10.1 - hooks: - - id: isort - args: - - --multi-line=3 - - --trailing-comma - - --force-grid-wrap=0 - - --use-parentheses - - --line-width=88 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v5.0.0 hooks: - id: check-executables-have-shebangs stages: [manual] @@ -57,12 +29,8 @@ repos: - id: check-docstring-first - id: check-yaml - id: debug-statements - - repo: https://github.com/PyCQA/pydocstyle - rev: 6.1.1 - hooks: - - id: pydocstyle - repo: https://github.com/adrienverge/yamllint.git - rev: v1.24.2 + rev: v1.35.1 hooks: - id: yamllint - repo: https://github.com/prettier/prettier @@ -71,7 +39,7 @@ repos: - id: prettier stages: [manual] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v0.930" + rev: "v1.15.0" hooks: - id: mypy files: ^openevsehttp/.+\.py$ diff --git a/EXTERNAL_SESSION.md b/EXTERNAL_SESSION.md index f853b27..7acbab5 100644 --- a/EXTERNAL_SESSION.md +++ b/EXTERNAL_SESSION.md @@ -25,11 +25,11 @@ async def main(): async with aiohttp.ClientSession(timeout=timeout) as session: # Pass the session to OpenEVSE charger = OpenEVSE("openevse.local", session=session) - + # Use the charger normally await charger.update() print(f"Status: {charger.status}") - + # Clean up await charger.ws_disconnect() # Session will be closed by the context manager @@ -43,11 +43,11 @@ from openevsehttp import OpenEVSE async def main(): # The library creates and manages its own sessions charger = OpenEVSE("openevse.local") - + # Use the charger normally await charger.update() print(f"Status: {charger.status}") - + await charger.ws_disconnect() ``` @@ -62,11 +62,11 @@ async def main(): # Use the same session for multiple chargers charger1 = OpenEVSE("charger1.local", session=session) charger2 = OpenEVSE("charger2.local", session=session) - + # Both chargers use the same session await charger1.update() await charger2.update() - + await charger1.ws_disconnect() await charger2.ws_disconnect() ``` diff --git a/example_external_session.py b/example_external_session.py index e426aff..8ff0c06 100644 --- a/example_external_session.py +++ b/example_external_session.py @@ -14,7 +14,7 @@ async def example_with_external_session(): - """Example using an external session.""" + """Demonstrate using an external session.""" # Create your own session with custom settings timeout = aiohttp.ClientTimeout(total=30) async with aiohttp.ClientSession(timeout=timeout) as session: @@ -32,7 +32,7 @@ async def example_with_external_session(): async def example_without_external_session(): - """Example without external session (backward compatible).""" + """Demonstrate without external session (backward compatible).""" # The library will create and manage its own sessions charger = OpenEVSE("openevse.local") @@ -45,7 +45,7 @@ async def example_without_external_session(): async def example_shared_session(): - """Example sharing a session between multiple clients.""" + """Demonstrate sharing a session between multiple clients.""" async with aiohttp.ClientSession() as session: # Use the same session for multiple chargers charger1 = OpenEVSE("charger1.local", session=session) diff --git a/openevsehttp/__main__.py b/openevsehttp/__main__.py index fe1421c..7a45a46 100644 --- a/openevsehttp/__main__.py +++ b/openevsehttp/__main__.py @@ -6,8 +6,9 @@ import json import logging import re +from collections.abc import Callable from datetime import datetime, timedelta, timezone -from typing import Any, Callable, Dict, Union +from typing import Any import aiohttp # type: ignore from aiohttp.client_exceptions import ContentTypeError, ServerTimeoutError @@ -356,7 +357,7 @@ async def repeat(self, interval, func, *args, **kwargs): await asyncio.sleep(interval) await func(*args, **kwargs) - async def get_schedule(self) -> Union[Dict[str, str], Dict[str, Any]]: + async def get_schedule(self) -> dict[str, str] | dict[str, Any]: """Return the current schedule.""" url = f"{self.url}schedule" @@ -375,9 +376,7 @@ async def set_charge_mode(self, mode: str = "fast") -> None: data = {"charge_mode": mode} _LOGGER.debug("Setting charge mode to %s", mode) - response = await self.process_request( - url=url, method="post", data=data - ) # noqa: E501 + response = await self.process_request(url=url, method="post", data=data) # noqa: E501 result = response["msg"] if result not in ["done", "no change"]: _LOGGER.error("Problem issuing command: %s", response["msg"]) @@ -402,13 +401,11 @@ async def divert_mode(self) -> dict[str, str] | dict[str, Any]: data = {"divert_enabled": mode} _LOGGER.debug("Toggling divert: %s", mode) - response = await self.process_request( - url=url, method="post", data=data - ) # noqa: E501 + response = await self.process_request(url=url, method="post", data=data) # noqa: E501 _LOGGER.debug("divert_mode response: %s", response) return response - async def get_override(self) -> Union[Dict[str, str], Dict[str, Any]]: + async def get_override(self) -> dict[str, str] | dict[str, Any]: """Get the manual override status.""" if not self._version_check("4.0.1"): _LOGGER.debug("Feature not supported for older firmware.") @@ -455,9 +452,7 @@ async def set_override( _LOGGER.debug("Override data: %s", data) _LOGGER.debug("Setting override config on %s", url) - response = await self.process_request( - url=url, method="post", data=data - ) # noqa: E501 + response = await self.process_request(url=url, method="post", data=data) # noqa: E501 return response async def toggle_override(self) -> None: @@ -528,9 +523,7 @@ async def set_service_level(self, level: int = 2) -> None: data = {"service": level} _LOGGER.debug("Set service level to: %s", level) - response = await self.process_request( - url=url, method="post", data=data - ) # noqa: E501 + response = await self.process_request(url=url, method="post", data=data) # noqa: E501 _LOGGER.debug("service response: %s", response) result = response["msg"] if result not in ["done", "no change"]: @@ -800,7 +793,7 @@ async def set_limit( raise UnsupportedFeature url = f"{self.url}limit" - data: Dict[str, Any] = await self.get_limit() + data: dict[str, Any] = await self.get_limit() valid_types = ["time", "energy", "soc", "range"] if limit_type not in valid_types: @@ -813,9 +806,7 @@ async def set_limit( _LOGGER.debug("Limit data: %s", data) _LOGGER.debug("Setting limit config on %s", url) - response = await self.process_request( - url=url, method="post", data=data - ) # noqa: E501 + response = await self.process_request(url=url, method="post", data=data) # noqa: E501 return response async def clear_limit(self) -> Any: @@ -825,12 +816,10 @@ async def clear_limit(self) -> Any: raise UnsupportedFeature url = f"{self.url}limit" - data: Dict[str, Any] = {} + data: dict[str, Any] = {} _LOGGER.debug("Clearing limit config on %s", url) - response = await self.process_request( - url=url, method="delete", data=data - ) # noqa: E501 + response = await self.process_request(url=url, method="delete", data=data) # noqa: E501 return response async def get_limit(self) -> Any: @@ -840,12 +829,10 @@ async def get_limit(self) -> Any: raise UnsupportedFeature url = f"{self.url}limit" - data: Dict[str, Any] = {} + data: dict[str, Any] = {} _LOGGER.debug("Getting limit config on %s", url) - response = await self.process_request( - url=url, method="get", data=data - ) # noqa: E501 + response = await self.process_request(url=url, method="get", data=data) # noqa: E501 return response async def make_claim( @@ -880,9 +867,7 @@ async def make_claim( _LOGGER.debug("Claim data: %s", data) _LOGGER.debug("Setting up claim on %s", url) - response = await self.process_request( - url=url, method="post", data=data - ) # noqa: E501 + response = await self.process_request(url=url, method="post", data=data) # noqa: E501 return response async def release_claim(self, client: int = CLIENT) -> Any: @@ -1387,7 +1372,7 @@ def freeram(self) -> int | None: # Safety counts @property def checks_count(self) -> dict: - """Return the saftey checks counts.""" + """Return the safety checks counts.""" attributes = ("gfcicount", "nogndcount", "stuckcount") counts = {} if self._status is not None and set(attributes).issubset(self._status.keys()): diff --git a/openevsehttp/websocket.py b/openevsehttp/websocket.py index 0788eef..f7bb34f 100644 --- a/openevsehttp/websocket.py +++ b/openevsehttp/websocket.py @@ -170,7 +170,7 @@ async def keepalive(self): if self._ping and self._pong: time_delta = self._pong - self._ping if time_delta < datetime.timedelta(0): - # Negitive time should indicate no pong reply so consider the + # Negative time should indicate no pong reply so consider the # websocket disconnected. self._error_reason = ERROR_PING_TIMEOUT await self._set_state(STATE_DISCONNECTED) diff --git a/pylintrc b/pylintrc deleted file mode 100644 index e7646dc..0000000 --- a/pylintrc +++ /dev/null @@ -1,41 +0,0 @@ -[MASTER] -ignore=tests -# Use a conservative default here; 2 should speed up most setups and not hurt -# any too bad. Override on command line as appropriate. -jobs=2 -persistent=no - -[BASIC] -good-names=id,i,j,k,ex,Run,_,fp -max-attributes=15 - -[MESSAGES CONTROL] -# Reasons disabled: -# locally-disabled - it spams too much -# too-many-* - are not enforced for the sake of readability -# too-few-* - same as too-many-* -# import-outside-toplevel - TODO -disable= - duplicate-code, - fixme, - import-outside-toplevel, - locally-disabled, - too-few-public-methods, - too-many-arguments, - too-many-public-methods, - too-many-instance-attributes, - too-many-branches, - too-many-statements, - too-many-lines, - too-many-positional-arguments, - too-many-return-statements - -[REPORTS] -score=no - -[TYPECHECK] -# For attrs -ignored-classes=_CountingAttr - -[FORMAT] -expected-line-ending-format=LF diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b45d339 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[tool.ruff] +target-version = "py310" +line-length = 88 + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "C", # flake8-comprehensions + "B", # flake8-bugbear + "UP", # pyupgrade + "D", # pydocstyle +] +ignore = [ + "D100", # missing docstring in public module + "D101", # missing docstring in public class + "D102", # missing docstring in public method + "D103", # missing docstring in public function + "D104", # missing docstring in public package + "D105", # missing docstring in magic method + "D106", # missing docstring in public nested class + "D107", # missing docstring in __init__ + "D202", # No blank lines allowed after function docstring + "D203", # 1 blank line required before class docstring + "D213", # Multi-line docstring summary should start at the second line + "E501", # line too long +] + +[tool.ruff.lint.mccabe] +max-complexity = 18 diff --git a/requirements_lint.txt b/requirements_lint.txt index eaf9307..2d179ee 100644 --- a/requirements_lint.txt +++ b/requirements_lint.txt @@ -1,7 +1,4 @@ -r requirements.txt -black==26.1.0 -flake8==7.3.0 -mypy==1.19.1 -pydocstyle==6.3.0 -isort -pylint==4.0.5 \ No newline at end of file +pre-commit==4.1.0 +ruff==0.9.7 +mypy==1.15.0 diff --git a/requirements_test.txt b/requirements_test.txt index e152bed..88d5a34 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -8,4 +8,4 @@ requests_mock aiohttp aioresponses tox==4.47.0 -freezegun \ No newline at end of file +freezegun diff --git a/setup.py b/setup.py index 6b21153..ac23680 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "License :: OSI Approved :: Apache Software License", ], ) diff --git a/tests/fixtures/github_v2.json b/tests/fixtures/github_v2.json index 4f64ec0..7b53089 100644 --- a/tests/fixtures/github_v2.json +++ b/tests/fixtures/github_v2.json @@ -72,4 +72,3 @@ "zipball_url": "https://api.github.com/repos/OpenEVSE/ESP8266_WiFi_v2.x/zipball/2.9.1", "body": "- Fix GUI typos " } - \ No newline at end of file diff --git a/tests/fixtures/github_v4.json b/tests/fixtures/github_v4.json index 42c2048..3fb4592 100644 --- a/tests/fixtures/github_v4.json +++ b/tests/fixtures/github_v4.json @@ -209,4 +209,3 @@ "body": "> **_IMPORTANT:_** Breaking change! V4.x recommends a minimum of [7.1.2](https://github.com/openenergymonitor/open_evse/releases) of the OpenEVSE controller firmware, some features do not function with older EVSE controller firmware. \r\n\r\n## What's Changed\r\n* Add RFID support and upgrade OCPP library (RFID hardware required) by @matth-x in https://github.com/OpenEVSE/ESP32_WiFi_V4.x/pull/325\r\n* Huzzah board button press note by @mikebaz in https://github.com/OpenEVSE/ESP32_WiFi_V4.x/pull/365\r\n* Fixed broken/hi-jacked url from openevse.org to openevse.com by @d3wy in https://github.com/OpenEVSE/ESP32_WiFi_V4.x/pull/363\r\n* Fix for WiFi portal page by @jeremypoulter in https://github.com/OpenEVSE/ESP32_WiFi_V4.x/pull/375\r\n* Platform IO ESP32 core 4.4 by @jeremypoulter in https://github.com/OpenEVSE/ESP32_WiFi_V4.x/pull/207\r\n* Adding back the ability to use disabled instead of sleep to stop charging by @jeremypoulter in https://github.com/OpenEVSE/ESP32_WiFi_V4.x/pull/371\r\n* GZip the static content by @jeremypoulter in https://github.com/OpenEVSE/ESP32_WiFi_V4.x/pull/384\r\n* Add default delay timmer and apply a random start delay to the timer to comply with UK regulations @jeremypoulter in https://github.com/OpenEVSE/ESP32_WiFi_V4.x/pull/391\r\n\r\n**Full Changelog**: https://github.com/OpenEVSE/ESP32_WiFi_V4.x/compare/4.1.3...4.1.4\r\n\r\n## Updating Firmware\r\n\r\nFirmware can be updated via the web interface. **Be sure to select the correct firmware for your hardware and unzip before uploading .bin**\r\n\r\n![Wifi Modules](https://github.com/OpenEVSE/ESP32_WiFi_V4.x/raw/master/docs/openevse-wifi-modules.png) \r\n\r\n- Huzzah ESP8266 - can only run V2.x firmware, see [archive V2.x repository](https://github.com/OpenEVSE/ESP8266_WiFi_v2.x)\r\n- Huzzah ESP32 - can run V3.x and V4.x firmware - use `openevse_huzzah32.bin`\r\n- **OpenEVSE V1 - designed for V4.x firmware - currently shipping in 2021** use `openevse_wifi_v1.bin`\r\n- [Olimex ESP32 Gateway (Wired Ethernet)](docs/wired-ethernet.md) - can run V3.x and V4.x firmware - use `esp32-gateway-e.bin` - NOTE: Ethernet gateway does not support HTTP update, FW must be uploaded via micro usb and esptool, see [docs](docs/wired-ethernet.md).\r\n\r\n**After updating FW browser cache may need clearing to reload the web interface**\r\n\r\n*** \r\n\r\nIf the web interface cannot be loaded, the firmware must be loaded via a [USB to serial programmer,](https://shop.openenergymonitor.com/programmer-usb-to-serial-uart/) see [instructions in Readme.md](https://github.com/OpenEVSE/ESP32_WiFi_V3.x#via-usb-serial-programmer). ", "mentions_count": 4 } - \ No newline at end of file diff --git a/tests/fixtures/v4_json/config-new.json b/tests/fixtures/v4_json/config-new.json index 87162df..5a96ac5 100644 --- a/tests/fixtures/v4_json/config-new.json +++ b/tests/fixtures/v4_json/config-new.json @@ -105,4 +105,4 @@ "default_state": true, "mqtt_protocol": "mqtt", "charge_mode": "eco" -} \ No newline at end of file +} diff --git a/tests/fixtures/v4_json/status-broken.json b/tests/fixtures/v4_json/status-broken.json index d547d84..005fde2 100644 --- a/tests/fixtures/v4_json/status-broken.json +++ b/tests/fixtures/v4_json/status-broken.json @@ -40,8 +40,8 @@ "time": "2021-08-10T23:00:11Z", "offset": "-0700", "shaper": 1, - "shaper_live_pwr": 2299, - "shaper_max_pwr": 4000, + "shaper_live_pwr": 2299, + "shaper_max_pwr": 4000, "shaper_cur": 255, "vehicle_soc": 75, "vehicle_range": 468, diff --git a/tests/fixtures/v4_json/status-new.json b/tests/fixtures/v4_json/status-new.json index cf72508..6956170 100644 --- a/tests/fixtures/v4_json/status-new.json +++ b/tests/fixtures/v4_json/status-new.json @@ -77,4 +77,4 @@ "tesla_vehicle_count": false, "tesla_vehicle_id": false, "tesla_vehicle_name": false -} \ No newline at end of file +} diff --git a/tests/fixtures/v4_json/status.json b/tests/fixtures/v4_json/status.json index bbd4f6e..61e014f 100644 --- a/tests/fixtures/v4_json/status.json +++ b/tests/fixtures/v4_json/status.json @@ -43,8 +43,8 @@ "time": "2021-08-10T23:00:11Z", "offset": "-0700", "shaper": 1, - "shaper_live_pwr": 2299, - "shaper_max_pwr": 4000, + "shaper_live_pwr": 2299, + "shaper_max_pwr": 4000, "shaper_cur": 21, "vehicle_soc": 75, "vehicle_range": 468, diff --git a/tests/fixtures/websocket.json b/tests/fixtures/websocket.json index d4b77d1..df08070 100644 --- a/tests/fixtures/websocket.json +++ b/tests/fixtures/websocket.json @@ -65,4 +65,4 @@ "tesla_vehicle_count": false, "tesla_vehicle_id": false, "tesla_vehicle_name": false -} \ No newline at end of file +} diff --git a/tests/test_main.py b/tests/test_main.py index 25104b5..327a650 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -864,7 +864,7 @@ async def test_toggle_override_v2_err(test_charger_v2, mock_aioclient, caplog): """Test v4 Status reply.""" await test_charger_v2.update() content_error = mock.Mock() - setattr(content_error, "real_url", f"{TEST_URL_RAPI}") + content_error.real_url = f"{TEST_URL_RAPI}" mock_aioclient.post( TEST_URL_RAPI, exception=ContentTypeError( @@ -1079,7 +1079,7 @@ async def test_set_divertmode( async def test_test_and_get(test_charger, test_charger_v2, mock_aioclient, caplog): - """Test v4 Status reply""" + """Test v4 Status reply.""" data = await test_charger.test_and_get() mock_aioclient.get( TEST_URL_CONFIG, @@ -1118,7 +1118,7 @@ async def test_firmware_check( mock_aioclient, caplog, ): - """Test v4 Status reply""" + """Test v4 Status reply.""" await test_charger.update() mock_aioclient.get( TEST_URL_GITHUB_v4, diff --git a/tests/test_websocket.py b/tests/test_websocket.py index 0a8586e..e56f160 100644 --- a/tests/test_websocket.py +++ b/tests/test_websocket.py @@ -294,7 +294,6 @@ async def test_state_setter_threadsafe_fallback(ws_client): ) as mock_create_task, patch("asyncio.get_event_loop", return_value=mock_loop), ): - ws_client.state = STATE_CONNECTED assert ws_client.state == STATE_CONNECTED diff --git a/tox.ini b/tox.ini index 5a5b0bf..19b6494 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py310, py311, py312, py313, lint, mypy +envlist = py310, py311, py312, py313, py314, lint, mypy skip_missing_interpreters = True [gh-actions] @@ -7,7 +7,8 @@ python = 3.10: py310 3.11: py311 3.12: py312 - 3.13: py313, lint, mypy + 3.13: py313 + 3.14: py314, lint, mypy [pytest] asyncio_default_fixture_loop_scope=function @@ -22,10 +23,8 @@ deps = basepython = python3 ignore_errors = True commands = - black --check ./ - flake8 openevsehttp - pylint openevsehttp - pydocstyle openevsehttp tests + ruff format --check ./ + ruff check openevsehttp tests deps = -rrequirements_lint.txt -rrequirements_test.txt @@ -37,6 +36,3 @@ commands = mypy openevsehttp deps = -rrequirements_lint.txt - -[flake8] -max-line-length = 88 \ No newline at end of file From c2e2b9c1d5973bf8bf751691ae3c806c22b6394a Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Tue, 3 Mar 2026 18:02:41 -0700 Subject: [PATCH 2/4] require Python 3.13 or newer --- .github/workflows/test.yml | 3 --- setup.py | 5 +---- tox.ini | 5 +---- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 720c908..2b7ba4c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,9 +18,6 @@ jobs: strategy: matrix: python-version: - - "3.10" - - "3.11" - - "3.12" - "3.13" - "3.14" diff --git a/setup.py b/setup.py index ac23680..3094e8e 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ long_description=README_FILE.read_text(encoding="utf-8"), long_description_content_type="text/markdown", packages=find_packages(exclude=["test.*", "tests"]), - python_requires=">=3.10", + python_requires=">=3.13", install_requires=["aiohttp"], license="Apache-2.0", entry_points={}, @@ -30,9 +30,6 @@ "Intended Audience :: Developers", "Natural Language :: English", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "License :: OSI Approved :: Apache Software License", diff --git a/tox.ini b/tox.ini index 19b6494..5766ab5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,9 @@ [tox] -envlist = py310, py311, py312, py313, py314, lint, mypy +envlist = py313, py314, lint, mypy skip_missing_interpreters = True [gh-actions] python = - 3.10: py310 - 3.11: py311 - 3.12: py312 3.13: py313 3.14: py314, lint, mypy From 8b88ce170529bf16dda875b86324d50a904cdafe Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Tue, 3 Mar 2026 18:07:01 -0700 Subject: [PATCH 3/4] fix version conflict --- requirements_lint.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_lint.txt b/requirements_lint.txt index 2d179ee..844cb13 100644 --- a/requirements_lint.txt +++ b/requirements_lint.txt @@ -1,4 +1,4 @@ -r requirements.txt -pre-commit==4.1.0 +pre-commit==4.5.1 ruff==0.9.7 mypy==1.15.0 From f98c212d0702ff89e4ef214fd5c4abb37be4b300 Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Wed, 4 Mar 2026 07:34:37 -0700 Subject: [PATCH 4/4] conditionally include JSON data in HTTP requests --- openevsehttp/__main__.py | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/openevsehttp/__main__.py b/openevsehttp/__main__.py index 7a45a46..5d963b3 100644 --- a/openevsehttp/__main__.py +++ b/openevsehttp/__main__.py @@ -152,12 +152,10 @@ async def _process_request_with_session( method, ) try: - async with http_method( - url, - data=rapi, - json=data, - auth=auth, - ) as resp: + kwargs = {"data": rapi, "auth": auth} + if data is not None: + kwargs["json"] = data + async with http_method(url, **kwargs) as resp: try: message = await resp.text() except UnicodeDecodeError: @@ -376,7 +374,7 @@ async def set_charge_mode(self, mode: str = "fast") -> None: data = {"charge_mode": mode} _LOGGER.debug("Setting charge mode to %s", mode) - response = await self.process_request(url=url, method="post", data=data) # noqa: E501 + response = await self.process_request(url=url, method="post", data=data) result = response["msg"] if result not in ["done", "no change"]: _LOGGER.error("Problem issuing command: %s", response["msg"]) @@ -401,7 +399,7 @@ async def divert_mode(self) -> dict[str, str] | dict[str, Any]: data = {"divert_enabled": mode} _LOGGER.debug("Toggling divert: %s", mode) - response = await self.process_request(url=url, method="post", data=data) # noqa: E501 + response = await self.process_request(url=url, method="post", data=data) _LOGGER.debug("divert_mode response: %s", response) return response @@ -452,7 +450,7 @@ async def set_override( _LOGGER.debug("Override data: %s", data) _LOGGER.debug("Setting override config on %s", url) - response = await self.process_request(url=url, method="post", data=data) # noqa: E501 + response = await self.process_request(url=url, method="post", data=data) return response async def toggle_override(self) -> None: @@ -523,7 +521,7 @@ async def set_service_level(self, level: int = 2) -> None: data = {"service": level} _LOGGER.debug("Set service level to: %s", level) - response = await self.process_request(url=url, method="post", data=data) # noqa: E501 + response = await self.process_request(url=url, method="post", data=data) _LOGGER.debug("service response: %s", response) result = response["msg"] if result not in ["done", "no change"]: @@ -806,7 +804,7 @@ async def set_limit( _LOGGER.debug("Limit data: %s", data) _LOGGER.debug("Setting limit config on %s", url) - response = await self.process_request(url=url, method="post", data=data) # noqa: E501 + response = await self.process_request(url=url, method="post", data=data) return response async def clear_limit(self) -> Any: @@ -816,10 +814,9 @@ async def clear_limit(self) -> Any: raise UnsupportedFeature url = f"{self.url}limit" - data: dict[str, Any] = {} _LOGGER.debug("Clearing limit config on %s", url) - response = await self.process_request(url=url, method="delete", data=data) # noqa: E501 + response = await self.process_request(url=url, method="delete") return response async def get_limit(self) -> Any: @@ -829,10 +826,9 @@ async def get_limit(self) -> Any: raise UnsupportedFeature url = f"{self.url}limit" - data: dict[str, Any] = {} _LOGGER.debug("Getting limit config on %s", url) - response = await self.process_request(url=url, method="get", data=data) # noqa: E501 + response = await self.process_request(url=url, method="get") return response async def make_claim( @@ -867,7 +863,7 @@ async def make_claim( _LOGGER.debug("Claim data: %s", data) _LOGGER.debug("Setting up claim on %s", url) - response = await self.process_request(url=url, method="post", data=data) # noqa: E501 + response = await self.process_request(url=url, method="post", data=data) return response async def release_claim(self, client: int = CLIENT) -> Any: @@ -879,7 +875,7 @@ async def release_claim(self, client: int = CLIENT) -> Any: url = f"{self.url}claims/{client}" _LOGGER.debug("Releasing claim on %s", url) - response = await self.process_request(url=url, method="delete") # noqa: E501 + response = await self.process_request(url=url, method="delete") return response async def list_claims(self, target: bool | None = None) -> Any: @@ -895,7 +891,7 @@ async def list_claims(self, target: bool | None = None) -> Any: url = f"{self.url}claims{target_check}" _LOGGER.debug("Getting claims on %s", url) - response = await self.process_request(url=url, method="get") # noqa: E501 + response = await self.process_request(url=url, method="get") return response async def set_led_brightness(self, level: int) -> None: @@ -909,7 +905,7 @@ async def set_led_brightness(self, level: int) -> None: data["led_brightness"] = level _LOGGER.debug("Setting LED brightness to %s", level) - await self.process_request(url=url, method="post", data=data) # noqa: E501 + await self.process_request(url=url, method="post", data=data) async def set_divert_mode(self, mode: str = "fast") -> None: """Set the divert mode."""