learning-loop-node 0.15.0__tar.gz → 0.16.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.
Potentially problematic release.
This version of learning-loop-node might be problematic. Click here for more details.
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/PKG-INFO +1 -1
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/annotation/annotator_node.py +7 -1
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/data_classes/__init__.py +32 -6
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/data_classes/general.py +10 -11
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/detector_node.py +145 -134
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/outbox.py +2 -2
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/loop_communication.py +9 -8
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/node.py +49 -36
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/rest.py +3 -2
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/annotator/test_annotator_node.py +4 -1
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/detector/conftest.py +9 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/detector/test_outbox.py +27 -15
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/states/test_state_sync_confusion_matrix.py +4 -1
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/trainer/trainer_logic_generic.py +3 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/trainer/trainer_node.py +4 -3
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/pyproject.toml +1 -1
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/README.md +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/annotation/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/annotation/annotator_logic.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/data_classes/annotations.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/data_classes/detections.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/data_classes/image_metadata.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/data_classes/socket_response.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/data_classes/training.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/data_exchanger.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/detector_logic.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/exceptions.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/inbox_filter/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/inbox_filter/cam_observation_history.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/inbox_filter/relevance_filter.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/rest/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/rest/about.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/rest/backdoor_controls.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/rest/detect.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/rest/model_version_control.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/rest/operation_mode.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/rest/outbox_mode.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/rest/upload.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/enums/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/enums/annotator.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/enums/detector.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/enums/general.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/enums/trainer.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/examples/novelty_score_updater.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/globals.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/helpers/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/helpers/background_tasks.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/helpers/environment_reader.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/helpers/gdrive_downloader.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/helpers/log_conf.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/helpers/misc.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/helpers/run.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/py.typed +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/annotator/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/annotator/conftest.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/annotator/pytest.ini +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/detector/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/detector/inbox_filter/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/detector/inbox_filter/test_observation.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/detector/inbox_filter/test_relevance_group.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/detector/inbox_filter/test_unexpected_observations_count.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/detector/pytest.ini +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/detector/test.jpg +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/detector/test_client_communication.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/detector/test_detector_node.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/detector/test_relevance_filter.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/detector/testing_detector.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/general/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/general/conftest.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/general/pytest.ini +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/general/test_data/file_1.txt +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/general/test_data/file_2.txt +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/general/test_data/model.json +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/general/test_data_classes.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/general/test_downloader.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/general/test_learning_loop_node.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/test_helper.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/conftest.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/pytest.ini +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/state_helper.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/states/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/states/test_state_cleanup.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/states/test_state_detecting.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/states/test_state_download_train_model.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/states/test_state_prepare.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/states/test_state_train.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/states/test_state_upload_detections.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/states/test_state_upload_model.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/test_errors.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/test_trainer_states.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/tests/trainer/testing_trainer_logic.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/trainer/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/trainer/downloader.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/trainer/exceptions.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/trainer/executor.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/trainer/io_helpers.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/trainer/rest/__init__.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/trainer/rest/backdoor_controls.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/trainer/test_executor.py +0 -0
- {learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/trainer/trainer_logic.py +0 -0
|
@@ -18,7 +18,7 @@ from .annotator_logic import AnnotatorLogic
|
|
|
18
18
|
class AnnotatorNode(Node):
|
|
19
19
|
|
|
20
20
|
def __init__(self, name: str, annotator_logic: AnnotatorLogic, uuid: Optional[str] = None):
|
|
21
|
-
super().__init__(name, uuid, 'annotation_node')
|
|
21
|
+
super().__init__(name, uuid=uuid, node_type='annotation_node')
|
|
22
22
|
self.tool = annotator_logic
|
|
23
23
|
self.histories: Dict = {}
|
|
24
24
|
annotator_logic.init(self)
|
|
@@ -35,6 +35,9 @@ class AnnotatorNode(Node):
|
|
|
35
35
|
return self.tool.logout_user(sid)
|
|
36
36
|
|
|
37
37
|
async def _handle_user_input(self, user_input_dict: Dict) -> str:
|
|
38
|
+
if not self.sio_client or not self.sio_client.connected:
|
|
39
|
+
raise ConnectionError('SocketIO client is not connected')
|
|
40
|
+
|
|
38
41
|
user_input = from_dict(data_class=UserInput, data=user_input_dict)
|
|
39
42
|
|
|
40
43
|
if user_input.data.key_up == 'Escape':
|
|
@@ -66,6 +69,9 @@ class AnnotatorNode(Node):
|
|
|
66
69
|
|
|
67
70
|
async def send_status(self):
|
|
68
71
|
|
|
72
|
+
if not self.sio_client or not self.sio_client.connected:
|
|
73
|
+
raise ConnectionError('SocketIO client is not connected')
|
|
74
|
+
|
|
69
75
|
status = AnnotationNodeStatus(
|
|
70
76
|
id=self.uuid,
|
|
71
77
|
name=self.name,
|
{learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/data_classes/__init__.py
RENAMED
|
@@ -1,17 +1,43 @@
|
|
|
1
1
|
from .annotations import AnnotationData, SegmentationAnnotation, ToolOutput, UserInput
|
|
2
|
-
from .detections import (
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
from .detections import (
|
|
3
|
+
BoxDetection,
|
|
4
|
+
ClassificationDetection,
|
|
5
|
+
Detections,
|
|
6
|
+
Observation,
|
|
7
|
+
Point,
|
|
8
|
+
PointDetection,
|
|
9
|
+
SegmentationDetection,
|
|
10
|
+
Shape,
|
|
11
|
+
)
|
|
12
|
+
from .general import (
|
|
13
|
+
AboutResponse,
|
|
14
|
+
AnnotationNodeStatus,
|
|
15
|
+
Category,
|
|
16
|
+
Context,
|
|
17
|
+
DetectorStatus,
|
|
18
|
+
ErrorConfiguration,
|
|
19
|
+
ModelInformation,
|
|
20
|
+
ModelVersionResponse,
|
|
21
|
+
NodeState,
|
|
22
|
+
NodeStatus,
|
|
23
|
+
)
|
|
6
24
|
from .image_metadata import ImageMetadata, ImagesMetadata
|
|
7
25
|
from .socket_response import SocketResponse
|
|
8
|
-
from .training import
|
|
26
|
+
from .training import (
|
|
27
|
+
Errors,
|
|
28
|
+
PretrainedModel,
|
|
29
|
+
Training,
|
|
30
|
+
TrainingError,
|
|
31
|
+
TrainingOut,
|
|
32
|
+
TrainingStateData,
|
|
33
|
+
TrainingStatus,
|
|
34
|
+
)
|
|
9
35
|
|
|
10
36
|
__all__ = [
|
|
11
37
|
'AboutResponse', 'AnnotationData', 'SegmentationAnnotation', 'ToolOutput', 'UserInput',
|
|
12
38
|
'BoxDetection', 'ClassificationDetection', 'ImageMetadata', 'Observation', 'Point', 'PointDetection',
|
|
13
39
|
'SegmentationDetection', 'Shape', 'Detections',
|
|
14
|
-
'AnnotationNodeStatus', 'Category', 'Context', '
|
|
40
|
+
'AnnotationNodeStatus', 'Category', 'Context', 'DetectorStatus', 'ErrorConfiguration',
|
|
15
41
|
'ModelInformation', 'NodeState', 'NodeStatus', 'ModelVersionResponse', 'ImagesMetadata',
|
|
16
42
|
'SocketResponse',
|
|
17
43
|
'Errors', 'PretrainedModel', 'Training',
|
{learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/data_classes/general.py
RENAMED
|
@@ -148,8 +148,8 @@ class NodeState(str, Enum):
|
|
|
148
148
|
class NodeStatus():
|
|
149
149
|
id: str
|
|
150
150
|
name: str
|
|
151
|
-
state:
|
|
152
|
-
uptime:
|
|
151
|
+
state: NodeState = NodeState.Online
|
|
152
|
+
uptime: int = 0
|
|
153
153
|
errors: Dict = field(default_factory=dict)
|
|
154
154
|
capabilities: List[str] = field(default_factory=list)
|
|
155
155
|
|
|
@@ -175,14 +175,13 @@ class AnnotationNodeStatus(NodeStatus):
|
|
|
175
175
|
|
|
176
176
|
|
|
177
177
|
@dataclass(**KWONLY_SLOTS)
|
|
178
|
-
class
|
|
179
|
-
|
|
178
|
+
class DetectorStatus():
|
|
179
|
+
uuid: str
|
|
180
180
|
name: str
|
|
181
|
+
state: NodeState
|
|
182
|
+
uptime: int
|
|
181
183
|
model_format: str
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
errors:
|
|
185
|
-
|
|
186
|
-
current_model: Optional[str] = None
|
|
187
|
-
target_model: Optional[str] = None
|
|
188
|
-
operation_mode: Optional[str] = None
|
|
184
|
+
current_model: Optional[str]
|
|
185
|
+
target_model: Optional[str]
|
|
186
|
+
errors: Dict
|
|
187
|
+
operation_mode: str
|
{learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/detector_node.py
RENAMED
|
@@ -13,9 +13,17 @@ from dacite import from_dict
|
|
|
13
13
|
from fastapi.encoders import jsonable_encoder
|
|
14
14
|
from socketio import AsyncClient
|
|
15
15
|
|
|
16
|
-
from ..data_classes import (
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
from ..data_classes import (
|
|
17
|
+
AboutResponse,
|
|
18
|
+
Category,
|
|
19
|
+
Context,
|
|
20
|
+
DetectorStatus,
|
|
21
|
+
ImageMetadata,
|
|
22
|
+
ImagesMetadata,
|
|
23
|
+
ModelInformation,
|
|
24
|
+
ModelVersionResponse,
|
|
25
|
+
Shape,
|
|
26
|
+
)
|
|
19
27
|
from ..data_exchanger import DataExchanger, DownloadError
|
|
20
28
|
from ..enums import OperationMode, VersionMode
|
|
21
29
|
from ..globals import GLOBALS
|
|
@@ -37,7 +45,7 @@ from .rest import upload as rest_upload
|
|
|
37
45
|
class DetectorNode(Node):
|
|
38
46
|
|
|
39
47
|
def __init__(self, name: str, detector: DetectorLogic, uuid: Optional[str] = None, use_backdoor_controls: bool = False) -> None:
|
|
40
|
-
super().__init__(name, uuid, 'detector', False)
|
|
48
|
+
super().__init__(name, uuid=uuid, node_type='detector', needs_login=False, needs_sio=False)
|
|
41
49
|
self.detector_logic = detector
|
|
42
50
|
self.organization = environment_reader.organization()
|
|
43
51
|
self.project = environment_reader.project()
|
|
@@ -64,6 +72,10 @@ class DetectorNode(Node):
|
|
|
64
72
|
self.target_model: Optional[ModelInformation] = None
|
|
65
73
|
self.loop_deployment_target: Optional[ModelInformation] = None
|
|
66
74
|
|
|
75
|
+
self._regular_status_sync_cycles: int = int(os.environ.get('SYNC_CYCLES', '6'))
|
|
76
|
+
"""sync status every 6 cycles (6*10s = 1min)"""
|
|
77
|
+
self._repeat_cycles_to_next_sync: int = 0
|
|
78
|
+
|
|
67
79
|
self.include_router(rest_detect.router, tags=["detect"])
|
|
68
80
|
self.include_router(rest_upload.router, prefix="")
|
|
69
81
|
self.include_router(rest_mode.router, tags=["operation_mode"])
|
|
@@ -74,7 +86,7 @@ class DetectorNode(Node):
|
|
|
74
86
|
if use_backdoor_controls or os.environ.get('USE_BACKDOOR_CONTROLS', '0').lower() in ('1', 'true'):
|
|
75
87
|
self.include_router(backdoor_controls.router)
|
|
76
88
|
|
|
77
|
-
self.
|
|
89
|
+
self._setup_sio_server()
|
|
78
90
|
|
|
79
91
|
def get_about_response(self) -> AboutResponse:
|
|
80
92
|
return AboutResponse(
|
|
@@ -190,13 +202,7 @@ class DetectorNode(Node):
|
|
|
190
202
|
except Exception:
|
|
191
203
|
self.log.exception("error during 'shutdown'")
|
|
192
204
|
|
|
193
|
-
|
|
194
|
-
try:
|
|
195
|
-
await self._check_for_update()
|
|
196
|
-
except Exception:
|
|
197
|
-
self.log.exception("error during '_check_for_update'")
|
|
198
|
-
|
|
199
|
-
def setup_sio_server(self) -> None:
|
|
205
|
+
def _setup_sio_server(self) -> None:
|
|
200
206
|
"""The DetectorNode acts as a SocketIO server. This method sets up the server and defines the event handlers."""
|
|
201
207
|
# pylint: disable=unused-argument
|
|
202
208
|
|
|
@@ -322,96 +328,22 @@ class DetectorNode(Node):
|
|
|
322
328
|
def connect(sid, environ, auth) -> None:
|
|
323
329
|
self.connected_clients.append(sid)
|
|
324
330
|
|
|
325
|
-
|
|
326
|
-
try:
|
|
327
|
-
self.log.debug('Current operation mode is %s', self.operation_mode)
|
|
328
|
-
try:
|
|
329
|
-
await self.sync_status_with_learning_loop()
|
|
330
|
-
except Exception:
|
|
331
|
-
self.log.exception('Sync with learning loop failed (could not check for updates):')
|
|
332
|
-
return
|
|
333
|
-
|
|
334
|
-
if self.operation_mode != OperationMode.Idle:
|
|
335
|
-
self.log.debug('not checking for updates; operation mode is %s', self.operation_mode)
|
|
336
|
-
return
|
|
337
|
-
|
|
338
|
-
self.status.reset_error('update_model')
|
|
339
|
-
if self.target_model is None:
|
|
340
|
-
self.log.debug('not checking for updates; no target model selected')
|
|
341
|
-
return
|
|
342
|
-
|
|
343
|
-
if self.detector_logic.model_info is not None:
|
|
344
|
-
current_version = self.detector_logic.model_info.version
|
|
345
|
-
else:
|
|
346
|
-
current_version = None
|
|
331
|
+
# ================================== Repeat Cycle, sync and model updates ==================================
|
|
347
332
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
try:
|
|
360
|
-
await self.data_exchanger.download_model(target_model_folder,
|
|
361
|
-
Context(organization=self.organization,
|
|
362
|
-
project=self.project),
|
|
363
|
-
self.target_model.id,
|
|
364
|
-
self.detector_logic.model_format)
|
|
365
|
-
self.log.info('Downloaded model %s', self.target_model.version)
|
|
366
|
-
except Exception:
|
|
367
|
-
self.log.exception('Could not download model %s', self.target_model.version)
|
|
368
|
-
shutil.rmtree(target_model_folder, ignore_errors=True)
|
|
369
|
-
return
|
|
370
|
-
try:
|
|
371
|
-
os.unlink(model_symlink)
|
|
372
|
-
os.remove(model_symlink)
|
|
373
|
-
except Exception:
|
|
374
|
-
pass
|
|
375
|
-
os.symlink(target_model_folder, model_symlink)
|
|
376
|
-
self.log.info('Updated symlink for model to %s', os.readlink(model_symlink))
|
|
377
|
-
|
|
378
|
-
try:
|
|
379
|
-
self.detector_logic.load_model_info_and_init_model()
|
|
380
|
-
except NodeNeedsRestartError:
|
|
381
|
-
self.log.error('Node needs restart')
|
|
382
|
-
sys.exit(0)
|
|
383
|
-
except Exception:
|
|
384
|
-
self.log.exception('Could not load model, will retry download on next check')
|
|
385
|
-
shutil.rmtree(target_model_folder, ignore_errors=True)
|
|
386
|
-
return
|
|
387
|
-
try:
|
|
388
|
-
await self.sync_status_with_learning_loop()
|
|
389
|
-
except Exception:
|
|
390
|
-
pass
|
|
391
|
-
# self.reload(reason='new model installed')
|
|
392
|
-
|
|
393
|
-
except Exception as e:
|
|
394
|
-
self.log.exception('check_for_update failed')
|
|
395
|
-
msg = e.cause if isinstance(e, DownloadError) else str(e)
|
|
396
|
-
self.status.set_error('update_model', f'Could not update model: {msg}')
|
|
397
|
-
try:
|
|
398
|
-
await self.sync_status_with_learning_loop()
|
|
399
|
-
except Exception:
|
|
400
|
-
pass
|
|
401
|
-
|
|
402
|
-
async def sync_status_with_learning_loop(self) -> None:
|
|
403
|
-
"""Sync status of the detector with the Learning Loop.
|
|
404
|
-
The Learning Loop will respond with the model info of the deployment target.
|
|
405
|
-
If version_control is set to FollowLoop, the detector will update the target_model.
|
|
406
|
-
Return if the communication was successful.
|
|
407
|
-
|
|
408
|
-
Raises:
|
|
409
|
-
Exception: If the communication with the Learning Loop failed.
|
|
410
|
-
"""
|
|
333
|
+
async def on_repeat(self) -> None:
|
|
334
|
+
"""Implementation of the repeat cycle. This method is called every 10 seconds.
|
|
335
|
+
To avoid too many requests, the status is only synced every 6 cycles (1 minute)."""
|
|
336
|
+
try:
|
|
337
|
+
self._repeat_cycles_to_next_sync -= 1
|
|
338
|
+
if self._repeat_cycles_to_next_sync <= 0:
|
|
339
|
+
self._repeat_cycles_to_next_sync = self._regular_status_sync_cycles
|
|
340
|
+
await self._sync_status_with_loop()
|
|
341
|
+
await self._update_model_if_required()
|
|
342
|
+
except Exception:
|
|
343
|
+
self.log.exception("error during '_check_for_update'")
|
|
411
344
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
raise Exception('Status sync failed: not connected')
|
|
345
|
+
async def _sync_status_with_loop(self) -> None:
|
|
346
|
+
"""Sync status of the detector with the Learning Loop."""
|
|
415
347
|
|
|
416
348
|
if self.detector_logic.model_info is not None:
|
|
417
349
|
current_model = self.detector_logic.model_info.version
|
|
@@ -420,8 +352,8 @@ class DetectorNode(Node):
|
|
|
420
352
|
|
|
421
353
|
target_model_version = self.target_model.version if self.target_model else None
|
|
422
354
|
|
|
423
|
-
status =
|
|
424
|
-
|
|
355
|
+
status = DetectorStatus(
|
|
356
|
+
uuid=self.uuid,
|
|
425
357
|
name=self.name,
|
|
426
358
|
state=self.status.state,
|
|
427
359
|
errors=self.status.errors,
|
|
@@ -432,49 +364,128 @@ class DetectorNode(Node):
|
|
|
432
364
|
model_format=self.detector_logic.model_format,
|
|
433
365
|
)
|
|
434
366
|
|
|
435
|
-
self.log_status_on_change(status.state
|
|
367
|
+
self.log_status_on_change(status.state, status)
|
|
368
|
+
|
|
369
|
+
try:
|
|
370
|
+
response = await self.loop_communicator.post(
|
|
371
|
+
f'/{self.organization}/projects/{self.project}/detectors', json=jsonable_encoder(asdict(status)))
|
|
372
|
+
except Exception:
|
|
373
|
+
self.log.warning('Exception while trying to sync status with loop')
|
|
374
|
+
|
|
375
|
+
if response.status_code != 200:
|
|
376
|
+
self.log.warning('Status update failed: %s', str(response))
|
|
436
377
|
|
|
437
|
-
|
|
378
|
+
async def _update_model_if_required(self) -> None:
|
|
379
|
+
"""Check if a new model is available and update if necessary.
|
|
380
|
+
The Learning Loop will respond with the model info of the deployment target.
|
|
381
|
+
If version_control is set to FollowLoop or the chosen target model is not used,
|
|
382
|
+
the detector will update the target_model."""
|
|
438
383
|
try:
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
self.
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
self.
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
384
|
+
if self.operation_mode != OperationMode.Idle:
|
|
385
|
+
self.log.debug('not checking for updates; operation mode is %s', self.operation_mode)
|
|
386
|
+
return
|
|
387
|
+
|
|
388
|
+
await self._check_for_new_deployment_target()
|
|
389
|
+
|
|
390
|
+
self.status.reset_error('update_model')
|
|
391
|
+
if self.target_model is None:
|
|
392
|
+
self.log.debug('not running any updates; target model is None')
|
|
393
|
+
return
|
|
394
|
+
|
|
395
|
+
current_version = self.detector_logic.model_info.version \
|
|
396
|
+
if self.detector_logic.model_info is not None else None
|
|
397
|
+
|
|
398
|
+
if current_version != self.target_model.version:
|
|
399
|
+
self.log.info('Updating model from %s to %s',
|
|
400
|
+
current_version or "-", self.target_model.version)
|
|
401
|
+
await self._update_model(self.target_model)
|
|
402
|
+
|
|
403
|
+
except Exception as e:
|
|
404
|
+
self.log.exception('check_for_update failed')
|
|
405
|
+
msg = e.cause if isinstance(e, DownloadError) else str(e)
|
|
406
|
+
self.status.set_error('update_model', f'Could not update model: {msg}')
|
|
407
|
+
await self._sync_status_with_loop()
|
|
408
|
+
|
|
409
|
+
async def _check_for_new_deployment_target(self) -> None:
|
|
410
|
+
"""Ask the learning loop for the current deployment target and update self.loop_deployment_target.
|
|
411
|
+
If version_control is set to FollowLoop, also update target_model."""
|
|
412
|
+
try:
|
|
413
|
+
response = await self.loop_communicator.get(
|
|
414
|
+
f'/{self.organization}/projects/{self.project}/deployment/target')
|
|
415
|
+
except Exception:
|
|
416
|
+
self.log.warning('Exception while trying to check for new deployment target')
|
|
417
|
+
return
|
|
418
|
+
|
|
419
|
+
if response.status_code != 200:
|
|
420
|
+
self.log.warning('Failed to check for new deployment target: %s', str(response))
|
|
421
|
+
return
|
|
422
|
+
|
|
423
|
+
response_data = response.json()
|
|
424
|
+
|
|
425
|
+
deployment_target_uuid = response_data['model_uuid']
|
|
426
|
+
deployment_target_version = response_data['version']
|
|
460
427
|
self.loop_deployment_target = ModelInformation(organization=self.organization, project=self.project,
|
|
461
428
|
host="", categories=[],
|
|
462
|
-
id=
|
|
463
|
-
version=
|
|
429
|
+
id=deployment_target_uuid,
|
|
430
|
+
version=deployment_target_version)
|
|
464
431
|
|
|
465
432
|
if (self.version_control == VersionMode.FollowLoop and
|
|
466
433
|
self.target_model != self.loop_deployment_target):
|
|
467
|
-
|
|
434
|
+
previous_version = self.target_model.version if self.target_model else None
|
|
468
435
|
self.target_model = self.loop_deployment_target
|
|
469
|
-
self.log.info('
|
|
470
|
-
|
|
436
|
+
self.log.info('Deployment target changed from %s to %s',
|
|
437
|
+
previous_version, self.target_model.version)
|
|
438
|
+
|
|
439
|
+
async def _update_model(self, target_model: ModelInformation) -> None:
|
|
440
|
+
"""Download and install the target model.
|
|
441
|
+
On failure, the target_model will be set to None which will trigger a retry on the next check."""
|
|
442
|
+
|
|
443
|
+
with step_into(GLOBALS.data_folder):
|
|
444
|
+
target_model_folder = f'models/{target_model.version}'
|
|
445
|
+
if os.path.exists(target_model_folder) and len(os.listdir(target_model_folder)) > 0:
|
|
446
|
+
self.log.info('No need to download model. %s (already exists)', target_model.version)
|
|
447
|
+
else:
|
|
448
|
+
os.makedirs(target_model_folder, exist_ok=True)
|
|
449
|
+
try:
|
|
450
|
+
await self.data_exchanger.download_model(target_model_folder,
|
|
451
|
+
Context(organization=self.organization,
|
|
452
|
+
project=self.project),
|
|
453
|
+
target_model.id, self.detector_logic.model_format)
|
|
454
|
+
self.log.info('Downloaded model %s', target_model.version)
|
|
455
|
+
except Exception:
|
|
456
|
+
self.log.exception('Could not download model %s', target_model.version)
|
|
457
|
+
shutil.rmtree(target_model_folder, ignore_errors=True)
|
|
458
|
+
self.target_model = None
|
|
459
|
+
return
|
|
460
|
+
|
|
461
|
+
model_symlink = 'model'
|
|
462
|
+
try:
|
|
463
|
+
os.unlink(model_symlink)
|
|
464
|
+
os.remove(model_symlink)
|
|
465
|
+
except Exception:
|
|
466
|
+
pass
|
|
467
|
+
os.symlink(target_model_folder, model_symlink)
|
|
468
|
+
self.log.info('Updated symlink for model to %s', os.readlink(model_symlink))
|
|
469
|
+
|
|
470
|
+
try:
|
|
471
|
+
self.detector_logic.load_model_info_and_init_model()
|
|
472
|
+
except NodeNeedsRestartError:
|
|
473
|
+
self.log.error('Node needs restart')
|
|
474
|
+
sys.exit(0)
|
|
475
|
+
except Exception:
|
|
476
|
+
self.log.exception('Could not load model, will retry download on next check')
|
|
477
|
+
shutil.rmtree(target_model_folder, ignore_errors=True)
|
|
478
|
+
self.target_model = None
|
|
479
|
+
return
|
|
480
|
+
|
|
481
|
+
await self._sync_status_with_loop()
|
|
482
|
+
# self.reload(reason='new model installed')
|
|
483
|
+
|
|
484
|
+
# ================================== API Implementations ==================================
|
|
471
485
|
|
|
472
486
|
async def set_operation_mode(self, mode: OperationMode):
|
|
473
487
|
self.operation_mode = mode
|
|
474
|
-
|
|
475
|
-
await self.sync_status_with_learning_loop()
|
|
476
|
-
except Exception as e:
|
|
477
|
-
self.log.warning('Operation mode set to %s, but sync failed: %s', mode, e)
|
|
488
|
+
await self._sync_status_with_loop()
|
|
478
489
|
|
|
479
490
|
def reload(self, reason: str):
|
|
480
491
|
"""provide a cause for the reload"""
|
{learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/detector/outbox.py
RENAMED
|
@@ -189,7 +189,7 @@ class Outbox():
|
|
|
189
189
|
|
|
190
190
|
async def _continuous_upload(self) -> None:
|
|
191
191
|
self.log.info('continuous upload started')
|
|
192
|
-
assert self.shutdown_event is not None
|
|
192
|
+
assert self.shutdown_event is not None, 'shutdown_event is None'
|
|
193
193
|
while not self.shutdown_event.is_set():
|
|
194
194
|
await self.upload()
|
|
195
195
|
await asyncio.sleep(self.UPLOAD_INTERVAL_S)
|
|
@@ -287,7 +287,7 @@ class Outbox():
|
|
|
287
287
|
return True
|
|
288
288
|
|
|
289
289
|
try:
|
|
290
|
-
assert self.shutdown_event is not None
|
|
290
|
+
assert self.shutdown_event is not None, 'shutdown_event is None'
|
|
291
291
|
self.shutdown_event.set()
|
|
292
292
|
await asyncio.wait_for(self.upload_task, timeout=self.UPLOAD_TIMEOUT_S + 1)
|
|
293
293
|
except asyncio.TimeoutError:
|
{learning_loop_node-0.15.0 → learning_loop_node-0.16.1}/learning_loop_node/loop_communication.py
RENAMED
|
@@ -8,7 +8,8 @@ from httpx import Cookies, Timeout
|
|
|
8
8
|
|
|
9
9
|
from .helpers import environment_reader
|
|
10
10
|
|
|
11
|
-
logging.
|
|
11
|
+
logger = logging.getLogger('loop_communication')
|
|
12
|
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
12
13
|
|
|
13
14
|
SLEEP_TIME_ON_429 = 5
|
|
14
15
|
MAX_RETRIES_ON_429 = 20
|
|
@@ -37,9 +38,9 @@ class LoopCommunicator():
|
|
|
37
38
|
host: str = environment_reader.host(default='learning-loop.ai')
|
|
38
39
|
self.ssl_cert_path = environment_reader.ssl_certificate_path()
|
|
39
40
|
if self.ssl_cert_path:
|
|
40
|
-
|
|
41
|
+
logger.info('Using SSL certificate at %s', self.ssl_cert_path)
|
|
41
42
|
else:
|
|
42
|
-
|
|
43
|
+
logger.info('No SSL certificate path set')
|
|
43
44
|
self.host: str = host
|
|
44
45
|
self.username: str = environment_reader.username()
|
|
45
46
|
self.password: str = environment_reader.password()
|
|
@@ -52,7 +53,7 @@ class LoopCommunicator():
|
|
|
52
53
|
else:
|
|
53
54
|
self.async_client = httpx.AsyncClient(base_url=self.base_url, timeout=Timeout(60.0))
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
logger.info('Loop interface initialized with base_url: %s / user: %s', self.base_url, self.username)
|
|
56
57
|
|
|
57
58
|
def websocket_url(self) -> str:
|
|
58
59
|
return f'ws{"s" if "learning-loop.ai" in self.host else ""}://' + self.host
|
|
@@ -65,7 +66,7 @@ class LoopCommunicator():
|
|
|
65
66
|
self.async_client.cookies.clear()
|
|
66
67
|
response = await self.async_client.post('/api/login', data={'username': self.username, 'password': self.password})
|
|
67
68
|
if response.status_code != 200:
|
|
68
|
-
|
|
69
|
+
logger.info('Login failed with response: %s', response)
|
|
69
70
|
raise LoopCommunicationException('Login failed with response: ' + str(response))
|
|
70
71
|
self.async_client.cookies.update(response.cookies)
|
|
71
72
|
|
|
@@ -74,7 +75,7 @@ class LoopCommunicator():
|
|
|
74
75
|
|
|
75
76
|
response = await self.async_client.post('/api/logout')
|
|
76
77
|
if response.status_code != 200:
|
|
77
|
-
|
|
78
|
+
logger.info('Logout failed with response: %s', response)
|
|
78
79
|
raise LoopCommunicationException('Logout failed with response: ' + str(response))
|
|
79
80
|
self.async_client.cookies.clear()
|
|
80
81
|
|
|
@@ -90,12 +91,12 @@ class LoopCommunicator():
|
|
|
90
91
|
start_time = time.time()
|
|
91
92
|
while True:
|
|
92
93
|
try:
|
|
93
|
-
|
|
94
|
+
logger.info('Checking if backend is ready')
|
|
94
95
|
response = await self.get('/status', requires_login=False)
|
|
95
96
|
if response.status_code == 200:
|
|
96
97
|
return True
|
|
97
98
|
except Exception:
|
|
98
|
-
|
|
99
|
+
logger.info('backend not ready yet.')
|
|
99
100
|
if timeout is not None and time.time() + 10 - start_time > timeout:
|
|
100
101
|
raise TimeoutError('Backend not ready within timeout')
|
|
101
102
|
await asyncio.sleep(10)
|