motorcortex-python 1.0.0rc1__tar.gz → 1.0.1__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.
- {motorcortex_python-1.0.0rc1/motorcortex_python.egg-info → motorcortex_python-1.0.1}/PKG-INFO +1 -1
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/__init__.py +106 -20
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/_request_builders.py +1 -1
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/_request_utils.py +2 -2
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/message_types.py +9 -24
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/request.py +1 -1
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/session.py +1 -1
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/subscribe.py +2 -2
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/subscription.py +2 -2
- motorcortex_python-1.0.1/motorcortex/version.py +1 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1/motorcortex_python.egg-info}/PKG-INFO +1 -1
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/pyproject.toml +1 -1
- motorcortex_python-1.0.0rc1/motorcortex/version.py +0 -1
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/LICENSE +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/MANIFEST.in +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/README.md +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/_connection_state.py +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/_subscribe_dispatch.py +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/exceptions.py +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/init_threads.py +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/motorcortex_hash.json +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/motorcortex_pb2.py +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/motorcortex_pb2.pyi +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/nng_url.py +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/parameter_tree.py +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/py.typed +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/reply.py +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/setup_logger.py +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/state_callback_handler.py +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/timespec.py +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/SOURCES.txt +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/dependency_links.txt +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/requires.txt +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/top_level.txt +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/setup.cfg +0 -0
- {motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/setup.py +0 -0
|
@@ -14,6 +14,7 @@ Provides high-level APIs for communication, login, and data exchange using proto
|
|
|
14
14
|
See documentation for usage examples.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
+
from enum import IntEnum as _IntEnum
|
|
17
18
|
from typing import Any
|
|
18
19
|
|
|
19
20
|
from motorcortex.version import __version__
|
|
@@ -39,6 +40,39 @@ from motorcortex.exceptions import (
|
|
|
39
40
|
McxLoginError,
|
|
40
41
|
McxTimeout,
|
|
41
42
|
)
|
|
43
|
+
from motorcortex.motorcortex_pb2 import StatusCode as _PbStatusCode
|
|
44
|
+
from motorcortex.setup_logger import logger # not re-exported — ``__all__`` gates package surface
|
|
45
|
+
|
|
46
|
+
# Build an ``IntEnum`` facade over the protobuf ``StatusCode`` enum so
|
|
47
|
+
# callers can write ``motorcortex.StatusCode.OK`` without reaching into
|
|
48
|
+
# the generated protobuf module. Subclassing ``int`` means comparisons
|
|
49
|
+
# like ``reply.status == StatusCode.OK`` work against the raw ints that
|
|
50
|
+
# come back on the wire.
|
|
51
|
+
StatusCode = _IntEnum(
|
|
52
|
+
"StatusCode",
|
|
53
|
+
{v.name: v.number for v in _PbStatusCode.DESCRIPTOR.values},
|
|
54
|
+
)
|
|
55
|
+
StatusCode.__doc__ = (
|
|
56
|
+
"Motorcortex response status codes. Members mirror the ``StatusCode`` "
|
|
57
|
+
"enum in the wire protocol. Use ``motorcortex.StatusCode.OK`` (or the "
|
|
58
|
+
"flat re-exports such as ``motorcortex.OK``) to compare against "
|
|
59
|
+
"``reply.status``."
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Flat re-exports for the conventional ``motorcortex.OK`` idiom. Values
|
|
63
|
+
# are members of :class:`StatusCode` and therefore also ``int``.
|
|
64
|
+
OK = StatusCode.OK
|
|
65
|
+
READ_ONLY_MODE = StatusCode.READ_ONLY_MODE
|
|
66
|
+
FAILED = StatusCode.FAILED
|
|
67
|
+
FAILED_TO_DECODE = StatusCode.FAILED_TO_DECODE
|
|
68
|
+
SUB_LIST_IS_FULL = StatusCode.SUB_LIST_IS_FULL
|
|
69
|
+
WRONG_PARAMETER_PATH = StatusCode.WRONG_PARAMETER_PATH
|
|
70
|
+
FAILED_TO_SET_REQUESTED_FRQ = StatusCode.FAILED_TO_SET_REQUESTED_FRQ
|
|
71
|
+
FAILED_TO_OPEN_FILE = StatusCode.FAILED_TO_OPEN_FILE
|
|
72
|
+
GROUP_LIST_IS_FULL = StatusCode.GROUP_LIST_IS_FULL
|
|
73
|
+
WRONG_PASSWORD = StatusCode.WRONG_PASSWORD
|
|
74
|
+
USER_NOT_LOGGED_IN = StatusCode.USER_NOT_LOGGED_IN
|
|
75
|
+
PERMISSION_DENIED = StatusCode.PERMISSION_DENIED
|
|
42
76
|
|
|
43
77
|
# ``__all__`` is the 1.0 public API surface. Anything not listed here —
|
|
44
78
|
# including module-level helpers defined below (``parseUrl``,
|
|
@@ -72,6 +106,20 @@ __all__ = [
|
|
|
72
106
|
"McxConnectionError",
|
|
73
107
|
"McxLoginError",
|
|
74
108
|
"McxTimeout",
|
|
109
|
+
# Status codes
|
|
110
|
+
"StatusCode",
|
|
111
|
+
"OK",
|
|
112
|
+
"READ_ONLY_MODE",
|
|
113
|
+
"FAILED",
|
|
114
|
+
"FAILED_TO_DECODE",
|
|
115
|
+
"SUB_LIST_IS_FULL",
|
|
116
|
+
"WRONG_PARAMETER_PATH",
|
|
117
|
+
"FAILED_TO_SET_REQUESTED_FRQ",
|
|
118
|
+
"FAILED_TO_OPEN_FILE",
|
|
119
|
+
"GROUP_LIST_IS_FULL",
|
|
120
|
+
"WRONG_PASSWORD",
|
|
121
|
+
"USER_NOT_LOGGED_IN",
|
|
122
|
+
"PERMISSION_DENIED",
|
|
75
123
|
# Tuning
|
|
76
124
|
"init_nng_threads",
|
|
77
125
|
]
|
|
@@ -133,6 +181,56 @@ def makeUrl(address: str, port: int | None) -> str:
|
|
|
133
181
|
return address
|
|
134
182
|
|
|
135
183
|
|
|
184
|
+
def _reconnect_state_update(
|
|
185
|
+
req: Any, sub: Any, state: "ConnectionState", *,
|
|
186
|
+
motorcortex_types: "MessageTypes",
|
|
187
|
+
login: Any,
|
|
188
|
+
password: Any,
|
|
189
|
+
token_interval_sec: float,
|
|
190
|
+
initial_connect_done: list,
|
|
191
|
+
) -> None:
|
|
192
|
+
"""Default ``state_update`` body for :func:`connect` in reconnect mode.
|
|
193
|
+
|
|
194
|
+
Fires on every state transition from the ``StateCallbackHandler``
|
|
195
|
+
worker thread. Only acts on the ``CONNECTION_OK`` edge *after* the
|
|
196
|
+
initial connect has completed — i.e. on a successful **reconnect**:
|
|
197
|
+
|
|
198
|
+
1. Try to restore the previous session via ``restoreSession(token)``.
|
|
199
|
+
Silent 5-second timeout + generic exception swallow — on any
|
|
200
|
+
failure we fall through to step 2 so a torn-down session never
|
|
201
|
+
leaves the reconnect half-authenticated.
|
|
202
|
+
2. Otherwise re-login with the originally supplied credentials.
|
|
203
|
+
3. Kick the token-refresh timer and resubscribe all existing groups
|
|
204
|
+
on the Subscribe side.
|
|
205
|
+
|
|
206
|
+
Extracted from a closure inside :func:`connect` so it can be
|
|
207
|
+
unit-tested without spinning up a real server. ``initial_connect_done``
|
|
208
|
+
is passed as a 1-element list so the caller (``connect``) can flip
|
|
209
|
+
the flag after the first successful connect without rebinding.
|
|
210
|
+
"""
|
|
211
|
+
if state != ConnectionState.CONNECTION_OK or not initial_connect_done[0]:
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
restored = False
|
|
215
|
+
if req.token:
|
|
216
|
+
try:
|
|
217
|
+
restore_reply = req.restoreSession(req.token)
|
|
218
|
+
restore_msg = restore_reply.get(timeout_ms=5000)
|
|
219
|
+
motorcortex_msg = motorcortex_types.motorcortex()
|
|
220
|
+
if restore_msg and restore_msg.status == motorcortex_msg.OK:
|
|
221
|
+
restored = True
|
|
222
|
+
logger.debug("[SESSION] Session restored using token")
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.debug(f"[SESSION] Token restore failed: {e}")
|
|
225
|
+
|
|
226
|
+
if not restored:
|
|
227
|
+
logger.debug("[SESSION] Falling back to login")
|
|
228
|
+
req.login(login, password).get()
|
|
229
|
+
|
|
230
|
+
req._startTokenRefresh(token_interval_sec)
|
|
231
|
+
sub.resubscribe()
|
|
232
|
+
|
|
233
|
+
|
|
136
234
|
def connect(
|
|
137
235
|
url: str,
|
|
138
236
|
motorcortex_types: "MessageTypes",
|
|
@@ -185,26 +283,14 @@ def connect(
|
|
|
185
283
|
if reconnect and not kwargs.get("state_update"):
|
|
186
284
|
|
|
187
285
|
def stateUpdate(req, sub, state):
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if restore_msg and restore_msg.status == motorcortex_msg.OK:
|
|
197
|
-
restored = True
|
|
198
|
-
logger.debug("[SESSION] Session restored using token")
|
|
199
|
-
except Exception as e:
|
|
200
|
-
logger.debug(f"[SESSION] Token restore failed: {e}")
|
|
201
|
-
|
|
202
|
-
if not restored:
|
|
203
|
-
logger.debug("[SESSION] Falling back to login")
|
|
204
|
-
req.login(kwargs.get("login"), kwargs.get("password")).get()
|
|
205
|
-
|
|
206
|
-
req._startTokenRefresh(token_interval_sec)
|
|
207
|
-
sub.resubscribe()
|
|
286
|
+
_reconnect_state_update(
|
|
287
|
+
req, sub, state,
|
|
288
|
+
motorcortex_types=motorcortex_types,
|
|
289
|
+
login=kwargs.get("login"),
|
|
290
|
+
password=kwargs.get("password"),
|
|
291
|
+
token_interval_sec=token_interval_sec,
|
|
292
|
+
initial_connect_done=initial_connect_done,
|
|
293
|
+
)
|
|
208
294
|
|
|
209
295
|
kwargs.update(state_update=stateUpdate)
|
|
210
296
|
|
|
@@ -21,7 +21,7 @@ from typing import Any, Optional, TYPE_CHECKING
|
|
|
21
21
|
|
|
22
22
|
from motorcortex.setup_logger import logger
|
|
23
23
|
|
|
24
|
-
if TYPE_CHECKING:
|
|
24
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
25
25
|
from motorcortex.message_types import MessageTypes
|
|
26
26
|
from motorcortex.parameter_tree import ParameterTree
|
|
27
27
|
|
|
@@ -242,7 +242,7 @@ def _purge_stale_cache_files(path: str) -> None:
|
|
|
242
242
|
try:
|
|
243
243
|
os.unlink(match)
|
|
244
244
|
logger.debug("[REQUEST] Evicted stale cache file: %s", entry)
|
|
245
|
-
except OSError:
|
|
245
|
+
except OSError: # pragma: no cover
|
|
246
246
|
pass
|
|
247
247
|
|
|
248
248
|
|
|
@@ -276,7 +276,7 @@ def save_parameter_tree_file(path: str, parameter_tree: Any) -> Any:
|
|
|
276
276
|
with os.fdopen(fd, "w") as outfile:
|
|
277
277
|
outfile.write(json.dumps(envelope))
|
|
278
278
|
os.replace(tmp_path, path)
|
|
279
|
-
except Exception:
|
|
279
|
+
except Exception: # pragma: no cover
|
|
280
280
|
# Clean up the temp file if the rename never ran.
|
|
281
281
|
if os.path.exists(tmp_path):
|
|
282
282
|
try:
|
|
@@ -5,35 +5,20 @@
|
|
|
5
5
|
# All rights reserved. Copyright (c) 2016 VECTIONEER.
|
|
6
6
|
#
|
|
7
7
|
|
|
8
|
-
from __future__ import unicode_literals
|
|
9
8
|
import motorcortex.motorcortex_pb2
|
|
10
9
|
import logging
|
|
11
|
-
import sys
|
|
12
10
|
import os
|
|
11
|
+
from importlib.machinery import SourceFileLoader
|
|
12
|
+
from types import ModuleType
|
|
13
13
|
|
|
14
|
-
if sys.version_info[0] >= 3:
|
|
15
|
-
from importlib.machinery import SourceFileLoader
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
def importLibrary(name, path):
|
|
16
|
+
loader = SourceFileLoader(name, path)
|
|
17
|
+
mod = ModuleType(loader.name)
|
|
18
|
+
loader.exec_module(mod)
|
|
19
|
+
return mod
|
|
22
20
|
|
|
23
21
|
|
|
24
|
-
def importLibrary(name, path):
|
|
25
|
-
loader = SourceFileLoader(name, path)
|
|
26
|
-
mod = ModuleType(loader.name)
|
|
27
|
-
loader.exec_module(mod)
|
|
28
|
-
return mod
|
|
29
|
-
else:
|
|
30
|
-
from builtins import bytes
|
|
31
|
-
from imp import load_source
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def importLibrary(name, path):
|
|
35
|
-
return load_source(name, path)
|
|
36
|
-
|
|
37
22
|
from json import load
|
|
38
23
|
from inspect import ismodule
|
|
39
24
|
from typing import Dict
|
|
@@ -270,7 +255,7 @@ class MessageTypes(object):
|
|
|
270
255
|
self._hashes_by_name[name] = hash
|
|
271
256
|
self._types_by_hash[hash] = PrimitiveTypes(name)
|
|
272
257
|
logging.debug(f"Loaded types from {name}")
|
|
273
|
-
except Exception:
|
|
258
|
+
except Exception: # pragma: no cover
|
|
274
259
|
# Best-effort — not every namespace declares the enum we're
|
|
275
260
|
# probing for. Swallowed by design, but scoped to Exception
|
|
276
261
|
# so KeyboardInterrupt / SystemExit still propagate.
|
|
@@ -282,7 +267,7 @@ class MessageTypes(object):
|
|
|
282
267
|
for key in enum.DESCRIPTOR.values:
|
|
283
268
|
setattr(module, key.name, key.number)
|
|
284
269
|
logging.debug(f"Loaded enumerator {enum_name}")
|
|
285
|
-
except Exception:
|
|
270
|
+
except Exception: # pragma: no cover
|
|
286
271
|
# Same best-effort pattern as _loadPrimitives.
|
|
287
272
|
pass
|
|
288
273
|
|
|
@@ -59,7 +59,7 @@ def _register_shutdown(inst: "Request") -> None:
|
|
|
59
59
|
try:
|
|
60
60
|
import threading
|
|
61
61
|
threading._register_atexit(_close_at_exit, inst) # type: ignore[attr-defined]
|
|
62
|
-
except (AttributeError, ImportError):
|
|
62
|
+
except (AttributeError, ImportError): # pragma: no cover
|
|
63
63
|
atexit.register(_close_at_exit, inst)
|
|
64
64
|
|
|
65
65
|
|
|
@@ -41,7 +41,7 @@ from typing import Any, Optional, Type, TYPE_CHECKING
|
|
|
41
41
|
|
|
42
42
|
from motorcortex.exceptions import McxConnectionError
|
|
43
43
|
|
|
44
|
-
if TYPE_CHECKING:
|
|
44
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
45
45
|
from motorcortex.request import Request, ConnectionState
|
|
46
46
|
from motorcortex.subscribe import Subscribe
|
|
47
47
|
from motorcortex.message_types import MessageTypes
|
|
@@ -20,7 +20,7 @@ from motorcortex.setup_logger import logger
|
|
|
20
20
|
from motorcortex.nng_url import NngUrl
|
|
21
21
|
from motorcortex import _connection_state, _request_utils, _subscribe_dispatch
|
|
22
22
|
|
|
23
|
-
if TYPE_CHECKING:
|
|
23
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
24
24
|
from motorcortex.message_types import MessageTypes
|
|
25
25
|
|
|
26
26
|
|
|
@@ -64,7 +64,7 @@ def _register_shutdown(inst: "Subscribe") -> None:
|
|
|
64
64
|
try:
|
|
65
65
|
import threading
|
|
66
66
|
threading._register_atexit(_close_at_exit, inst) # type: ignore[attr-defined]
|
|
67
|
-
except (AttributeError, ImportError):
|
|
67
|
+
except (AttributeError, ImportError): # pragma: no cover
|
|
68
68
|
atexit.register(_close_at_exit, inst)
|
|
69
69
|
|
|
70
70
|
|
|
@@ -272,8 +272,8 @@ class Subscription(object):
|
|
|
272
272
|
|
|
273
273
|
Examples:
|
|
274
274
|
>>> def update(parameters):
|
|
275
|
-
>>>
|
|
276
|
-
>>>
|
|
275
|
+
>>> # parameters is a list of Parameter tuples
|
|
276
|
+
>>> print(parameters)
|
|
277
277
|
>>> data_sub.notify(update)
|
|
278
278
|
|
|
279
279
|
"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.0.1'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '1.0.0rc1'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex/state_callback_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
{motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{motorcortex_python-1.0.0rc1 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|