motorcortex-python 1.0.0rc1__py3-none-any.whl

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.
motorcortex/nng_url.py ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/python3
2
+
3
+ #
4
+ # Developer: Alexey Zakharov (alexey.zakharov@vectioneer.com)
5
+ # All rights reserved. Copyright (c) 2016-2026 VECTIONEER.
6
+ #
7
+
8
+ from pynng import ffi, lib # type: ignore[import-untyped]
9
+ from pynng.exceptions import check_err # type: ignore[import-untyped]
10
+
11
+
12
+ class NngUrl:
13
+ """Python wrapper for nng_url."""
14
+
15
+ def __init__(self, url: str) -> None:
16
+ self._url_p = ffi.new("nng_url **")
17
+ check_err(lib.nng_url_parse(self._url_p, url.encode()))
18
+
19
+ def __del__(self) -> None:
20
+ if self._url_p[0] != ffi.NULL:
21
+ lib.nng_url_free(self._url_p[0])
22
+
23
+ @property
24
+ def _url(self): # type: ignore[no-untyped-def]
25
+ return self._url_p[0]
26
+
27
+ @property
28
+ def hostname(self) -> str | None:
29
+ if self._url.u_hostname == ffi.NULL:
30
+ return None
31
+ return ffi.string(self._url.u_hostname).decode()
32
+
33
+ @property
34
+ def port(self) -> str | None:
35
+ if self._url.u_port == ffi.NULL:
36
+ return None
37
+ return ffi.string(self._url.u_port).decode()
38
+
39
+ def __repr__(self) -> str:
40
+ return (
41
+ f"NngUrl(scheme={self.scheme!r}, "
42
+ f"hostname={self.hostname!r}, port={self.port!r})"
43
+ )
44
+
45
+ @property
46
+ def scheme(self) -> str | None:
47
+ if self._url.u_scheme == ffi.NULL:
48
+ return None
49
+ return ffi.string(self._url.u_scheme).decode()
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/python3
2
+
3
+ #
4
+ # Developer: Alexey Zakharov (alexey.zakharov@vectioneer.com)
5
+ # All rights reserved. Copyright (c) 2016-2026 VECTIONEER.
6
+ #
7
+
8
+
9
+ from typing import Any, Dict, List, Optional
10
+
11
+
12
+ class ParameterTree(object):
13
+ """
14
+ Represents a parameter tree, obtained from the server.
15
+
16
+ Reference to a parameter tree instance is needed for resolving parameters,
17
+ data types and other information to build a correct request message.
18
+ """
19
+
20
+ def __init__(self) -> None:
21
+ """
22
+ Initializes an empty ParameterTree.
23
+ """
24
+ self._parameter_tree: List[Any] = []
25
+ self._parameter_map: Dict[str, Any] = dict()
26
+
27
+ def __repr__(self) -> str:
28
+ return f"ParameterTree(params={len(self._parameter_tree)})"
29
+
30
+ def load(self, parameter_tree_msg: Any) -> None:
31
+ """
32
+ Loads a parameter tree from ParameterTreeMsg received from the server.
33
+
34
+ Args:
35
+ parameter_tree_msg (Any): Parameter tree message from the server (should have a .params attribute).
36
+
37
+ Examples:
38
+ >>> parameter_tree = motorcortex.ParameterTree()
39
+ >>> parameter_tree_msg = param_tree_reply.get()
40
+ >>> parameter_tree.load(parameter_tree_msg)
41
+ """
42
+ self._parameter_tree = parameter_tree_msg.params
43
+ for param in self._parameter_tree:
44
+ self._parameter_map[param.path] = param
45
+
46
+ def getParameterTree(self) -> List[Any]:
47
+ """
48
+ Returns the list of parameter descriptions in the tree.
49
+
50
+ Returns:
51
+ List[Any]: A list of parameter descriptions (ParameterInfo objects).
52
+ """
53
+ return self._parameter_tree
54
+
55
+ def getInfo(self, parameter_path: str) -> Optional[Any]:
56
+ """
57
+ Get the parameter description for a given path.
58
+
59
+ Args:
60
+ parameter_path (str): Path of the parameter.
61
+
62
+ Returns:
63
+ Optional[Any]: Parameter description (ParameterInfo) if found, else None.
64
+ """
65
+ return self._parameter_map.get(parameter_path)
66
+
67
+ def getDataType(self, parameter_path: str) -> Any:
68
+ """
69
+ Get the data type for a given parameter path.
70
+
71
+ Args:
72
+ parameter_path (str): Path of the parameter.
73
+
74
+ Returns:
75
+ The parameter's data type.
76
+
77
+ Raises:
78
+ KeyError: if ``parameter_path`` is not in the tree. Callers
79
+ that want lenient "maybe" semantics should use
80
+ :meth:`getInfo` (returns ``Optional[Any]``) and branch
81
+ on ``None`` themselves.
82
+ """
83
+ info = self.getInfo(parameter_path)
84
+ if info is None:
85
+ raise KeyError(parameter_path)
86
+ return info.data_type
motorcortex/py.typed ADDED
File without changes
motorcortex/reply.py ADDED
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/python3
2
+
3
+ #
4
+ # Developer: Alexey Zakharov (alexey.zakharov@vectioneer.com)
5
+ # All rights reserved. Copyright (c) 2016-2026 VECTIONEER.
6
+ #
7
+
8
+
9
+ from concurrent.futures import Future, TimeoutError as _FutureTimeout
10
+ from typing import Any, Callable, Generic, Optional, TypeVar
11
+
12
+ from motorcortex.exceptions import McxTimeout
13
+
14
+
15
+ T = TypeVar("T")
16
+
17
+
18
+ class Reply(Generic[T]):
19
+ """Reply handle is a JavaScript-like Promise, generic over the
20
+ message type returned by the underlying RPC.
21
+
22
+ ``Reply[T].get()`` returns ``T`` — the decoded protobuf message
23
+ class for the specific RPC. For example,
24
+ ``Request.getParameter`` returns ``Reply[ParameterMsg]``, so
25
+ ``req.getParameter(path).get()`` is a ``ParameterMsg`` at the
26
+ type-check level, not ``Any``.
27
+
28
+ Runtime behavior is unchanged — ``Reply[X]`` is a typing-time
29
+ construct; at runtime it is just ``Reply``. Pre-generic annotations
30
+ like ``reply: Reply`` still work (equivalent to ``Reply[Any]``).
31
+ """
32
+
33
+ def __init__(self, future: "Future[Any]") -> None:
34
+ self._future = future
35
+
36
+ def __repr__(self) -> str:
37
+ return f"Reply(done={self._future.done()})"
38
+
39
+ def get(self, timeout_ms: Optional[int] = None) -> T:
40
+ """A blocking call to wait for the reply and returns a value.
41
+
42
+ Args:
43
+ timeout_ms(integer): timeout for reply in milliseconds
44
+
45
+ Returns:
46
+ A protobuf message with a parameter description and value.
47
+
48
+ Raises:
49
+ McxTimeout: ``timeout_ms`` elapsed before the reply
50
+ arrived. ``McxTimeout`` inherits ``TimeoutError``
51
+ so ``except TimeoutError`` still catches it.
52
+
53
+ Examples:
54
+ >>> param_tree_reply = req.getParameterTree()
55
+ >>> value = param_tree_reply.get()
56
+
57
+ """
58
+ try:
59
+ return self._future.result(timeout_ms / 1000.0 if timeout_ms else None)
60
+ except _FutureTimeout as e:
61
+ # Keep the typed-exception contract from ARCHITECTURE.md §2b:
62
+ # reply-level timeouts raise McxTimeout, not the stdlib
63
+ # TimeoutError that concurrent.futures would leak.
64
+ raise McxTimeout(
65
+ f"Reply.get timed out after {timeout_ms}ms"
66
+ ) from e
67
+
68
+ def done(self) -> bool:
69
+ """
70
+ Returns:
71
+ bool: True if the call was successfully canceled or finished running.
72
+ """
73
+ return self._future.done()
74
+
75
+ def then(self, received_clb: Callable[..., Any], *args: Any, **kwargs: Any) -> "Reply[T]":
76
+ """JavaScript-like promise, which is resolved when a reply is received.
77
+
78
+ Args:
79
+ received_clb: callback which is resolved when the reply is received.
80
+
81
+ Returns:
82
+ self pointer to add 'catch' callback
83
+
84
+ Examples:
85
+ >>> param_tree_reply.then(lambda reply: print("got reply: %s"%reply))
86
+ >>> .catch(lambda g: print("failed"))
87
+ """
88
+ self._future.add_done_callback(
89
+ lambda msg: received_clb(msg.result(), *args, **kwargs) if msg.result() else None
90
+ )
91
+ return self
92
+
93
+ def catch(self, failed_clb: Callable[..., Any], *args: Any, **kwargs: Any) -> "Reply[T]":
94
+ """JavaScript-like promise, which is resolved when receive has failed.
95
+
96
+ Args:
97
+ failed_clb: callback which is resolved when receive has failed
98
+
99
+ Returns:
100
+ self pointer to add 'then' callback
101
+
102
+ Examples:
103
+ >>> param_tree_reply.catch(lambda g: print("failed")).then(lambda reply: print("got reply: %s"%reply))
104
+ """
105
+ self._future.add_done_callback(
106
+ lambda msg: failed_clb(*args, **kwargs) if not msg.result() else None
107
+ )
108
+ return self