motorcortex-python 1.0.0__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/__init__.py +362 -0
- motorcortex/_connection_state.py +58 -0
- motorcortex/_request_builders.py +157 -0
- motorcortex/_request_utils.py +314 -0
- motorcortex/_subscribe_dispatch.py +90 -0
- motorcortex/exceptions.py +65 -0
- motorcortex/init_threads.py +103 -0
- motorcortex/message_types.py +387 -0
- motorcortex/motorcortex_hash.json +166 -0
- motorcortex/motorcortex_pb2.py +105 -0
- motorcortex/motorcortex_pb2.pyi +1961 -0
- motorcortex/nng_url.py +49 -0
- motorcortex/parameter_tree.py +86 -0
- motorcortex/py.typed +0 -0
- motorcortex/reply.py +108 -0
- motorcortex/request.py +668 -0
- motorcortex/session.py +194 -0
- motorcortex/setup_logger.py +10 -0
- motorcortex/state_callback_handler.py +92 -0
- motorcortex/subscribe.py +400 -0
- motorcortex/subscription.py +414 -0
- motorcortex/timespec.py +173 -0
- motorcortex/version.py +1 -0
- motorcortex_python-1.0.0.dist-info/LICENSE +22 -0
- motorcortex_python-1.0.0.dist-info/METADATA +171 -0
- motorcortex_python-1.0.0.dist-info/RECORD +28 -0
- motorcortex_python-1.0.0.dist-info/WHEEL +5 -0
- motorcortex_python-1.0.0.dist-info/top_level.txt +1 -0
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
|