Repository: home-assistant-libs/zwave-js-server-python (language: python)

Issue:
TODO: Use `orjson` if it's available



Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 417753df8..6f2659c9f 100644
--- a/README.md
+++ b/README.md
@@ -32,3 +32,7 @@ try:
 except zwave_js_server.client.FailedCommand as err:
     print("Command failed with", err.error_code)
 ```
+
+## Optional dependencies
+
+`zwave-js-server-python` optionally supports `orjson` which can be installed with the `orjson` extra (e.g. `pip install zwave-js-server-python[orjson]`)
diff --git a/pyproject.toml b/pyproject.toml
index 235d132e5..995a94564 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,13 +21,16 @@ classifiers = [
 dependencies = ["aiohttp>3", "pydantic>=1.10.0"]
 dynamic = ["version"]
 
-[project.urls]
-"Source Code" = "https://github.com/home-assistant-libs/zwave-js-server-python"
-"Bug Reports" = "https://github.com/home-assistant-libs/zwave-js-server-python/issues"
+[project.optional-dependencies]
+orjson = ["orjson>=3.9.9"]
 
 [project.scripts]
 zwave-js-server-python = "zwave_js_server.__main__:main"
 
+[project.urls]
+"Source Code" = "https://github.com/home-assistant-libs/zwave-js-server-python"
+"Bug Reports" = "https://github.com/home-assistant-libs/zwave-js-server-python/issues"
+
 [tool.setuptools.dynamic]
 version = { attr = "zwave_js_server.const.__version__" }
 
diff --git a/requirements_dev.txt b/requirements_dev.txt
index 55024d1d3..4adb34c6e 100644
--- a/requirements_dev.txt
+++ b/requirements_dev.txt
@@ -1,4 +1,5 @@
 -r requirements.txt
+-r requirements_extra.txt
 -r requirements_lint.txt
 -r requirements_scripts.txt
 -r requirements_test.txt
diff --git a/requirements_extra.txt b/requirements_extra.txt
new file mode 100644
index 000000000..bf4f8d2c0
--- /dev/null
+++ b/requirements_extra.txt
@@ -0,0 +1,1 @@
+orjson==3.9.9
diff --git a/tox.ini b/tox.ini
index 344d99741..06c44bc24 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,16 +1,29 @@
 [tox]
-envlist = py311, lint, mypy
+envlist = clean, py311, test_orjson, lint, mypy
 skip_missing_interpreters = True
 
 [gh-actions]
 python =
-  3.11: py311, lint, mypy
+  3.11: clean, py311, test_orjson, lint, mypy
+
+[testenv:clean]
+deps = coverage
+skip_install = true
+commands = coverage erase
 
 [testenv]
 commands =
-  pytest --timeout=30 --cov=zwave_js_server --cov-report=xml {posargs}
+  pytest --timeout=30 --cov=zwave_js_server --cov-report=xml --cov-append {posargs}
+deps =
+  -rrequirements.txt
+  -rrequirements_test.txt
+
+[testenv:test_orjson]
+commands =
+  pytest --timeout=30 --cov=zwave_js_server --cov-report=xml --cov-append {posargs}
 deps =
   -rrequirements.txt
+  -rrequirements_extra.txt
   -rrequirements_test.txt
 
 [testenv:lint]
diff --git a/zwave_js_server/client.py b/zwave_js_server/client.py
index 5b63c4d2c..bc9c80980 100644
--- a/zwave_js_server/client.py
+++ b/zwave_js_server/client.py
@@ -6,6 +6,7 @@
 from collections.abc import Callable
 from copy import deepcopy
 from datetime import datetime
+import functools
 import logging
 from operator import itemgetter
 import pprint
@@ -39,6 +40,11 @@
 from .model.log_message import LogMessage
 from .model.version import VersionInfo, VersionInfoDataType
 
+try:
+    import orjson as json
+except ImportError:
+    import json
+
 SIZE_PARSE_JSON_EXECUTOR = 8192
 
 # Message IDs
@@ -458,9 +464,11 @@ async def _receive_json_or_raise(self) -> dict:
 
         try:
             if len(msg.data) > SIZE_PARSE_JSON_EXECUTOR:
-                data: dict = await self._loop.run_in_executor(None, msg.json)
+                data: dict = await self._loop.run_in_executor(
+                    None, functools.partial(msg.json, loads=json.loads)
+                )
             else:
-                data = msg.json()
+                data = msg.json(loads=json.loads)
         except ValueError as err:
             raise InvalidMessage("Received invalid JSON.") from err
 
@@ -557,7 +565,7 @@ async def _send_json_message(self, message: dict[str, Any]) -> None:
                 }
             )
 
-        await self._client.send_json(message)
+        await self._client.send_json(message, dumps=json.dumps)
 
     async def __aenter__(self) -> Client:
         """Connect to the websocket."""


Test patch:
diff --git a/test/conftest.py b/test/conftest.py
index fe04a2565..fc4948c0e 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -177,7 +177,7 @@ async def receive():
 
     ws_client.receive.side_effect = receive
 
-    async def close_client(msg):
+    async def close_client(msg, dumps=json.dumps):
         """Close the client."""
         if msg["command"] in ("initialize", "start_listening"):
             return
@@ -328,7 +328,7 @@ def apply_mock_command(
         mock_responses.append((match_command, response, success))
         return ack_commands
 
-    async def set_response(message):
+    async def set_response(message, dumps=json.dumps):
         """Check the message and set the mocked response if a command matches."""
         for match_command, response, success in mock_responses:
             if all(message[key] == value for key, value in match_command.items()):