shvcli 0.2.1__tar.gz → 0.4.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 (35) hide show
  1. {shvcli-0.2.1/shvcli.egg-info → shvcli-0.4.0}/PKG-INFO +9 -12
  2. {shvcli-0.2.1 → shvcli-0.4.0}/README.rst +5 -3
  3. shvcli-0.4.0/pyproject.toml +101 -0
  4. shvcli-0.4.0/shvcli/__init__.py +5 -0
  5. {shvcli-0.2.1 → shvcli-0.4.0}/shvcli/__main__.py +18 -3
  6. shvcli-0.4.0/shvcli/__version__.py +25 -0
  7. {shvcli-0.2.1 → shvcli-0.4.0}/shvcli/builtin.py +1 -0
  8. {shvcli-0.2.1 → shvcli-0.4.0}/shvcli/builtin_impl.py +34 -29
  9. {shvcli-0.2.1 → shvcli-0.4.0}/shvcli/cli.py +7 -4
  10. {shvcli-0.2.1 → shvcli-0.4.0}/shvcli/client.py +30 -19
  11. {shvcli-0.2.1 → shvcli-0.4.0}/shvcli/complet.py +3 -2
  12. {shvcli-0.2.1 → shvcli-0.4.0}/shvcli/complet_tools.py +1 -0
  13. shvcli-0.4.0/shvcli/config.py +156 -0
  14. shvcli-0.4.0/shvcli/lsdir.py +119 -0
  15. {shvcli-0.2.1 → shvcli-0.4.0}/shvcli/parse.py +5 -5
  16. shvcli-0.4.0/shvcli/scan.py +30 -0
  17. shvcli-0.4.0/shvcli/tools.py +133 -0
  18. {shvcli-0.2.1 → shvcli-0.4.0}/shvcli/valid.py +3 -2
  19. shvcli-0.4.0/shvcli/version +1 -0
  20. {shvcli-0.2.1 → shvcli-0.4.0/shvcli.egg-info}/PKG-INFO +9 -12
  21. {shvcli-0.2.1 → shvcli-0.4.0}/shvcli.egg-info/SOURCES.txt +2 -0
  22. shvcli-0.4.0/shvcli.egg-info/requires.txt +4 -0
  23. {shvcli-0.2.1 → shvcli-0.4.0}/shvcli.egg-info/top_level.txt +1 -0
  24. shvcli-0.2.1/pyproject.toml +0 -89
  25. shvcli-0.2.1/shvcli/__init__.py +0 -5
  26. shvcli-0.2.1/shvcli/config.py +0 -109
  27. shvcli-0.2.1/shvcli/lsdir.py +0 -54
  28. shvcli-0.2.1/shvcli/tools.py +0 -83
  29. shvcli-0.2.1/shvcli/version +0 -1
  30. shvcli-0.2.1/shvcli.egg-info/requires.txt +0 -10
  31. {shvcli-0.2.1 → shvcli-0.4.0}/LICENSE +0 -0
  32. {shvcli-0.2.1 → shvcli-0.4.0}/setup.cfg +0 -0
  33. {shvcli-0.2.1 → shvcli-0.4.0}/shvcli/py.typed +0 -0
  34. {shvcli-0.2.1 → shvcli-0.4.0}/shvcli.egg-info/dependency_links.txt +0 -0
  35. {shvcli-0.2.1 → shvcli-0.4.0}/shvcli.egg-info/entry_points.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: shvcli
3
- Version: 0.2.1
3
+ Version: 0.4.0
4
4
  Summary: Silicon Heaven CLI access application
5
5
  Author: Elektroline a.s.
6
6
  License: MIT License
@@ -25,7 +25,7 @@ License: MIT License
25
25
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
26
  SOFTWARE.
27
27
 
28
- Project-URL: repository, https://gitlab.com/elektroline-predator/shvcli
28
+ Project-URL: repository, https://gitlab.com/silicon-heaven/shvcli
29
29
  Classifier: Development Status :: 4 - Beta
30
30
  Classifier: License :: OSI Approved :: MIT License
31
31
  Classifier: Programming Language :: Python :: 3
@@ -38,27 +38,22 @@ Classifier: Topic :: Terminals
38
38
  Requires-Python: >=3.11
39
39
  Description-Content-Type: text/x-rst
40
40
  License-File: LICENSE
41
- Requires-Dist: pyshv>=0.5.0
41
+ Requires-Dist: pyshv>=0.6.0
42
42
  Requires-Dist: prompt_toolkit
43
+ Requires-Dist: pygments
43
44
  Requires-Dist: pyxdg
44
- Provides-Extra: lint
45
- Requires-Dist: black; extra == "lint"
46
- Requires-Dist: isort; extra == "lint"
47
- Requires-Dist: mypy; extra == "lint"
48
- Requires-Dist: pydocstyle; extra == "lint"
49
- Requires-Dist: pylint; extra == "lint"
50
45
 
51
46
  =====================================
52
47
  Silicon Heaven CLI access application
53
48
  =====================================
54
- .. image:: https://gitlab.com/elektroline-predator/shvcli/-/raw/master/logo.svg
49
+ .. image:: https://gitlab.com/silicon-heaven/shvcli/-/raw/master/logo.svg
55
50
  :align: right
56
51
  :height: 128px
57
52
 
58
53
  This provides an easy to use CLI interfase to access the SHV network.
59
54
 
60
- * `📃 Sources <https://gitlab.com/elektroline-predator/shvcli>`__
61
- * `⁉️ Issue tracker <https://gitlab.com/elektroline-predator/shvcli/-/issues>`__
55
+ * `📃 Sources <https://gitlab.com/silicon-heaven/shvcli>`__
56
+ * `⁉️ Issue tracker <https://gitlab.com/silicon-heaven/shvcli/-/issues>`__
62
57
  * `📕 Silicon Heaven protocol documentation <https://silicon-heaven.github.io/shv-doc/>`__
63
58
 
64
59
 
@@ -123,6 +118,8 @@ switched in runtime. The following options are available:
123
118
 
124
119
  * **vimode**: If Vi input mode should be used for command line input. The
125
120
  default is ``false``.
121
+ * **autoget**: Automatically call getter methods and print received values when
122
+ listing nodes and methods (``ls`` and ``dir`` methods special handling).
126
123
  * **autoprobe**: Completion process benefits from probing of the SHV nodes with
127
124
  ``ls`` and ``dir``, and to provide easier usage this can happen automatically
128
125
  in background. This is what this option controls. It is ``true`` by default
@@ -1,14 +1,14 @@
1
1
  =====================================
2
2
  Silicon Heaven CLI access application
3
3
  =====================================
4
- .. image:: https://gitlab.com/elektroline-predator/shvcli/-/raw/master/logo.svg
4
+ .. image:: https://gitlab.com/silicon-heaven/shvcli/-/raw/master/logo.svg
5
5
  :align: right
6
6
  :height: 128px
7
7
 
8
8
  This provides an easy to use CLI interfase to access the SHV network.
9
9
 
10
- * `📃 Sources <https://gitlab.com/elektroline-predator/shvcli>`__
11
- * `⁉️ Issue tracker <https://gitlab.com/elektroline-predator/shvcli/-/issues>`__
10
+ * `📃 Sources <https://gitlab.com/silicon-heaven/shvcli>`__
11
+ * `⁉️ Issue tracker <https://gitlab.com/silicon-heaven/shvcli/-/issues>`__
12
12
  * `📕 Silicon Heaven protocol documentation <https://silicon-heaven.github.io/shv-doc/>`__
13
13
 
14
14
 
@@ -73,6 +73,8 @@ switched in runtime. The following options are available:
73
73
 
74
74
  * **vimode**: If Vi input mode should be used for command line input. The
75
75
  default is ``false``.
76
+ * **autoget**: Automatically call getter methods and print received values when
77
+ listing nodes and methods (``ls`` and ``dir`` methods special handling).
76
78
  * **autoprobe**: Completion process benefits from probing of the SHV nodes with
77
79
  ``ls`` and ``dir``, and to provide easier usage this can happen automatically
78
80
  in background. This is what this option controls. It is ``true`` by default
@@ -0,0 +1,101 @@
1
+ [project]
2
+ name = "shvcli"
3
+ version = "0.4.0"
4
+ description = "Silicon Heaven CLI access application"
5
+ readme = "README.rst"
6
+ license = {file = "LICENSE"}
7
+ authors = [
8
+ { name="Elektroline a.s." },
9
+ ]
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "License :: OSI Approved :: MIT License",
13
+ "Programming Language :: Python :: 3",
14
+ "Operating System :: OS Independent",
15
+ "Environment :: Console",
16
+ "Intended Audience :: End Users/Desktop",
17
+ "Topic :: Home Automation",
18
+ "Topic :: System :: Shells",
19
+ "Topic :: Terminals",
20
+ ]
21
+ requires-python = ">=3.11"
22
+ dependencies = [
23
+ "pyshv >= 0.6.0",
24
+ "prompt_toolkit",
25
+ "pygments",
26
+ "pyxdg",
27
+ ]
28
+
29
+ [project.urls]
30
+ repository = "https://gitlab.com/silicon-heaven/shvcli"
31
+
32
+ [project.scripts]
33
+ shvcli = "shvcli.__main__:main"
34
+
35
+ [build-system]
36
+ requires = ["setuptools"]
37
+ build-backend = "setuptools.build_meta"
38
+
39
+ [tool.setuptools.packages.find]
40
+ [tool.setuptools.package-data]
41
+ "*" = ["py.typed"]
42
+
43
+ [tool.coverage.run]
44
+ branch = true
45
+ [tool.coverage.report]
46
+ exclude_lines = [
47
+ "pragma: no cover",
48
+ "if typing.TYPE_CHECKING:"
49
+ ]
50
+
51
+ [tool.ruff]
52
+ target-version = "py311"
53
+ [tool.ruff.lint]
54
+ select = [
55
+ "F", "E", "W", "I", "N", "D", "UP", "YTT", "ANN", "ASYNC", "TRIO", "S", "BLE",
56
+ "B", "A", "COM", "PL", "RUF"
57
+ ]
58
+ ignore = [
59
+ # Magic methods have defined behavior so what is there to document. __init__
60
+ # should be documented in class docstring.
61
+ "D105", "D107",
62
+ # These have conflicting rules.
63
+ "D203", "D213",
64
+ # Ignore too long lines (format tackles that)
65
+ "E501",
66
+ # It is nice that assert should not be used for input checking but it can be
67
+ # also used for internal consistency checking.
68
+ "S101", "B011",
69
+ # subprocess can be insecurely used but not is insecure by design
70
+ "S404", "S603", "S607",
71
+ # Catching Exception is valid and safe in some cases and thus should be allowed
72
+ "BLE001",
73
+ # Conflict with formatter
74
+ "COM812",
75
+ # Remove too-many limit for more complex code.
76
+ "PLR0904", "PLR0911", "PLR0912", "PLR0913", "PLR0917", "PLR2004", "PLR1702",
77
+ "PLR0916",
78
+ # This seems like a wrong rule
79
+ "PLW1641",
80
+ # Deprecated
81
+ "ANN101", "ANN102",
82
+ ]
83
+ preview = true
84
+ [tool.ruff.lint.per-file-ignores]
85
+ "tests/**" = [
86
+ "D100", "D101", "D102", "D103", "D104", "S101", "S106", "ANN001", "ANN002",
87
+ "ANN003", "ANN201", "ANN202", "ANN204", "ANN205", "PLR6301",
88
+ ]
89
+ "docs/conf.py" = ["D100", "D103", "A001", "ANN201", "ANN001"]
90
+ [tool.ruff.format]
91
+ docstring-code-format = true
92
+ preview = true
93
+
94
+ [tool.mypy]
95
+ python_version = "3.11"
96
+ ignore_missing_imports = true
97
+ disallow_untyped_defs = true
98
+ warn_redundant_casts = true
99
+ warn_unused_ignores = true
100
+ warn_return_any = true
101
+ warn_unreachable = true
@@ -0,0 +1,5 @@
1
+ """Silicon Heaven Command Line Interface."""
2
+
3
+ from .__version__ import VERSION
4
+
5
+ __all__ = ["VERSION"]
@@ -1,4 +1,5 @@
1
1
  """Implementation of command line application."""
2
+
2
3
  import argparse
3
4
  import asyncio
4
5
  import logging
@@ -18,11 +19,22 @@ def parse_args() -> argparse.Namespace:
18
19
  action="store_true",
19
20
  help="Enable logging that provides communication debug info.",
20
21
  )
22
+ parser.add_argument(
23
+ "-s",
24
+ "--scan",
25
+ action="store_true",
26
+ help="Perform scan as a first action right after connect.",
27
+ )
28
+ parser.add_argument(
29
+ "--scan-depth",
30
+ type=int,
31
+ default=3,
32
+ help="Depth of the scan performed if --scan is used.",
33
+ )
21
34
  parser.add_argument(
22
35
  "URL",
23
36
  nargs="?",
24
- default="tcp://test@localhost?password=test",
25
- help="SHV RPC URL specifying connection to the broker.",
37
+ help="SHV RPC URL specifying connection to the broker or host from configuration.",
26
38
  )
27
39
  return parser.parse_args()
28
40
 
@@ -32,8 +44,11 @@ def main() -> None:
32
44
  logging.basicConfig(format="[%(asctime)s] [%(levelname)s] - %(message)s")
33
45
  args = parse_args()
34
46
  config = CliConfig()
35
- config.url = args.URL
47
+ if args.URL is not None:
48
+ config.url = args.URL
36
49
  config.debug = config.debug or args.debug
50
+ config.initial_scan = args.scan
51
+ config.initial_scan_depth = args.scan_depth
37
52
  asyncio.run(run(config))
38
53
 
39
54
 
@@ -0,0 +1,25 @@
1
+ """The smarter loader of the version.
2
+
3
+ It identifies if this package is installed and returns version from egg info or
4
+ it looks for pyproject.toml file in upper directory.
5
+ """
6
+
7
+ import importlib.metadata
8
+ import pathlib
9
+ import tomllib
10
+
11
+
12
+ def _get_version() -> str:
13
+ for dist_name in importlib.metadata.packages_distributions().get(__package__, []):
14
+ dist = importlib.metadata.distribution(dist_name)
15
+ if str(dist.locate_file(f"{__package__}/__version__.py")) == __file__:
16
+ return dist.version
17
+ pyproject = pathlib.Path(__file__).parent.parent / "pyproject.toml"
18
+ with pyproject.open("rb") as f:
19
+ res = tomllib.load(f)["project"]["version"]
20
+ if isinstance(res, str):
21
+ return res
22
+ return "unknown"
23
+
24
+
25
+ VERSION = _get_version()
@@ -1,4 +1,5 @@
1
1
  """Builtin methods glue code so they can be defined separatelly."""
2
+
2
3
  import collections.abc
3
4
  import dataclasses
4
5
  import typing
@@ -1,9 +1,10 @@
1
1
  """Implementations of our builtin methods."""
2
+
2
3
  import collections.abc
4
+ import inspect
3
5
  import itertools
4
6
 
5
7
  from prompt_toolkit.completion import Completion
6
- from prompt_toolkit.shortcuts import ProgressBar, ProgressBarCounter
7
8
  from shv import RpcSubscription
8
9
 
9
10
  from .builtin import METHODS, Argument, XMethod, builtin, xbuiltin
@@ -12,6 +13,7 @@ from .complet_tools import comp_from, comp_path
12
13
  from .config import CliConfig
13
14
  from .lsdir import ls_node_format
14
15
  from .parse import CliItems
16
+ from .scan import scan_nodes
15
17
  from .tools import lookahead, print_block, print_ftext, print_row
16
18
 
17
19
 
@@ -35,11 +37,14 @@ def argument_set_comp(
35
37
  """Completion for !set method."""
36
38
  opt, val = items.interpret_param_set()
37
39
  if val is not None:
38
- if opt in config.opts_bool:
40
+ if config.OPTS.get(opt or "") is config.Type.BOOL:
39
41
  yield from comp_from(val, {"true", "false"})
40
42
  else:
41
- yield from comp_from(items.param_raw, config.opts_bool)
42
- yield from comp_from(items.param_raw, (f"no{v}" for v in config.opts_bool))
43
+ yield from comp_from(items.param_raw, config.OPTS.keys())
44
+ yield from comp_from(
45
+ items.param_raw,
46
+ (f"no{v}" for v, t in config.OPTS.items() if t is config.Type.BOOL),
47
+ )
43
48
 
44
49
 
45
50
  argument_signal = Argument("[PATH:SIGNAL]", argument_signal_comp, autoprobe=True)
@@ -58,8 +63,8 @@ async def _help(_: SHVClient, __: CliConfig, ___: CliItems) -> None:
58
63
  call = f"{name}[X]"
59
64
  else:
60
65
  call = name + "".join(f"|{n}" for n in m.aliases)
61
- print_row(call + (f" {m.argument.description}" if m.argument else ""), 2)
62
- print_block(m.description, 4)
66
+ print_row(f"{call} {m.argument.description if m.argument else ''}")
67
+ print_block(inspect.cleandoc(m.description), 4)
63
68
 
64
69
 
65
70
  @builtin(aliases={"sub"}, argument=argument_signal)
@@ -91,7 +96,10 @@ async def tree(shvclient: SHVClient, config: CliConfig, items: CliItems) -> None
91
96
  print_ftext(
92
97
  itertools.chain(
93
98
  (("", "│ " if c else " ") for c in cols),
94
- iter([("", "├─" if hasnext else "└─"), ls_node_format(node, name)]),
99
+ iter([
100
+ ("", "├─" if hasnext else "└─"),
101
+ ls_node_format(name, parent_node=node),
102
+ ]),
95
103
  )
96
104
  )
97
105
  print_node(node[name], [*cols, hasnext])
@@ -114,22 +122,7 @@ async def scan(
114
122
 
115
123
  Scan uses 'ls' and 'dir' to fetch info about all nodes.
116
124
  """
117
- depth = depth or 3
118
- path = items.interpret_param_path(config)
119
- depth += path.count("/") # Extend depth to the depth in path
120
- pths = [path]
121
- with ProgressBar() as pb:
122
- pbcnt: ProgressBarCounter = pb()
123
- pbcnt.total = 1
124
- while pths:
125
- pth = pths.pop()
126
- pbcnt.label = pth
127
- node = await shvclient.probe(pth)
128
- if node is not None and (pth.count("/") + 1 if pth else 0) < depth:
129
- assert node.nodes is not None
130
- pths.extend(f"{pth}{'/' if pth else ''}{name}" for name in node.nodes)
131
- pbcnt.total += len(node.nodes)
132
- pbcnt.item_completed()
125
+ await scan_nodes(shvclient, items.interpret_param_path(config), depth or 3)
133
126
 
134
127
 
135
128
  @builtin("set", aliases={"s"}, argument=argument_set)
@@ -137,17 +130,29 @@ async def _set(_: SHVClient, config: CliConfig, items: CliItems) -> None:
137
130
  """Set configuration options in runtime."""
138
131
  opt, val = items.interpret_param_set()
139
132
  if not opt:
140
- for n in config.opts_bool:
141
- print(f"{n}: {str(getattr(config, n)).lower()}")
133
+ w = max(len(n) for n in config.OPTS.keys())
134
+ for n, t in config.OPTS.items():
135
+ row = [("", (" " * (w - len(n))) + n + " ")]
136
+ match t:
137
+ case config.Type.BOOL:
138
+ v = getattr(config, n)
139
+ assert isinstance(v, bool)
140
+ row.append(("ansigreen" if v else "ansired", str(v).lower()))
141
+ case config.Type.INT:
142
+ row.append(("", str(getattr(config, n))))
143
+ case _:
144
+ raise NotImplementedError(f"Unimplemented {t!r}")
145
+ print_row(row)
142
146
  return
143
- no = opt.startswith("no")
144
- if no:
147
+
148
+ # TODO from here we need add support for integers when we need it.
149
+ if no := opt.startswith("no"):
145
150
  opt = opt[2:]
146
- if opt not in config.opts_bool:
151
+ if opt not in config.OPTS.keys():
147
152
  print(f"Invalid option: {opt}")
148
153
  return
149
154
  if val is None:
150
- value = not getattr(config, opt)
155
+ value = not no
151
156
  else:
152
157
  m = {"true": True, "t": True, "false": False, "f": False}
153
158
  if val not in m:
@@ -1,4 +1,5 @@
1
1
  """Command line interface."""
2
+
2
3
  import asyncio
3
4
  import json
4
5
  import pathlib
@@ -8,16 +9,16 @@ import xdg.BaseDirectory
8
9
  from prompt_toolkit import PromptSession
9
10
  from prompt_toolkit.history import FileHistory
10
11
  from prompt_toolkit.patch_stdout import patch_stdout
11
- from shv import RpcError, RpcUrl
12
- from shv.cpon import Cpon
12
+ from shv import RpcError, RpcLogin, RpcUrl
13
13
 
14
- from . import builtin_impl as _
14
+ from . import builtin_impl as _ # noqa F401
15
15
  from .builtin import call_builtin
16
16
  from .client import Node, SHVClient
17
17
  from .complet import CliCompleter
18
18
  from .config import CliConfig
19
19
  from .lsdir import dir_method, ls_method
20
20
  from .parse import parse_line
21
+ from .scan import scan_nodes
21
22
  from .tools import print_cpon
22
23
  from .valid import CliValidator
23
24
 
@@ -68,7 +69,7 @@ async def run(config: CliConfig) -> None:
68
69
  location=config.url.location,
69
70
  port=config.url.port,
70
71
  protocol=config.url.protocol,
71
- username=config.url.username,
72
+ login=RpcLogin(username=config.url.login.username),
72
73
  )
73
74
  cpath = xdg.BaseDirectory.save_cache_path("shvcli")
74
75
  fname = re.sub(r"[^\w_. -]", "_", cacheurl.to_url())
@@ -76,6 +77,8 @@ async def run(config: CliConfig) -> None:
76
77
  if cachepath.exists():
77
78
  with cachepath.open("r") as f:
78
79
  shvclient.tree = Node.load(json.load(f))
80
+ if config.initial_scan:
81
+ await scan_nodes(shvclient, "", config.initial_scan_depth)
79
82
 
80
83
  clitask = asyncio.create_task(_app(config, shvclient))
81
84
  await shvclient.client.wait_disconnect()
@@ -1,4 +1,5 @@
1
1
  """SHV client for shvcli."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import collections.abc
@@ -6,7 +7,6 @@ import typing
6
7
  from pathlib import PurePosixPath
7
8
 
8
9
  from shv import (
9
- RpcClient,
10
10
  RpcError,
11
11
  RpcMessage,
12
12
  RpcMethodDesc,
@@ -20,7 +20,7 @@ from shv.cpon import Cpon
20
20
  from . import VERSION
21
21
 
22
22
 
23
- class Node(collections.abc.Mapping):
23
+ class Node(collections.abc.Mapping[str, "Node"]):
24
24
  """Abstraction on the tree node."""
25
25
 
26
26
  def __init__(self) -> None:
@@ -31,7 +31,7 @@ class Node(collections.abc.Mapping):
31
31
  self.nodes_probed = False
32
32
  self.methods_probed = False
33
33
 
34
- def __getitem__(self, key: str) -> "Node":
34
+ def __getitem__(self, key: str) -> Node:
35
35
  """Get node from nodes."""
36
36
  return self.nodes[key]
37
37
 
@@ -43,7 +43,7 @@ class Node(collections.abc.Mapping):
43
43
  """Get number of child nodes."""
44
44
  return len(self.nodes)
45
45
 
46
- def valid_path(self, path: str | PurePosixPath) -> "Node":
46
+ def valid_path(self, path: str | PurePosixPath) -> Node:
47
47
  """Add valid path relative to this node."""
48
48
  if isinstance(path, str):
49
49
  path = PurePosixPath(path)
@@ -68,7 +68,7 @@ class Node(collections.abc.Mapping):
68
68
  if pnode is not None:
69
69
  pnode.nodes.pop(path.name)
70
70
 
71
- def get_path(self, path: str | PurePosixPath) -> typing.Union[None, "Node"]:
71
+ def get_path(self, path: str | PurePosixPath) -> None | Node:
72
72
  """Get node on given path."""
73
73
  if isinstance(path, str):
74
74
  path = PurePosixPath(path)
@@ -79,7 +79,7 @@ class Node(collections.abc.Mapping):
79
79
  node = node[n]
80
80
  return node
81
81
 
82
- def dump(self) -> typing.Any:
82
+ def dump(self) -> dict[str, object]:
83
83
  """Dump the data to basic types."""
84
84
  return {
85
85
  "nodes": {n: v.dump() for n, v in self.nodes.items()},
@@ -90,15 +90,18 @@ class Node(collections.abc.Mapping):
90
90
  }
91
91
 
92
92
  @classmethod
93
- def load(cls, data: typing.Any) -> Node:
93
+ def load(cls, data: collections.abc.Mapping[str, object]) -> Node:
94
94
  """Load node tree from dump."""
95
95
  assert isinstance(data, collections.abc.Mapping)
96
96
  res = cls()
97
- res.nodes = {n: cls.load(v) for n, v in data["nodes"].items()}
98
- res.methods = set(data["methods"])
99
- res.signals = set(data["signals"])
100
- res.nodes_probed = data["nodes_probed"]
101
- res.methods_probed = data["methods_probed"]
97
+ if isinstance(data["nodes"], collections.abc.Mapping):
98
+ res.nodes = {n: cls.load(v) for n, v in data["nodes"].items()}
99
+ if isinstance(data["methods"], collections.abc.Sequence):
100
+ res.methods = set(data["methods"])
101
+ if isinstance(data["signals"], collections.abc.Sequence):
102
+ res.signals = set(data["signals"])
103
+ res.nodes_probed = bool(data["nodes_probed"])
104
+ res.methods_probed = bool(data["methods_probed"])
102
105
  return res
103
106
 
104
107
 
@@ -108,11 +111,11 @@ class SHVClient(SimpleClient):
108
111
  APP_NAME = "shvcli"
109
112
  APP_VERSION = VERSION
110
113
 
111
- def __init__(self, client: RpcClient) -> None:
114
+ def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: # noqa ANN401
112
115
  """Initialize client and set create reference to tree."""
113
116
  self.tree = Node()
114
117
  self.tree.valid_path(".app")
115
- super().__init__(client)
118
+ super().__init__(*args, **kwargs)
116
119
 
117
120
  async def _loop(self) -> None:
118
121
  await super()._loop()
@@ -150,19 +153,27 @@ class SHVClient(SimpleClient):
150
153
  self.tree.invalid_path(path)
151
154
  raise exc
152
155
  node = self.tree.valid_path(path)
153
- node.methods = {d.name for d in res if RpcMethodFlags.SIGNAL not in d.flags}
154
- node.signals = {d.name for d in res if RpcMethodFlags.SIGNAL in d.flags}
156
+ node.methods = {
157
+ d.name for d in res if RpcMethodFlags.NOT_CALLABLE not in d.flags
158
+ }
159
+ node.signals = {d.name for d in res if RpcMethodFlags.NOT_CALLABLE in d.flags}
155
160
  node.methods_probed = True
156
161
  return res
157
162
 
158
- async def call(self, path: str, method: str, param: SHVType = None) -> SHVType:
163
+ async def call(
164
+ self,
165
+ path: str,
166
+ method: str,
167
+ *args: typing.Any, # noqa ANN401
168
+ **kwargs: typing.Any, # noqa ANN401
169
+ ) -> SHVType:
159
170
  """Perform call same as ValueClient.
160
171
 
161
172
  This uses every call to get insite into existence of method and node and records
162
173
  that in the tree.
163
174
  """
164
175
  try:
165
- res = await super().call(path, method, param)
176
+ res = await super().call(path, method, *args, **kwargs)
166
177
  except RpcMethodNotFoundError as exc:
167
178
  raise exc
168
179
  except RpcError as exc:
@@ -190,7 +201,7 @@ class SHVClient(SimpleClient):
190
201
  async def path_is_valid(self, path: str) -> bool:
191
202
  """Check if given path is valid by using ls command."""
192
203
  path = path.strip("/")
193
- if path == "":
204
+ if not path:
194
205
  return True # top level is always valid
195
206
  if "/" in path:
196
207
  pth, name = path.rsplit("/", maxsplit=1)
@@ -1,4 +1,5 @@
1
1
  """Completion for CLI."""
2
+
2
3
  import collections.abc
3
4
  import typing
4
5
 
@@ -28,7 +29,7 @@ class CliCompleter(Completer):
28
29
 
29
30
  # Parameters
30
31
  if CliFlags.COMPLETE_CALL in items.flags:
31
- if items.method in ("ls", "dir") and not self.config.raw:
32
+ if items.method in {"ls", "dir"} and not self.config.raw:
32
33
  yield from comp_path(self.shvclient, self.config, items)
33
34
  elif bmethod := builtin.get_builtin(items.method[1:]):
34
35
  if bmethod.argument:
@@ -58,7 +59,7 @@ class CliCompleter(Completer):
58
59
  items = parse_line(document.text)
59
60
  if self.config.autoprobe and (
60
61
  CliFlags.COMPLETE_CALL not in items.flags
61
- or items.method in ("ls", "dir")
62
+ or items.method in {"ls", "dir"}
62
63
  or (
63
64
  items.method[0] == "!"
64
65
  and (bmethod := builtin.get_builtin(items.method[1:])) is not None
@@ -1,4 +1,5 @@
1
1
  """Tool for completion algorithms."""
2
+
2
3
  import collections.abc
3
4
  import pathlib
4
5