Skip to content

Commit 756e18c

Browse files
committed
feat(plugin): hass-cli raw ws for websocket apis
Why: * testing out websocket apis before there are commands for it are tedious. would be nice to have ability like `raw get` to access websocket api. This change addreses the need by: * introduce `hass-cli raw ws <method> --json <optional data>` command. Example: ``` hass-cli raw ws config/area_registry/delete --json='{ "area_id":"2c8bf93c8082492f99c989896962f207" }' ``` * Fixed a few mypy / static check errors. Resolves #271
1 parent ea1b455 commit 756e18c

File tree

5 files changed

+117
-3
lines changed

5 files changed

+117
-3
lines changed

homeassistant_cli/autocompletion.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,25 @@ def api_methods(
168168
completions = []
169169
for name, value in getmembers(hassconst):
170170
if name.startswith('URL_API_'):
171-
completions.append((value, name[len('URL_API_'):]))
171+
completions.append((value, name[len('URL_API_') :]))
172+
173+
completions.sort()
174+
175+
return [c for c in completions if incomplete in c[0]]
176+
177+
178+
def wsapi_methods(
179+
ctx: Configuration, args: List, incomplete: str
180+
) -> List[Tuple[str, str]]:
181+
"""Auto completion for websocket methods."""
182+
_init_ctx(ctx)
183+
184+
from inspect import getmembers
185+
186+
completions = []
187+
for name, value in getmembers(hassconst):
188+
if name.startswith('WS_TYPE_'):
189+
completions.append((value, name[len('WS_TYPE_') :]))
172190

173191
completions.sort()
174192

homeassistant_cli/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@ def auto_output(self, auto_output: str) -> str:
170170
def yaml(self) -> YAML:
171171
"""Create default yaml parser."""
172172
if self:
173-
return yaml.yaml()
173+
yaml.yaml()
174+
return yaml.yaml()
174175

175176
def yamlload(self, source: str) -> Any:
176177
"""Load YAML from source."""

homeassistant_cli/plugins/raw.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Raw plugin for Home Assistant CLI (hass-cli)."""
22
import json as json_
33
import logging
4+
from typing import Any, Dict, List, cast # noqa: F401
45

56
import click
67

@@ -67,3 +68,31 @@ def post(ctx: Configuration, method, json):
6768
response = api.restapi(ctx, 'post', method, data)
6869

6970
_report(ctx, "GET", method, response)
71+
72+
73+
@cli.command("ws")
74+
@click.argument( # type: ignore
75+
'wstype', autocompletion=autocompletion.wsapi_methods
76+
)
77+
@click.option('--json')
78+
@pass_context
79+
def websocket(ctx: Configuration, wstype, json): # noqa: D301
80+
"""Send a websocket request against /api/websocket.
81+
82+
WSTYPE is name of websocket methods.
83+
84+
\b
85+
--json is dictionary to pass in addition to the type.
86+
Example: --json='{ "area_id":"2c8bf93c8082492f99c989896962f207" }'
87+
"""
88+
if json:
89+
data = json_.loads(json)
90+
else:
91+
data = {}
92+
93+
frame = {'type': wstype}
94+
frame = {**frame, **data} # merging data into frame
95+
96+
response = cast(List[Dict[str, Any]], api.wsapi(ctx, frame))
97+
98+
ctx.echo(format_output(ctx, response))

homeassistant_cli/yaml.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
def yaml() -> YAML:
1010
"""Return default YAML parser."""
1111
yamlp = YAML(typ='safe', pure=True)
12-
yamlp.preserve_quotes = True
12+
yamlp.preserve_quotes = cast(None, True)
1313
yamlp.default_flow_style = False
1414
return yamlp
1515

tests/test_raw.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Tests file for Home Assistant CLI (hass-cli)."""
22
import json
3+
import unittest.mock as mocker
4+
from unittest.mock import ANY
35

46
from click.testing import CliRunner
57
import requests_mock
@@ -59,3 +61,67 @@ def test_apimethod_completion(default_services) -> None:
5961
resultdict = dict(result)
6062

6163
assert "/api/discovery_info" in resultdict
64+
65+
66+
def test_wsapimethod_completion(default_services) -> None:
67+
"""Test completion for raw ws API methods."""
68+
cfg = Configuration()
69+
70+
result = autocompletion.wsapi_methods(
71+
cfg, ["raw", "get"], "config/device_registry/l"
72+
)
73+
assert len(result) == 1
74+
75+
resultdict = dict(result)
76+
77+
assert "config/device_registry/list" in resultdict
78+
79+
80+
def test_raw_ws() -> None:
81+
"""Test websocket."""
82+
with mocker.patch(
83+
'homeassistant_cli.remote.wsapi', return_value={"result": "worked"}
84+
) as mockmethod:
85+
86+
runner = CliRunner()
87+
result = runner.invoke(
88+
cli.cli,
89+
["--output=json", "raw", "ws", "config/wsmethod"],
90+
catch_exceptions=False,
91+
)
92+
assert result.exit_code == 0
93+
94+
mockmethod.assert_called_once()
95+
mockmethod.assert_called_with(ANY, {"type": "config/wsmethod"})
96+
97+
data = json.loads(result.output)
98+
assert len(data) == 1
99+
100+
101+
def test_raw_ws_data() -> None:
102+
"""Test websocket with data."""
103+
with mocker.patch(
104+
'homeassistant_cli.remote.wsapi', return_value={"result": "worked"}
105+
) as mockmethod:
106+
107+
runner = CliRunner()
108+
result = runner.invoke(
109+
cli.cli,
110+
[
111+
"--output=json",
112+
"raw",
113+
"ws",
114+
"config/wsmethod",
115+
"--json",
116+
'{ "id":"secret"}',
117+
],
118+
catch_exceptions=False,
119+
)
120+
assert result.exit_code == 0
121+
122+
mockmethod.assert_called_with(
123+
ANY, {"type": "config/wsmethod", "id": "secret"}
124+
)
125+
126+
data = json.loads(result.output)
127+
assert len(data) == 1

0 commit comments

Comments
 (0)