motorcortex-python 1.0.0rc2__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.0rc2/motorcortex_python.egg-info → motorcortex_python-1.0.1}/PKG-INFO +1 -1
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/__init__.py +58 -20
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/_request_builders.py +1 -1
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/_request_utils.py +2 -2
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/message_types.py +9 -24
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/request.py +1 -1
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/session.py +1 -1
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/subscribe.py +2 -2
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/subscription.py +2 -2
- motorcortex_python-1.0.1/motorcortex/version.py +1 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1/motorcortex_python.egg-info}/PKG-INFO +1 -1
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/pyproject.toml +1 -1
- motorcortex_python-1.0.0rc2/motorcortex/version.py +0 -1
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/LICENSE +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/MANIFEST.in +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/README.md +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/_connection_state.py +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/_subscribe_dispatch.py +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/exceptions.py +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/init_threads.py +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/motorcortex_hash.json +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/motorcortex_pb2.py +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/motorcortex_pb2.pyi +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/nng_url.py +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/parameter_tree.py +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/py.typed +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/reply.py +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/setup_logger.py +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/state_callback_handler.py +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/timespec.py +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/SOURCES.txt +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/dependency_links.txt +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/requires.txt +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/top_level.txt +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/setup.cfg +0 -0
- {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/setup.py +0 -0
|
@@ -181,6 +181,56 @@ def makeUrl(address: str, port: int | None) -> str:
|
|
|
181
181
|
return address
|
|
182
182
|
|
|
183
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
|
+
|
|
184
234
|
def connect(
|
|
185
235
|
url: str,
|
|
186
236
|
motorcortex_types: "MessageTypes",
|
|
@@ -233,26 +283,14 @@ def connect(
|
|
|
233
283
|
if reconnect and not kwargs.get("state_update"):
|
|
234
284
|
|
|
235
285
|
def stateUpdate(req, sub, state):
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if restore_msg and restore_msg.status == motorcortex_msg.OK:
|
|
245
|
-
restored = True
|
|
246
|
-
logger.debug("[SESSION] Session restored using token")
|
|
247
|
-
except Exception as e:
|
|
248
|
-
logger.debug(f"[SESSION] Token restore failed: {e}")
|
|
249
|
-
|
|
250
|
-
if not restored:
|
|
251
|
-
logger.debug("[SESSION] Falling back to login")
|
|
252
|
-
req.login(kwargs.get("login"), kwargs.get("password")).get()
|
|
253
|
-
|
|
254
|
-
req._startTokenRefresh(token_interval_sec)
|
|
255
|
-
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
|
+
)
|
|
256
294
|
|
|
257
295
|
kwargs.update(state_update=stateUpdate)
|
|
258
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.0rc2'
|
|
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.0rc2 → motorcortex_python-1.0.1}/motorcortex/state_callback_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
{motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|