learning-loop-node 0.13.5__tar.gz → 0.13.7__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.

Potentially problematic release.


This version of learning-loop-node might be problematic. Click here for more details.

Files changed (102) hide show
  1. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/PKG-INFO +5 -3
  2. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/README.md +4 -2
  3. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/data_exchanger.py +1 -1
  4. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/detector_logic.py +10 -18
  5. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/detector_node.py +43 -22
  6. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/loop_communication.py +18 -18
  7. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/node.py +2 -1
  8. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/conftest.py +0 -4
  9. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/test_client_communication.py +0 -3
  10. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/pyproject.toml +1 -1
  11. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/__init__.py +0 -0
  12. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/annotation/__init__.py +0 -0
  13. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/annotation/annotator_logic.py +0 -0
  14. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/annotation/annotator_node.py +0 -0
  15. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/data_classes/__init__.py +0 -0
  16. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/data_classes/annotations.py +0 -0
  17. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/data_classes/detections.py +0 -0
  18. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/data_classes/general.py +0 -0
  19. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/data_classes/image_metadata.py +0 -0
  20. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/data_classes/socket_response.py +0 -0
  21. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/data_classes/training.py +0 -0
  22. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/__init__.py +0 -0
  23. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/exceptions.py +0 -0
  24. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/inbox_filter/__init__.py +0 -0
  25. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/inbox_filter/cam_observation_history.py +0 -0
  26. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/inbox_filter/relevance_filter.py +0 -0
  27. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/outbox.py +0 -0
  28. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/__init__.py +0 -0
  29. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/about.py +0 -0
  30. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/backdoor_controls.py +0 -0
  31. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/detect.py +0 -0
  32. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/model_version_control.py +0 -0
  33. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/operation_mode.py +0 -0
  34. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/outbox_mode.py +0 -0
  35. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/upload.py +0 -0
  36. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/enums/__init__.py +0 -0
  37. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/enums/annotator.py +0 -0
  38. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/enums/detector.py +0 -0
  39. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/enums/general.py +0 -0
  40. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/enums/trainer.py +0 -0
  41. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/examples/novelty_score_updater.py +0 -0
  42. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/globals.py +0 -0
  43. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/helpers/__init__.py +0 -0
  44. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/helpers/environment_reader.py +0 -0
  45. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/helpers/gdrive_downloader.py +0 -0
  46. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/helpers/log_conf.py +0 -0
  47. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/helpers/misc.py +0 -0
  48. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/py.typed +0 -0
  49. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/rest.py +0 -0
  50. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/__init__.py +0 -0
  51. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/annotator/__init__.py +0 -0
  52. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/annotator/conftest.py +0 -0
  53. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/annotator/pytest.ini +0 -0
  54. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/annotator/test_annotator_node.py +0 -0
  55. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/__init__.py +0 -0
  56. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/inbox_filter/__init__.py +0 -0
  57. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/inbox_filter/test_observation.py +0 -0
  58. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/inbox_filter/test_relevance_group.py +0 -0
  59. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/inbox_filter/test_unexpected_observations_count.py +0 -0
  60. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/pytest.ini +0 -0
  61. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/test.jpg +0 -0
  62. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/test_detector_node.py +0 -0
  63. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/test_outbox.py +0 -0
  64. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/test_relevance_filter.py +0 -0
  65. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/testing_detector.py +0 -0
  66. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/__init__.py +0 -0
  67. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/conftest.py +0 -0
  68. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/pytest.ini +0 -0
  69. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/test_data/file_1.txt +0 -0
  70. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/test_data/file_2.txt +0 -0
  71. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/test_data/model.json +0 -0
  72. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/test_data_classes.py +0 -0
  73. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/test_downloader.py +0 -0
  74. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/test_learning_loop_node.py +0 -0
  75. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/test_helper.py +0 -0
  76. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/__init__.py +0 -0
  77. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/conftest.py +0 -0
  78. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/pytest.ini +0 -0
  79. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/state_helper.py +0 -0
  80. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/states/__init__.py +0 -0
  81. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/states/test_state_cleanup.py +0 -0
  82. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/states/test_state_detecting.py +0 -0
  83. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/states/test_state_download_train_model.py +0 -0
  84. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/states/test_state_prepare.py +0 -0
  85. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/states/test_state_sync_confusion_matrix.py +0 -0
  86. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/states/test_state_train.py +0 -0
  87. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/states/test_state_upload_detections.py +0 -0
  88. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/states/test_state_upload_model.py +0 -0
  89. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/test_errors.py +0 -0
  90. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/test_trainer_states.py +0 -0
  91. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/testing_trainer_logic.py +0 -0
  92. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/trainer/__init__.py +0 -0
  93. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/trainer/downloader.py +0 -0
  94. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/trainer/exceptions.py +0 -0
  95. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/trainer/executor.py +0 -0
  96. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/trainer/io_helpers.py +0 -0
  97. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/trainer/rest/__init__.py +0 -0
  98. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/trainer/rest/backdoor_controls.py +0 -0
  99. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/trainer/test_executor.py +0 -0
  100. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/trainer/trainer_logic.py +0 -0
  101. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/trainer/trainer_logic_generic.py +0 -0
  102. {learning_loop_node-0.13.5 → learning_loop_node-0.13.7}/learning_loop_node/trainer/trainer_node.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: learning-loop-node
3
- Version: 0.13.5
3
+ Version: 0.13.7
4
4
  Summary: Python Library for Nodes which connect to the Zauberzeug Learning Loop
5
5
  Home-page: https://github.com/zauberzeug/learning_loop_node
6
6
  License: MIT
@@ -63,8 +63,8 @@ You can configure connection to our Learning Loop by specifying the following en
63
63
  | LOOP_USERNAME | USERNAME | Learning Loop user name | all besides Detector |
64
64
  | LOOP_PASSWORD | PASSWORD | Learning Loop password | all besides Detector |
65
65
  | LOOP_SSL_CERT_PATH | - | Path to the SSL certificate | all (opt.) |
66
- | LOOP_ORGANIZATION | ORGANIZATION | Organization name | Detector |
67
- | LOOP_PROJECT | PROJECT | Project name | Detector (opt.) |
66
+ | LOOP_ORGANIZATION | ORGANIZATION | Organization ID | Detector |
67
+ | LOOP_PROJECT | PROJECT | Project ID | Detector (opt.) |
68
68
  | MIN_UNCERTAIN_THRESHOLD | - | smallest confidence (float) at which auto-upload will happen | Detector (opt.) |
69
69
  | MAX_UNCERTAIN_THRESHOLD | - | largest confidence (float) at which auto-upload will happen | Detector (opt.) |
70
70
  | INFERENCE_BATCH_SIZE | - | Batch size of trainer when calculating detections | Trainer (opt.) |
@@ -73,6 +73,8 @@ You can configure connection to our Learning Loop by specifying the following en
73
73
  | TRAINER_IDLE_TIMEOUT_SEC | - | Automatically shutdown trainer after timeout (in seconds) | Trainer (opt.) |
74
74
  | USE_BACKDOOR_CONTROLS | - | Always enable backdoor controls (set to 1) | Trainer / Detector (opt.) |
75
75
 
76
+ Note that organization and project IDs are always lower case and may differ from the names in the Learning Loop which can have uppercase letters.
77
+
76
78
  #### Testing
77
79
 
78
80
  We use github actions for CI. Tests can also be executed locally by running
@@ -23,8 +23,8 @@ You can configure connection to our Learning Loop by specifying the following en
23
23
  | LOOP_USERNAME | USERNAME | Learning Loop user name | all besides Detector |
24
24
  | LOOP_PASSWORD | PASSWORD | Learning Loop password | all besides Detector |
25
25
  | LOOP_SSL_CERT_PATH | - | Path to the SSL certificate | all (opt.) |
26
- | LOOP_ORGANIZATION | ORGANIZATION | Organization name | Detector |
27
- | LOOP_PROJECT | PROJECT | Project name | Detector (opt.) |
26
+ | LOOP_ORGANIZATION | ORGANIZATION | Organization ID | Detector |
27
+ | LOOP_PROJECT | PROJECT | Project ID | Detector (opt.) |
28
28
  | MIN_UNCERTAIN_THRESHOLD | - | smallest confidence (float) at which auto-upload will happen | Detector (opt.) |
29
29
  | MAX_UNCERTAIN_THRESHOLD | - | largest confidence (float) at which auto-upload will happen | Detector (opt.) |
30
30
  | INFERENCE_BATCH_SIZE | - | Batch size of trainer when calculating detections | Trainer (opt.) |
@@ -33,6 +33,8 @@ You can configure connection to our Learning Loop by specifying the following en
33
33
  | TRAINER_IDLE_TIMEOUT_SEC | - | Automatically shutdown trainer after timeout (in seconds) | Trainer (opt.) |
34
34
  | USE_BACKDOOR_CONTROLS | - | Always enable backdoor controls (set to 1) | Trainer / Detector (opt.) |
35
35
 
36
+ Note that organization and project IDs are always lower case and may differ from the names in the Learning Loop which can have uppercase letters.
37
+
36
38
  #### Testing
37
39
 
38
40
  We use github actions for CI. Tests can also be executed locally by running
@@ -143,7 +143,7 @@ class DataExchanger():
143
143
  logging.info('Downloading model data for uuid %s from the loop to %s..', model_uuid, target_folder)
144
144
 
145
145
  path = f'/{context.organization}/projects/{context.project}/models/{model_uuid}/{model_format}/file'
146
- response = await self.loop_communicator.get(path, requires_login=False)
146
+ response = await self.loop_communicator.get(path, requires_login=False, timeout=60*10)
147
147
  if response.status_code != 200:
148
148
  decoded_content = response.content.decode('utf-8')
149
149
  logging.error('could not download loop/%s: %s, content: %s', path,
@@ -13,37 +13,29 @@ class DetectorLogic():
13
13
 
14
14
  def __init__(self, model_format: str) -> None:
15
15
  self.model_format: str = model_format
16
- self._model_info: Optional[ModelInformation] = None
16
+ self.model_info: Optional[ModelInformation] = None
17
17
 
18
18
  self._remaining_init_attempts: int = 2
19
19
 
20
20
  async def soft_reload(self):
21
- self._model_info = None
22
-
23
- @property
24
- def model_info(self) -> ModelInformation:
25
- if self._model_info is None:
26
- raise Exception('Model not loaded')
27
- return self._model_info
28
-
29
- @property
30
- def is_initialized(self) -> bool:
31
- return self._model_info is not None
21
+ self.model_info = None
32
22
 
33
23
  def load_model_info_and_init_model(self):
34
24
  logging.info('Loading model from %s', GLOBALS.data_folder)
35
- self._model_info = ModelInformation.load_from_disk(f'{GLOBALS.data_folder}/model')
36
- if self._model_info is None:
25
+ self.model_info = ModelInformation.load_from_disk(f'{GLOBALS.data_folder}/model')
26
+ if self.model_info is None:
37
27
  logging.error('No model found')
38
- self._model_info = None
28
+ self.model_info = None
29
+ return
30
+
39
31
  try:
40
32
  self.init()
41
- logging.info('Successfully loaded model %s', self._model_info)
33
+ logging.info('Successfully loaded model %s', self.model_info)
42
34
  self._remaining_init_attempts = 2
43
35
  except Exception:
44
36
  self._remaining_init_attempts -= 1
45
- self._model_info = None
46
- logging.error('Could not init model %s. Retries left: %s', self._model_info, self._remaining_init_attempts)
37
+ self.model_info = None
38
+ logging.error('Could not init model %s. Retries left: %s', self.model_info, self._remaining_init_attempts)
47
39
  if self._remaining_init_attempts == 0:
48
40
  raise NodeNeedsRestartError('Could not init model') from None
49
41
  raise
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import contextlib
3
+ import math
3
4
  import os
4
5
  import shutil
5
6
  import subprocess
@@ -15,8 +16,16 @@ from dacite import from_dict
15
16
  from fastapi.encoders import jsonable_encoder
16
17
  from socketio import AsyncClient
17
18
 
18
- from ..data_classes import (AboutResponse, Category, Context, DetectionStatus, ImageMetadata, ModelInformation,
19
- ModelVersionResponse, Shape)
19
+ from ..data_classes import (
20
+ AboutResponse,
21
+ Category,
22
+ Context,
23
+ DetectionStatus,
24
+ ImageMetadata,
25
+ ModelInformation,
26
+ ModelVersionResponse,
27
+ Shape,
28
+ )
20
29
  from ..data_classes.socket_response import SocketResponse
21
30
  from ..data_exchanger import DataExchanger, DownloadError
22
31
  from ..enums import OperationMode, VersionMode
@@ -82,13 +91,13 @@ class DetectorNode(Node):
82
91
  return AboutResponse(
83
92
  operation_mode=self.operation_mode.value,
84
93
  state=self.status.state,
85
- model_info=self.detector_logic._model_info, # pylint: disable=protected-access
94
+ model_info=self.detector_logic.model_info, # pylint: disable=protected-access
86
95
  target_model=self.target_model.version if self.target_model else None,
87
96
  version_control=self.version_control.value
88
97
  )
89
98
 
90
99
  def get_model_version_response(self) -> ModelVersionResponse:
91
- current_version = self.detector_logic._model_info.version if self.detector_logic._model_info is not None else 'None' # pylint: disable=protected-access
100
+ current_version = self.detector_logic.model_info.version if self.detector_logic.model_info is not None else 'None' # pylint: disable=protected-access
92
101
  target_version = self.target_model.version if self.target_model is not None else 'None'
93
102
  loop_version = self.loop_deployment_target.version if self.loop_deployment_target is not None else 'None'
94
103
 
@@ -237,7 +246,7 @@ class DetectorNode(Node):
237
246
 
238
247
  @self.sio.event
239
248
  async def info(sid) -> Dict:
240
- if self.detector_logic.is_initialized:
249
+ if self.detector_logic.model_info is not None:
241
250
  return asdict(self.detector_logic.model_info)
242
251
  return {"status": "No model loaded"}
243
252
 
@@ -274,7 +283,7 @@ class DetectorNode(Node):
274
283
  '''upload an image with detections'''
275
284
 
276
285
  detection_data = data.get('detections', {})
277
- if detection_data and self.detector_logic.is_initialized:
286
+ if detection_data and self.detector_logic.model_info is not None:
278
287
  try:
279
288
  image_metadata = from_dict(data_class=ImageMetadata, data=detection_data)
280
289
  except Exception as e:
@@ -323,7 +332,7 @@ class DetectorNode(Node):
323
332
  self.log.debug('not checking for updates; no target model selected')
324
333
  return
325
334
 
326
- if self.detector_logic.is_initialized:
335
+ if self.detector_logic.model_info is not None:
327
336
  current_version = self.detector_logic.model_info.version
328
337
  else:
329
338
  current_version = None
@@ -335,16 +344,21 @@ class DetectorNode(Node):
335
344
  with step_into(GLOBALS.data_folder):
336
345
  model_symlink = 'model'
337
346
  target_model_folder = f'models/{self.target_model.version}'
338
- if not os.path.exists(target_model_folder):
339
- os.makedirs(target_model_folder)
340
- await self.data_exchanger.download_model(target_model_folder,
341
- Context(organization=self.organization,
342
- project=self.project),
343
- self.target_model.id,
344
- self.detector_logic.model_format)
345
- self.log.info('Downloaded model %s', self.target_model.version)
346
- else:
347
+ if os.path.exists(target_model_folder) and len(os.listdir(target_model_folder)) > 0:
347
348
  self.log.info('No need to download model %s (already exists)', self.target_model.version)
349
+ else:
350
+ os.makedirs(target_model_folder, exist_ok=True)
351
+ try:
352
+ await self.data_exchanger.download_model(target_model_folder,
353
+ Context(organization=self.organization,
354
+ project=self.project),
355
+ self.target_model.id,
356
+ self.detector_logic.model_format)
357
+ self.log.info('Downloaded model %s', self.target_model.version)
358
+ except Exception:
359
+ self.log.exception('Could not download model %s', self.target_model.version)
360
+ shutil.rmtree(target_model_folder, ignore_errors=True)
361
+ return
348
362
  try:
349
363
  os.unlink(model_symlink)
350
364
  os.remove(model_symlink)
@@ -391,9 +405,9 @@ class DetectorNode(Node):
391
405
  self.log.info('Status sync failed: not connected')
392
406
  raise Exception('Status sync failed: not connected')
393
407
 
394
- try:
408
+ if self.detector_logic.model_info is not None:
395
409
  current_model = self.detector_logic.model_info.version
396
- except Exception:
410
+ else:
397
411
  current_model = None
398
412
 
399
413
  target_model_version = self.target_model.version if self.target_model else None
@@ -413,16 +427,23 @@ class DetectorNode(Node):
413
427
  self.log_status_on_change(status.state or 'None', status)
414
428
 
415
429
  # NOTE: sending organization and project is no longer required!
416
- response = await self.sio_client.call('update_detector', (self.organization, self.project, jsonable_encoder(asdict(status))))
430
+ try:
431
+ response = await self.sio_client.call('update_detector', (self.organization, self.project, jsonable_encoder(asdict(status))))
432
+ except TimeoutError:
433
+ self.socket_connection_broken = True
434
+ self.log.exception('TimeoutError for sending status update (will try to reconnect):')
435
+ raise Exception('Status update failed due to timeout') from None
436
+
417
437
  if not response:
418
438
  self.socket_connection_broken = True
419
- return
439
+ self.log.error('Status update failed (will try to reconnect): %s', response)
440
+ raise Exception('Status update failed: Did not receive a response from the learning loop')
420
441
 
421
442
  socket_response = from_dict(data_class=SocketResponse, data=response)
422
443
  if not socket_response.success:
423
444
  self.socket_connection_broken = True
424
- self.log.error('Statusupdate failed: %s', response)
425
- raise Exception(f'Statusupdate failed: {response}')
445
+ self.log.error('Status update failed (will try to reconnect): %s', response)
446
+ raise Exception(f'Status update failed. Response from learning loop: {response}')
426
447
 
427
448
  assert socket_response.payload is not None
428
449
 
@@ -100,31 +100,31 @@ class LoopCommunicator():
100
100
  raise TimeoutError('Backend not ready within timeout')
101
101
  await asyncio.sleep(10)
102
102
 
103
- async def retry_on_401(self, func: Callable[..., Awaitable[httpx.Response]], *args, **kwargs) -> httpx.Response:
103
+ async def _retry_on_401(self, func: Callable[..., Awaitable[httpx.Response]], *args, **kwargs) -> httpx.Response:
104
104
  response = await func(*args, **kwargs)
105
105
  if response.status_code == 401:
106
106
  await self.ensure_login(relogin=True)
107
107
  response = await func(*args, **kwargs)
108
108
  return response
109
109
 
110
- async def get(self, path: str, requires_login: bool = True, api_prefix: str = '/api') -> httpx.Response:
110
+ async def get(self, path: str, requires_login: bool = True, api_prefix: str = '/api', timeout: int = 60) -> httpx.Response:
111
111
  if requires_login:
112
112
  await self.ensure_login()
113
- return await self.retry_on_401(self._get, path, api_prefix)
113
+ return await self._retry_on_401(self._get, path, api_prefix, timeout)
114
114
  return await self._get(path, api_prefix)
115
115
 
116
116
  @retry_on_429
117
- async def _get(self, path: str, api_prefix: str) -> httpx.Response:
118
- return await self.async_client.get(api_prefix+path)
117
+ async def _get(self, path: str, api_prefix: str, timeout: int = 60) -> httpx.Response:
118
+ return await self.async_client.get(api_prefix+path, timeout=timeout)
119
119
 
120
- async def put(self, path: str, files: Optional[List[str]] = None, requires_login: bool = True, api_prefix: str = '/api', **kwargs) -> httpx.Response:
120
+ async def put(self, path: str, files: Optional[List[str]] = None, requires_login: bool = True, api_prefix: str = '/api', timeout: int = 60, **kwargs) -> httpx.Response:
121
121
  if requires_login:
122
122
  await self.ensure_login()
123
- return await self.retry_on_401(self._put, path, files, api_prefix, **kwargs)
124
- return await self._put(path, files, api_prefix, **kwargs)
123
+ return await self._retry_on_401(self._put, path, files, api_prefix, timeout, **kwargs)
124
+ return await self._put(path, files, api_prefix, timeout, **kwargs)
125
125
 
126
126
  @retry_on_429
127
- async def _put(self, path: str, files: Optional[List[str]], api_prefix: str, **kwargs) -> httpx.Response:
127
+ async def _put(self, path: str, files: Optional[List[str]], api_prefix: str, timeout: int = 60, **kwargs) -> httpx.Response:
128
128
  if files is None:
129
129
  return await self.async_client.put(api_prefix+path, **kwargs)
130
130
 
@@ -139,29 +139,29 @@ class LoopCommunicator():
139
139
 
140
140
  try:
141
141
  file_list = [('files', fh) for fh in file_handles] # Use file handles
142
- response = await self.async_client.put(api_prefix+path, files=file_list)
142
+ response = await self.async_client.put(api_prefix+path, files=file_list, timeout=timeout)
143
143
  finally:
144
144
  for fh in file_handles:
145
145
  fh.close() # Ensure all files are closed
146
146
 
147
147
  return response
148
148
 
149
- async def post(self, path: str, requires_login: bool = True, api_prefix: str = '/api', **kwargs) -> httpx.Response:
149
+ async def post(self, path: str, requires_login: bool = True, api_prefix: str = '/api', timeout: int = 60, **kwargs) -> httpx.Response:
150
150
  if requires_login:
151
151
  await self.ensure_login()
152
- return await self.retry_on_401(self._post, path, api_prefix, **kwargs)
152
+ return await self._retry_on_401(self._post, path, api_prefix, timeout, **kwargs)
153
153
  return await self._post(path, api_prefix, **kwargs)
154
154
 
155
155
  @retry_on_429
156
- async def _post(self, path, api_prefix='/api', **kwargs) -> httpx.Response:
157
- return await self.async_client.post(api_prefix+path, **kwargs)
156
+ async def _post(self, path, api_prefix='/api', timeout: int = 60, **kwargs) -> httpx.Response:
157
+ return await self.async_client.post(api_prefix+path, timeout=timeout, **kwargs)
158
158
 
159
- async def delete(self, path: str, requires_login: bool = True, api_prefix: str = '/api', **kwargs) -> httpx.Response:
159
+ async def delete(self, path: str, requires_login: bool = True, api_prefix: str = '/api', timeout: int = 60, **kwargs) -> httpx.Response:
160
160
  if requires_login:
161
161
  await self.ensure_login()
162
- return await self.retry_on_401(self._delete, path, api_prefix, **kwargs)
162
+ return await self._retry_on_401(self._delete, path, api_prefix, timeout, **kwargs)
163
163
  return await self._delete(path, api_prefix, **kwargs)
164
164
 
165
165
  @retry_on_429
166
- async def _delete(self, path, api_prefix, **kwargs) -> httpx.Response:
167
- return await self.async_client.delete(api_prefix+path, **kwargs)
166
+ async def _delete(self, path, api_prefix, timeout: int = 60, **kwargs) -> httpx.Response:
167
+ return await self.async_client.delete(api_prefix+path, timeout=timeout, **kwargs)
@@ -74,6 +74,7 @@ class Node(FastAPI):
74
74
  self.repeat_loop_lock = asyncio.Lock()
75
75
 
76
76
  self.previous_state: Optional[str] = None
77
+ self.repeat_loop_cycle_sec = 5
77
78
 
78
79
  def log_status_on_change(self, current_state_str: str, full_status: Any):
79
80
  if self.previous_state != current_state_str:
@@ -146,7 +147,7 @@ class Node(FastAPI):
146
147
  except Exception:
147
148
  self.log.exception('error in repeat loop')
148
149
 
149
- await asyncio.sleep(5)
150
+ await asyncio.sleep(self.repeat_loop_cycle_sec)
150
151
 
151
152
  async def _ensure_sio_connection(self):
152
153
  if self.socket_connection_broken or self._sio_client is None or not self.sio_client.connected:
@@ -129,10 +129,6 @@ class MockDetectorLogic(DetectorLogic): # pylint: disable=abstract-method
129
129
  model_name="mock",
130
130
  )])
131
131
 
132
- @property
133
- def is_initialized(self):
134
- return True
135
-
136
132
  def evaluate_with_all_info(self, image: np.ndarray, tags: List[str], source: Optional[str] = None, creation_date: Optional[str] = None):
137
133
  return self.image_metadata
138
134
 
@@ -51,9 +51,6 @@ def test_rest_detect(test_detector_node: DetectorNode, grouping_key: str):
51
51
  headers = {grouping_key: '0:0:0:0', 'tags': 'some_tag'}
52
52
 
53
53
  assert isinstance(test_detector_node.detector_logic, TestingDetectorLogic)
54
- # test_detector_node.detector_logic.mock_is_initialized = True
55
- # print(test_detector_node.detector_logic.mock_is_initialized)
56
- # print(test_detector_node.detector_logic.is_initialized)
57
54
  response = requests.post(f'http://localhost:{GLOBALS.detector_port}/detect',
58
55
  files=image, headers=headers, timeout=30)
59
56
  assert response.status_code == 200
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "learning_loop_node"
3
- version = "v0.13.5"
3
+ version = "v0.13.7"
4
4
  description = "Python Library for Nodes which connect to the Zauberzeug Learning Loop"
5
5
  authors = ["Zauberzeug GmbH <info@zauberzeug.com>"]
6
6
  license = "MIT"