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.
Files changed (36) hide show
  1. {motorcortex_python-1.0.0rc2/motorcortex_python.egg-info → motorcortex_python-1.0.1}/PKG-INFO +1 -1
  2. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/__init__.py +58 -20
  3. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/_request_builders.py +1 -1
  4. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/_request_utils.py +2 -2
  5. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/message_types.py +9 -24
  6. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/request.py +1 -1
  7. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/session.py +1 -1
  8. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/subscribe.py +2 -2
  9. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/subscription.py +2 -2
  10. motorcortex_python-1.0.1/motorcortex/version.py +1 -0
  11. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1/motorcortex_python.egg-info}/PKG-INFO +1 -1
  12. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/pyproject.toml +1 -1
  13. motorcortex_python-1.0.0rc2/motorcortex/version.py +0 -1
  14. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/LICENSE +0 -0
  15. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/MANIFEST.in +0 -0
  16. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/README.md +0 -0
  17. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/_connection_state.py +0 -0
  18. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/_subscribe_dispatch.py +0 -0
  19. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/exceptions.py +0 -0
  20. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/init_threads.py +0 -0
  21. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/motorcortex_hash.json +0 -0
  22. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/motorcortex_pb2.py +0 -0
  23. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/motorcortex_pb2.pyi +0 -0
  24. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/nng_url.py +0 -0
  25. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/parameter_tree.py +0 -0
  26. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/py.typed +0 -0
  27. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/reply.py +0 -0
  28. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/setup_logger.py +0 -0
  29. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/state_callback_handler.py +0 -0
  30. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex/timespec.py +0 -0
  31. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/SOURCES.txt +0 -0
  32. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/dependency_links.txt +0 -0
  33. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/requires.txt +0 -0
  34. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/motorcortex_python.egg-info/top_level.txt +0 -0
  35. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/setup.cfg +0 -0
  36. {motorcortex_python-1.0.0rc2 → motorcortex_python-1.0.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: motorcortex-python
3
- Version: 1.0.0rc2
3
+ Version: 1.0.1
4
4
  Summary: Python bindings for Motorcortex Engine
5
5
  Home-page: https://www.motorcortex.io
6
6
  Author: Alexey Zakharov
@@ -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
- if state == ConnectionState.CONNECTION_OK and initial_connect_done[0]:
237
- # Try to restore session using token, fall back to login
238
- restored = False
239
- if req.token:
240
- try:
241
- restore_reply = req.restoreSession(req.token)
242
- restore_msg = restore_reply.get(timeout_ms=5000)
243
- motorcortex_msg = motorcortex_types.motorcortex()
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
- if sys.version_info[1] < 4:
18
- def importLibrary(name, path):
19
- return SourceFileLoader(name, path).load_module()
20
- else:
21
- from types import ModuleType
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
- >>> print(parameters) #list of Parameter tuples
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: motorcortex-python
3
- Version: 1.0.0rc2
3
+ Version: 1.0.1
4
4
  Summary: Python bindings for Motorcortex Engine
5
5
  Home-page: https://www.motorcortex.io
6
6
  Author: Alexey Zakharov
@@ -27,7 +27,7 @@ omit = [
27
27
  [tool.coverage.report]
28
28
  show_missing = true
29
29
  skip_covered = false
30
- fail_under = 60
30
+ fail_under = 90
31
31
  exclude_lines = [
32
32
  "pragma: no cover",
33
33
  "raise NotImplementedError",
@@ -1 +0,0 @@
1
- __version__ = '1.0.0rc2'