learning-loop-node 0.10.13__py3-none-any.whl → 0.10.15__py3-none-any.whl

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 (34) hide show
  1. learning_loop_node/annotation/annotator_node.py +11 -10
  2. learning_loop_node/data_classes/detections.py +34 -26
  3. learning_loop_node/data_classes/general.py +27 -17
  4. learning_loop_node/data_exchanger.py +6 -5
  5. learning_loop_node/detector/detector_logic.py +3 -3
  6. learning_loop_node/detector/detector_node.py +21 -15
  7. learning_loop_node/detector/rest/about.py +34 -10
  8. learning_loop_node/detector/rest/backdoor_controls.py +9 -26
  9. learning_loop_node/detector/rest/detect.py +17 -16
  10. learning_loop_node/detector/rest/model_version_control.py +30 -13
  11. learning_loop_node/detector/rest/operation_mode.py +11 -5
  12. learning_loop_node/detector/rest/outbox_mode.py +7 -1
  13. learning_loop_node/loop_communication.py +7 -4
  14. learning_loop_node/node.py +100 -47
  15. learning_loop_node/rest.py +25 -2
  16. learning_loop_node/tests/detector/conftest.py +4 -4
  17. learning_loop_node/tests/detector/test_client_communication.py +21 -19
  18. learning_loop_node/tests/detector/test_detector_node.py +3 -3
  19. learning_loop_node/tests/trainer/conftest.py +4 -4
  20. learning_loop_node/tests/trainer/states/test_state_detecting.py +8 -9
  21. learning_loop_node/tests/trainer/states/test_state_download_train_model.py +8 -8
  22. learning_loop_node/tests/trainer/states/test_state_prepare.py +6 -7
  23. learning_loop_node/tests/trainer/states/test_state_sync_confusion_matrix.py +21 -18
  24. learning_loop_node/tests/trainer/states/test_state_train.py +6 -8
  25. learning_loop_node/tests/trainer/states/test_state_upload_detections.py +7 -9
  26. learning_loop_node/tests/trainer/states/test_state_upload_model.py +7 -8
  27. learning_loop_node/tests/trainer/test_errors.py +2 -2
  28. learning_loop_node/trainer/exceptions.py +11 -1
  29. learning_loop_node/trainer/rest/backdoor_controls.py +19 -40
  30. learning_loop_node/trainer/trainer_logic_generic.py +11 -5
  31. learning_loop_node/trainer/trainer_node.py +4 -3
  32. {learning_loop_node-0.10.13.dist-info → learning_loop_node-0.10.15.dist-info}/METADATA +1 -1
  33. {learning_loop_node-0.10.13.dist-info → learning_loop_node-0.10.15.dist-info}/RECORD +34 -34
  34. {learning_loop_node-0.10.13.dist-info → learning_loop_node-0.10.15.dist-info}/WHEEL +0 -0
@@ -11,11 +11,9 @@ from ..testing_trainer_logic import TestingTrainerLogic
11
11
 
12
12
  # pylint: disable=protected-access
13
13
 
14
- error_key = 'sync_confusion_matrix'
15
14
 
16
-
17
- def trainer_has_error(trainer: TrainerLogic):
18
- return trainer.errors.has_error_for(error_key)
15
+ def trainer_has_sync_confusion_matrix_error(trainer: TrainerLogic):
16
+ return trainer.errors.has_error_for('sync_confusion_matrix')
19
17
 
20
18
 
21
19
  async def test_nothing_to_sync(test_initialized_trainer: TestingTrainerLogic):
@@ -26,10 +24,10 @@ async def test_nothing_to_sync(test_initialized_trainer: TestingTrainerLogic):
26
24
  create_active_training_file(trainer, training_state=TrainerState.TrainingFinished)
27
25
  trainer._init_from_last_training()
28
26
 
29
- _ = asyncio.get_running_loop().create_task(trainer._run())
27
+ trainer._begin_training_task()
30
28
 
31
29
  await assert_training_state(trainer.training, TrainerState.ConfusionMatrixSynced, timeout=1, interval=0.001)
32
- assert trainer_has_error(trainer) is False
30
+ assert trainer_has_sync_confusion_matrix_error(trainer) is False
33
31
  assert trainer.training.training_state == TrainerState.ConfusionMatrixSynced
34
32
  assert trainer.node.last_training_io.load() == trainer.training
35
33
 
@@ -38,16 +36,16 @@ async def test_unsynced_model_available__sync_successful(test_initialized_traine
38
36
  trainer = test_initialized_trainer_node.trainer_logic
39
37
  assert isinstance(trainer, TestingTrainerLogic)
40
38
 
41
- await mock_socket_io_call(mocker, test_initialized_trainer_node, {'success': True})
39
+ await mock_socket_io_call(mocker, test_initialized_trainer_node, return_value={'success': True})
42
40
  create_active_training_file(trainer, training_state=TrainerState.TrainingFinished)
43
41
 
44
42
  trainer._init_from_last_training()
45
43
  trainer.has_new_model = True
46
44
 
47
- _ = asyncio.get_running_loop().create_task(trainer._run())
45
+ trainer._begin_training_task()
48
46
  await assert_training_state(trainer.training, TrainerState.ConfusionMatrixSynced, timeout=1, interval=0.001)
49
47
 
50
- assert trainer_has_error(trainer) is False
48
+ assert trainer_has_sync_confusion_matrix_error(trainer) is False
51
49
  # assert trainer.training.training_state == TrainerState.ConfusionMatrixSynced
52
50
  assert trainer.node.last_training_io.load() == trainer.training
53
51
 
@@ -56,17 +54,19 @@ async def test_unsynced_model_available__sio_not_connected(test_initialized_trai
56
54
  trainer = test_initialized_trainer_node.trainer_logic
57
55
  assert isinstance(trainer, TestingTrainerLogic)
58
56
 
57
+ await test_initialized_trainer_node.sio_client.disconnect()
58
+ test_initialized_trainer_node.set_skip_repeat_loop(True)
59
59
  create_active_training_file(trainer, training_state=TrainerState.TrainingFinished)
60
60
 
61
61
  assert test_initialized_trainer_node.sio_client.connected is False
62
62
  trainer.has_new_model = True
63
63
 
64
- _ = asyncio.get_running_loop().create_task(trainer._run())
64
+ trainer._begin_training_task()
65
65
 
66
- await assert_training_state(trainer.training, 'confusion_matrix_syncing', timeout=1, interval=0.001)
67
- await assert_training_state(trainer.training, TrainerState.TrainingFinished, timeout=1, interval=0.001)
66
+ await assert_training_state(trainer.training, TrainerState.ConfusionMatrixSyncing, timeout=1, interval=0.001)
67
+ await assert_training_state(trainer.training, TrainerState.TrainingFinished, timeout=10, interval=0.001)
68
68
 
69
- assert trainer_has_error(trainer)
69
+ assert trainer_has_sync_confusion_matrix_error(trainer) # Due to sio not being connected, the request will fail
70
70
  assert trainer.training.training_state == TrainerState.TrainingFinished
71
71
  assert trainer.node.last_training_io.load() == trainer.training
72
72
 
@@ -75,17 +75,17 @@ async def test_unsynced_model_available__request_is_not_successful(test_initiali
75
75
  trainer = test_initialized_trainer_node.trainer_logic
76
76
  assert isinstance(trainer, TestingTrainerLogic)
77
77
 
78
- await mock_socket_io_call(mocker, test_initialized_trainer_node, {'success': False})
78
+ await mock_socket_io_call(mocker, test_initialized_trainer_node, return_value={'success': False})
79
79
 
80
80
  create_active_training_file(trainer, training_state=TrainerState.TrainingFinished)
81
81
 
82
82
  trainer.has_new_model = True
83
- _ = asyncio.get_running_loop().create_task(trainer._run())
83
+ trainer._begin_training_task()
84
84
 
85
- await assert_training_state(trainer.training, 'confusion_matrix_syncing', timeout=1, interval=0.001)
86
- await assert_training_state(trainer.training, TrainerState.TrainingFinished, timeout=1, interval=0.001)
85
+ await assert_training_state(trainer.training, TrainerState.ConfusionMatrixSyncing, timeout=1, interval=0.001)
86
+ await assert_training_state(trainer.training, TrainerState.TrainingFinished, timeout=10, interval=0.001)
87
87
 
88
- assert trainer_has_error(trainer)
88
+ assert trainer_has_sync_confusion_matrix_error(trainer) # Due to sio call failure, the error will be set
89
89
  assert trainer.training.training_state == TrainerState.TrainingFinished
90
90
  assert trainer.node.last_training_io.load() == trainer.training
91
91
 
@@ -100,6 +100,9 @@ async def test_basic_mock(test_initialized_trainer_node: TrainerNode, mocker: Mo
100
100
 
101
101
 
102
102
  async def mock_socket_io_call(mocker, trainer_node: TrainerNode, return_value):
103
+ '''
104
+ Patch the socketio call function to always return the given return_value
105
+ '''
103
106
  for _ in range(10):
104
107
  if trainer_node.sio_client is None:
105
108
  await asyncio.sleep(0.1)
@@ -14,10 +14,10 @@ async def test_successful_training(test_initialized_trainer: TestingTrainerLogic
14
14
  create_active_training_file(trainer, training_state=TrainerState.TrainModelDownloaded)
15
15
  trainer._init_from_last_training()
16
16
 
17
- _ = asyncio.get_running_loop().create_task(trainer._run())
17
+ trainer._begin_training_task()
18
18
 
19
19
  await condition(lambda: trainer._executor and trainer._executor.is_running(), timeout=1, interval=0.01)
20
- await assert_training_state(trainer.training, TrainerState.TrainingRunning, timeout=1, interval=0.01)
20
+ await assert_training_state(trainer.training, TrainerState.TrainingRunning, timeout=10, interval=0.01)
21
21
  assert trainer.start_training_task is not None
22
22
 
23
23
  assert trainer._executor is not None
@@ -34,16 +34,15 @@ async def test_stop_running_training(test_initialized_trainer: TestingTrainerLog
34
34
  create_active_training_file(trainer, training_state=TrainerState.TrainModelDownloaded)
35
35
  trainer._init_from_last_training()
36
36
 
37
- _ = asyncio.get_running_loop().create_task(trainer._run())
37
+ trainer._begin_training_task()
38
38
 
39
39
  await condition(lambda: trainer._executor and trainer._executor.is_running(), timeout=1, interval=0.01)
40
- await assert_training_state(trainer.training, TrainerState.TrainingRunning, timeout=1, interval=0.01)
40
+ await assert_training_state(trainer.training, TrainerState.TrainingRunning, timeout=10, interval=0.01)
41
41
  assert trainer.start_training_task is not None
42
42
 
43
43
  await trainer.stop()
44
44
  await assert_training_state(trainer.training, TrainerState.TrainingFinished, timeout=2, interval=0.01)
45
45
 
46
- assert trainer.training.training_state == TrainerState.TrainingFinished
47
46
  assert trainer.node.last_training_io.load() == trainer.training
48
47
 
49
48
 
@@ -55,15 +54,14 @@ async def test_training_can_maybe_resumed(test_initialized_trainer: TestingTrain
55
54
  trainer._init_from_last_training()
56
55
  trainer._can_resume_flag = True
57
56
 
58
- _ = asyncio.get_running_loop().create_task(trainer._run())
57
+ trainer._begin_training_task()
59
58
 
60
59
  await condition(lambda: trainer._executor and trainer._executor.is_running(), timeout=1, interval=0.01)
61
- await assert_training_state(trainer.training, TrainerState.TrainingRunning, timeout=1, interval=0.001)
60
+ await assert_training_state(trainer.training, TrainerState.TrainingRunning, timeout=10, interval=0.001)
62
61
  assert trainer.start_training_task is not None
63
62
 
64
63
  assert trainer._executor is not None
65
64
  await trainer._executor.stop_and_wait() # NOTE normally a training terminates itself e.g
66
65
  await assert_training_state(trainer.training, TrainerState.TrainingFinished, timeout=1, interval=0.001)
67
66
 
68
- assert trainer.training.training_state == TrainerState.TrainingFinished
69
67
  assert trainer.node.last_training_io.load() == trainer.training
@@ -11,11 +11,10 @@ from ..state_helper import assert_training_state, create_active_training_file
11
11
  from ..testing_trainer_logic import TestingTrainerLogic
12
12
 
13
13
  # pylint: disable=protected-access
14
- error_key = 'upload_detections'
15
14
 
16
15
 
17
- def trainer_has_error(trainer: TrainerLogic):
18
- return trainer.errors.has_error_for(error_key)
16
+ def trainer_has_upload_detections_error(trainer: TrainerLogic):
17
+ return trainer.errors.has_error_for('upload_detections')
19
18
 
20
19
 
21
20
  async def create_valid_detection_file(trainer: TrainerLogic, number_of_entries: int = 1, file_index: int = 0):
@@ -125,12 +124,11 @@ async def test_bad_status_from_LearningLoop(test_initialized_trainer: TestingTra
125
124
  trainer._init_from_last_training()
126
125
  trainer.active_training_io.save_detections([get_dummy_detections()])
127
126
 
128
- _ = asyncio.get_running_loop().create_task(trainer._run())
127
+ trainer._begin_training_task()
129
128
  await assert_training_state(trainer.training, TrainerState.DetectionUploading, timeout=1, interval=0.001)
130
- await assert_training_state(trainer.training, TrainerState.Detected, timeout=1, interval=0.001)
129
+ await assert_training_state(trainer.training, TrainerState.Detected, timeout=10, interval=0.001)
131
130
 
132
- assert trainer_has_error(trainer)
133
- assert trainer.training.training_state == TrainerState.Detected
131
+ assert trainer_has_upload_detections_error(trainer)
134
132
  assert trainer.node.last_training_io.load() == trainer.training
135
133
 
136
134
 
@@ -143,7 +141,7 @@ async def test_go_to_cleanup_if_no_detections_exist(test_initialized_trainer: Te
143
141
  create_active_training_file(trainer, training_state=TrainerState.Detected)
144
142
  trainer._init_from_last_training()
145
143
 
146
- _ = asyncio.get_running_loop().create_task(trainer._run())
144
+ trainer._begin_training_task()
147
145
  await assert_training_state(trainer.training, TrainerState.ReadyForCleanup, timeout=1, interval=0.001)
148
146
 
149
147
 
@@ -154,7 +152,7 @@ async def test_abort_uploading(test_initialized_trainer: TestingTrainerLogic):
154
152
  trainer._init_from_last_training()
155
153
  await create_valid_detection_file(trainer)
156
154
 
157
- _ = asyncio.get_running_loop().create_task(trainer._run())
155
+ trainer._begin_training_task()
158
156
 
159
157
  await assert_training_state(trainer.training, TrainerState.DetectionUploading, timeout=1, interval=0.001)
160
158
 
@@ -8,11 +8,10 @@ from ..state_helper import assert_training_state, create_active_training_file
8
8
  from ..testing_trainer_logic import TestingTrainerLogic
9
9
 
10
10
  # pylint: disable=protected-access
11
- error_key = 'upload_model'
12
11
 
13
12
 
14
- def trainer_has_error(trainer: TrainerLogic):
15
- return trainer.errors.has_error_for(error_key)
13
+ def trainer_has_upload_model_error(trainer: TrainerLogic):
14
+ return trainer.errors.has_error_for('upload_model')
16
15
 
17
16
 
18
17
  async def test_successful_upload(mocker: MockerFixture, test_initialized_trainer: TestingTrainerLogic):
@@ -28,7 +27,7 @@ async def test_successful_upload(mocker: MockerFixture, test_initialized_trainer
28
27
  await assert_training_state(trainer.training, TrainerState.TrainModelUploading, timeout=1, interval=0.001)
29
28
  await train_task
30
29
 
31
- assert trainer_has_error(trainer) is False
30
+ assert trainer_has_upload_model_error(trainer) is False
32
31
  assert trainer.training.training_state == TrainerState.TrainModelUploaded
33
32
  assert trainer.training.model_uuid_for_detecting is not None
34
33
  assert trainer.node.last_training_io.load() == trainer.training
@@ -40,7 +39,7 @@ async def test_abort_upload_model(test_initialized_trainer: TestingTrainerLogic)
40
39
  create_active_training_file(trainer, training_state=TrainerState.ConfusionMatrixSynced)
41
40
  trainer._init_from_last_training()
42
41
 
43
- _ = asyncio.get_running_loop().create_task(trainer._run())
42
+ trainer._begin_training_task()
44
43
 
45
44
  await assert_training_state(trainer.training, TrainerState.TrainModelUploading, timeout=1, interval=0.001)
46
45
 
@@ -60,13 +59,13 @@ async def test_bad_server_response_content(test_initialized_trainer: TestingTrai
60
59
  create_active_training_file(trainer, training_state=TrainerState.ConfusionMatrixSynced)
61
60
  trainer._init_from_last_training()
62
61
 
63
- _ = asyncio.get_running_loop().create_task(trainer._run())
62
+ trainer._begin_training_task()
64
63
 
65
64
  await assert_training_state(trainer.training, TrainerState.TrainModelUploading, timeout=1, interval=0.001)
66
65
  # TODO goes to finished because of the error
67
- await assert_training_state(trainer.training, TrainerState.ReadyForCleanup, timeout=2, interval=0.001)
66
+ await assert_training_state(trainer.training, TrainerState.ReadyForCleanup, timeout=10, interval=0.001)
68
67
 
69
- assert trainer_has_error(trainer)
68
+ assert trainer_has_upload_model_error(trainer)
70
69
  assert trainer.training.training_state == TrainerState.ReadyForCleanup
71
70
  assert trainer.training.model_uuid_for_detecting is None
72
71
  assert trainer.node.last_training_io.load() == trainer.training
@@ -14,7 +14,7 @@ async def test_training_process_is_stopped_when_trainer_reports_error(test_initi
14
14
  trainer = test_initialized_trainer
15
15
  create_active_training_file(trainer, training_state=TrainerState.TrainModelDownloaded)
16
16
  trainer._init_from_last_training()
17
- _ = asyncio.get_running_loop().create_task(trainer._run())
17
+ trainer._begin_training_task()
18
18
 
19
19
  await assert_training_state(trainer.training, TrainerState.TrainingRunning, timeout=1, interval=0.001)
20
20
  trainer.error_msg = 'some_error'
@@ -26,7 +26,7 @@ async def test_log_can_provide_only_data_for_current_run(test_initialized_traine
26
26
  trainer = test_initialized_trainer
27
27
  create_active_training_file(trainer, training_state=TrainerState.TrainModelDownloaded)
28
28
  trainer._init_from_last_training()
29
- _ = asyncio.get_running_loop().create_task(trainer._run())
29
+ trainer._begin_training_task()
30
30
 
31
31
  await assert_training_state(trainer.training, TrainerState.TrainingRunning, timeout=1, interval=0.001)
32
32
  await asyncio.sleep(0.1) # give tests a bit time to to check for the state
@@ -1,2 +1,12 @@
1
1
  class CriticalError(Exception):
2
- pass
2
+ '''
3
+ CriticalError is raised when the training cannot be continued.
4
+ In this case the trainer jumps to the TrainerState.ReadyForCleanup and tries to upload the latest model.
5
+ '''
6
+
7
+
8
+ class NodeNeedsRestartError(Exception):
9
+ '''
10
+ NodeNeedsRestartError is raised when the node needs to be restarted.
11
+ This is e.g. the case when the GPU is not available anymore.
12
+ '''
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Dict
7
7
 
8
8
  from fastapi import APIRouter, HTTPException, Request
9
9
 
10
- from ...data_classes import ErrorConfiguration, NodeState
10
+ from ...data_classes import ErrorConfiguration
11
11
  from ..trainer_logic import TrainerLogic
12
12
 
13
13
  if TYPE_CHECKING:
@@ -16,32 +16,10 @@ if TYPE_CHECKING:
16
16
  router = APIRouter()
17
17
 
18
18
 
19
- @router.put("/socketio")
20
- async def switch_socketio(request: Request):
21
- '''
22
- Example Usage
23
-
24
- curl -X PUT -d "on" http://localhost:8001/socketio
25
- '''
26
- state = str(await request.body(), 'utf-8')
27
- await _switch_socketio(state, request.app)
28
-
29
-
30
- async def _switch_socketio(state: str, trainer_node: TrainerNode):
31
- if state == 'off':
32
- if trainer_node.status.state != NodeState.Offline:
33
- logging.debug('turning socketio off')
34
- await trainer_node.sio_client.disconnect() # pylint: disable=protected-access
35
- if state == 'on':
36
- if trainer_node.status.state == NodeState.Offline:
37
- logging.debug('turning socketio on')
38
- await trainer_node.connect_sio()
39
-
40
-
41
19
  @router.put("/provide_new_model")
42
20
  async def provide_new_model(request: Request):
43
21
  value = str(await request.body(), 'utf-8')
44
- trainer_node = trainer_node_from_request(request)
22
+ trainer_node = request.app
45
23
  # trainer_logic is MockTrainerLogic which has a property provide_new_model
46
24
  assert hasattr(trainer_node.trainer_logic,
47
25
  'provide_new_model'), 'trainer_logic does not have property provide_new_model'
@@ -56,17 +34,22 @@ async def provide_new_model(request: Request):
56
34
 
57
35
  @router.post("/reset")
58
36
  async def reset(request: Request):
59
- trainer_node = trainer_node_from_request(request)
60
- await _switch_socketio('on', trainer_node)
37
+ logging.info('BC: reset')
38
+ trainer_node: 'TrainerNode' = request.app
39
+ async with trainer_node.repeat_loop_lock:
61
40
 
62
- await trainer_node.trainer_logic.stop() # NOTE first stop may only kill running training process
63
- await trainer_node.trainer_logic.stop()
41
+ await trainer_node.trainer_logic.stop() # NOTE first stop may only kill running training process
42
+ await trainer_node.trainer_logic.stop()
43
+ trainer_node.last_training_io.delete()
44
+ trainer_node.status.reset_all_errors()
64
45
 
65
- trainer_node.last_training_io.delete()
46
+ try:
47
+ await trainer_node.reconnect_to_loop()
48
+ except Exception:
49
+ logging.exception('Could not reset sio connection to loop')
66
50
 
67
- trainer_node.status.reset_all_errors()
68
- logging.info('training should be killed, sending new state to LearningLoop')
69
- await trainer_node.send_status()
51
+ logging.info('training should be killed, sending new state to LearningLoop')
52
+ await trainer_node.send_status()
70
53
 
71
54
 
72
55
  @router.put("/error_configuration")
@@ -82,7 +65,7 @@ def set_error_configuration(msg: Dict, request: Request):
82
65
  save_model=msg.get('save_model', None), )
83
66
 
84
67
  logging.info(f'setting error configuration to: {asdict(error_configuration)}')
85
- trainer_logic = trainer_node_from_request(request).trainer_logic
68
+ trainer_logic = request.app.trainer_logic
86
69
 
87
70
  # NOTE: trainer_logic is MockTrainerLogic which has a property error_configuration
88
71
  assert hasattr(trainer_logic, 'error_configuration'), 'trainer_logic does not have property error_configuration'
@@ -92,7 +75,7 @@ def set_error_configuration(msg: Dict, request: Request):
92
75
  @router.post("/steps")
93
76
  async def add_steps(request: Request):
94
77
  logging.warning('Steps was called')
95
- trainer_node = trainer_node_from_request(request)
78
+ trainer_node = request.app
96
79
  trainer_logic = trainer_node.trainer_logic # NOTE: is MockTrainerLogic which has 'provide_new_model' and 'current_iteration'
97
80
 
98
81
  assert isinstance(trainer_logic, TrainerLogic), 'trainer_logic is not TrainerLogic'
@@ -123,7 +106,7 @@ async def add_steps(request: Request):
123
106
  async def kill_process(request: Request):
124
107
 
125
108
  # pylint: disable=protected-access
126
- trainer_node = trainer_node_from_request(request)
109
+ trainer_node = request.app
127
110
  trainer_logic = trainer_node.trainer_logic
128
111
  assert isinstance(trainer_logic, TrainerLogic), 'trainer_logic is not TrainerLogic'
129
112
  if not trainer_logic._executor or not trainer_logic._executor.is_running():
@@ -133,9 +116,5 @@ async def kill_process(request: Request):
133
116
 
134
117
  @router.post("/force_status_update")
135
118
  async def force_status_update(request: Request):
136
- trainer_node = trainer_node_from_request(request)
119
+ trainer_node = request.app
137
120
  await trainer_node.send_status()
138
-
139
-
140
- def trainer_node_from_request(request: Request) -> TrainerNode:
141
- return request.app
@@ -14,7 +14,7 @@ from ..data_classes import (Context, Errors, Hyperparameter, PretrainedModel, Tr
14
14
  TrainingOut, TrainingStateData)
15
15
  from ..helpers.misc import create_project_folder, delete_all_training_folders, generate_training, is_valid_uuid4
16
16
  from .downloader import TrainingsDownloader
17
- from .exceptions import CriticalError
17
+ from .exceptions import CriticalError, NodeNeedsRestartError
18
18
  from .io_helpers import ActiveTrainingIO, EnvironmentVars, LastTrainingIO
19
19
 
20
20
  if TYPE_CHECKING:
@@ -179,7 +179,7 @@ class TrainerLogicGeneric(ABC):
179
179
  if not self.training_active and self.last_training_io.exists():
180
180
  self._init_from_last_training()
181
181
  logger.info('found incomplete training, continuing now.')
182
- asyncio.get_event_loop().create_task(self._run())
182
+ self._begin_training_task()
183
183
  return True
184
184
  return False
185
185
 
@@ -195,7 +195,11 @@ class TrainerLogicGeneric(ABC):
195
195
  """Called on `begin_training` event from the Learning Loop.
196
196
  """
197
197
  self._init_new_training(Context(organization=organization, project=project), details)
198
- asyncio.get_event_loop().create_task(self._run())
198
+ self._begin_training_task()
199
+
200
+ def _begin_training_task(self) -> None:
201
+ # NOTE: Task object is used to potentially cancel the task
202
+ self.training_task = asyncio.get_event_loop().create_task(self._run())
199
203
 
200
204
  def _init_new_training(self, context: Context, details: Dict) -> None:
201
205
  """Called on `begin_training` event from the Learning Loop.
@@ -218,8 +222,7 @@ class TrainerLogicGeneric(ABC):
218
222
  """
219
223
  self.errors.reset_all()
220
224
  try:
221
- self.training_task = asyncio.get_running_loop().create_task(self._training_loop())
222
- await self.training_task # NOTE: Task object is used to potentially cancel the task
225
+ await self._training_loop()
223
226
  except asyncio.CancelledError:
224
227
  if not self.shutdown_event.is_set():
225
228
  logger.info('CancelledError in _run - training task was cancelled but not by shutdown event')
@@ -291,6 +294,9 @@ class TrainerLogicGeneric(ABC):
291
294
  logger.error('CriticalError in %s - Exception: %s', state_during, e)
292
295
  self.errors.set(error_key, str(e))
293
296
  self.training.training_state = TrainerState.ReadyForCleanup
297
+ except NodeNeedsRestartError:
298
+ logger.error('Node Restart Requested')
299
+ sys.exit(0)
294
300
  except Exception as e:
295
301
  self.errors.set(error_key, str(e))
296
302
  logger.exception('Error in %s - Exception: %s', state_during, e)
@@ -77,7 +77,7 @@ class TrainerNode(Node):
77
77
 
78
78
  async def send_status(self):
79
79
  if not self.sio_client.connected:
80
- self.log.warning('cannot send status - not connected to the Learning Loop')
80
+ self.log.debug('cannot send status - not connected to the Learning Loop')
81
81
  return
82
82
 
83
83
  status = TrainingStatus(id=self.uuid,
@@ -98,10 +98,11 @@ class TrainerNode(Node):
98
98
  status.errors = self.trainer_logic.errors.errors
99
99
  status.context = self.trainer_logic.training_context
100
100
 
101
- self.log.info(f'sending status: {status.short_str()}')
101
+ self.log.debug('sending status: %s', status.short_str())
102
102
  result = await self.sio_client.call('update_trainer', jsonable_encoder(asdict(status)), timeout=30)
103
103
  if isinstance(result, Dict) and not result['success']:
104
- self.log.error(f'Error when sending status update: Response from loop was:\n {result}')
104
+ self.socket_connection_broken = True
105
+ self.log.error('Error when sending status update: Response from loop was:\n %s', result)
105
106
 
106
107
  def check_idle_timeout(self):
107
108
  if not self.idle_timeout:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: learning-loop-node
3
- Version: 0.10.13
3
+ Version: 0.10.15
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
@@ -1,28 +1,28 @@
1
1
  learning_loop_node/__init__.py,sha256=onN5s8-x_xBsCM6NLmJO0Ym1sJHeCFaGw8qb0oQZmz8,364
2
2
  learning_loop_node/annotation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  learning_loop_node/annotation/annotator_logic.py,sha256=BTaopkJZkIf1CI5lfsVKsxbxoUIbDJrevavuQUT5e_c,1000
4
- learning_loop_node/annotation/annotator_node.py,sha256=wk11CQtM3A0Dr7efCn_Mw2X7ql5xn2sgEJzrIeSBC6Q,4043
4
+ learning_loop_node/annotation/annotator_node.py,sha256=UrJ8MpZ44UhsjmVuSHr2BhHyLC-kIMDi3IuBBMKzN1g,4117
5
5
  learning_loop_node/data_classes/__init__.py,sha256=wCX88lDgbb8V-gtVCVe9i-NvvZuMe5FX7eD_UJgYYXw,1305
6
6
  learning_loop_node/data_classes/annotations.py,sha256=iInU0Nuy_oYT_sj4k_n-W0UShCBI2cHQYrt8imymbtM,1211
7
- learning_loop_node/data_classes/detections.py,sha256=iFSrnbvYvwhL8k9niz1BACn2QJV4RyW0DKezpmlUn9M,4398
8
- learning_loop_node/data_classes/general.py,sha256=Bd0ngYhYvS_9OYOO6lAKEnDzLuSdPmR4I2YV-0DRsxs,4694
7
+ learning_loop_node/data_classes/detections.py,sha256=hifsGz2LbmeKLZdHxG7cnlOYNEqDmtJd2gxhyU-Xjjs,5811
8
+ learning_loop_node/data_classes/general.py,sha256=usXokcTOVqTuaKJtBf0ffFWfzZhMrQtF7puKfwi6A5k,6195
9
9
  learning_loop_node/data_classes/socket_response.py,sha256=tIdt-oYf6ULoJIDYQCecNM9OtWR6_wJ9tL0Ksu83Vko,655
10
10
  learning_loop_node/data_classes/training.py,sha256=hnMHZMk-WNRERyo7U97qL09v1tIdhnzPfTH-JgifLwU,6164
11
- learning_loop_node/data_exchanger.py,sha256=U_MrBKSq1MbBwBmjrjxoIo_7xV4Lcwtk6uZDIgmhT_4,8914
11
+ learning_loop_node/data_exchanger.py,sha256=mwZvJf8L1-r6Wi2ZvDfFf4kFjkqxsThXj7AGLUANGlU,8979
12
12
  learning_loop_node/detector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- learning_loop_node/detector/detector_logic.py,sha256=Xlwc5yRUSe62gqcxdeLH7KXMujaW3JrCjfZo4iCWdHI,2087
14
- learning_loop_node/detector/detector_node.py,sha256=xuNwXwJYdWZhe3IzP3oz8WUhREXF8k3XI97LvvZqFLQ,19181
13
+ learning_loop_node/detector/detector_logic.py,sha256=IG1s9RF_cCBcNQ8WW1rAS37QKdGzlVoVkuO_CrLGvYs,2084
14
+ learning_loop_node/detector/detector_node.py,sha256=TeChzkpVVmEiZbnfWmtkjuUXNwCZvIWfingmcuAz2cs,19567
15
15
  learning_loop_node/detector/inbox_filter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  learning_loop_node/detector/inbox_filter/cam_observation_history.py,sha256=TD346I9ymtIP0_CJXCIKMRuiXbfVVanXNu_iHAwDd7Q,3318
17
17
  learning_loop_node/detector/inbox_filter/relevance_filter.py,sha256=7_-x8D8Zf6KJeJXmiC2VrRHU8Ig_R98uhdXVwwX0N4M,1240
18
18
  learning_loop_node/detector/outbox.py,sha256=HrYeS6XJLC-1kqq2hDufxXLRmOYGiBlz-m9B6HG5Ie8,8227
19
19
  learning_loop_node/detector/rest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- learning_loop_node/detector/rest/about.py,sha256=VVICeSHONk0VneUvbkOYV-Wbey1sXSej3pJFl57rQig,677
21
- learning_loop_node/detector/rest/backdoor_controls.py,sha256=ZtQAjxYpeskCHHQGjZd994eyq-u8LSrjObFtxLbU-Ds,1654
22
- learning_loop_node/detector/rest/detect.py,sha256=a_0A_65GQhuI-9eIgwj6gVyNu_CC9lqSMfAAN9HWFhQ,2065
23
- learning_loop_node/detector/rest/model_version_control.py,sha256=PKG7foFyNSvjoMhWCDb7w3mq-2e0bx5gq3ov7Rao8HU,3703
24
- learning_loop_node/detector/rest/operation_mode.py,sha256=eIo6_56qyZECftf4AEN8wJMABIojC0TRazvWeg0Uj_s,1664
25
- learning_loop_node/detector/rest/outbox_mode.py,sha256=anSZHB6jliz1t3fxrmEzgwNB62UHNdWNc9ZYOc5Nn9s,1018
20
+ learning_loop_node/detector/rest/about.py,sha256=COYgmYO1tXGSIwjF__P79mVZUfSDZoHsW0GUarQ2rv0,1686
21
+ learning_loop_node/detector/rest/backdoor_controls.py,sha256=ZNaFOvC0OLWNtcLiG-NIqS_y1kkLP4csgk3CHhp8Gis,885
22
+ learning_loop_node/detector/rest/detect.py,sha256=KSYUOuTxuc2q3RgV37cBBuCvCJf11BFL7QtnPAM4XbU,2343
23
+ learning_loop_node/detector/rest/model_version_control.py,sha256=jLp3rvCYq8T_QC3KK7uLDYpbDjydwazWkQCUXvkxl-c,4654
24
+ learning_loop_node/detector/rest/operation_mode.py,sha256=RAzVLtGzy4n9-LSIq_XSwMfXDehU4XmorgWAWbQ6BW8,1804
25
+ learning_loop_node/detector/rest/outbox_mode.py,sha256=H8coDNbgLGEfXmKQrhtXWeUHBAHpnrdZktuHXQz0xis,1148
26
26
  learning_loop_node/detector/rest/upload.py,sha256=IPzxJPayD7_Gx5uYC1lVJwWxdnQgM8MYGa5NugXVosY,544
27
27
  learning_loop_node/examples/novelty_score_updater.py,sha256=1DRgM9lxjFV-q2JvGDDsNLz_ic_rhEZ9wc6ZdjcxwPE,2038
28
28
  learning_loop_node/globals.py,sha256=tgw_8RYOipPV9aYlyUhYtXfUxvJKRvfUk6u-qVAtZmY,174
@@ -31,25 +31,25 @@ learning_loop_node/helpers/environment_reader.py,sha256=OtCTDc0KT9r-SMygkZB_Mw-Z
31
31
  learning_loop_node/helpers/gdrive_downloader.py,sha256=zeYJciTAJVRpu_eFjwgYLCpIa6hU1d71anqEBb564Rk,1145
32
32
  learning_loop_node/helpers/log_conf.py,sha256=z_0PHh7U7DkJbSbKoSPyUfS7NhBHtRxXHdNcj67Hpbc,951
33
33
  learning_loop_node/helpers/misc.py,sha256=j4is8Rv0ttnCqF-R-wP3xwEi67OI6IBJav5Woo5lyDk,7701
34
- learning_loop_node/loop_communication.py,sha256=rG5MdavSTaREZ6OWfAUIT_qkkYPw3is2_FujLmHQeIc,6576
35
- learning_loop_node/node.py,sha256=M846fxhutHU2djpd2Kz512iMscLJFk4DVUVO8fe2xF4,8051
34
+ learning_loop_node/loop_communication.py,sha256=xkoZtHRgxq1arusHQtC_lEBculFBLeCijyfVSYIEchY,6755
35
+ learning_loop_node/node.py,sha256=vbMR_6QsruB2IYYKUWx4--9Ywjf_vuBQb4jyzLRqpRQ,10300
36
36
  learning_loop_node/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- learning_loop_node/rest.py,sha256=5jxdfNJKbGklaVKufmprHclZF-_EKG67BeEV1XPW7mc,783
37
+ learning_loop_node/rest.py,sha256=o1dl4Mtznd5duyEQtCYSGlK04l1Y-p_YRjG40Q4l31c,1491
38
38
  learning_loop_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
39
  learning_loop_node/tests/annotator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  learning_loop_node/tests/annotator/conftest.py,sha256=G4ZvdZUdvPp9bYCzg3eEVkGCeXn9INZ3AcN7d5CyLkU,1931
41
41
  learning_loop_node/tests/annotator/pytest.ini,sha256=8QdjmawLy1zAzXrJ88or1kpFDhJw0W5UOnDfGGs_igU,262
42
42
  learning_loop_node/tests/annotator/test_annotator_node.py,sha256=TPNPPrQAxQ_zEecQcH7hlczgD3ABtTCNtUvWD1_oApk,1985
43
43
  learning_loop_node/tests/detector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
- learning_loop_node/tests/detector/conftest.py,sha256=kBg4ujWtKI03IRrftOcMly17CVFPgxWvYFpMA40OkU4,5394
44
+ learning_loop_node/tests/detector/conftest.py,sha256=Q14KHTSuSCsASVIxY9CttdVJm5FC7_JH-W5Q4CdDqoM,5414
45
45
  learning_loop_node/tests/detector/inbox_filter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  learning_loop_node/tests/detector/inbox_filter/test_observation.py,sha256=k4WYdvnuV7d_r7zI4M2aA8WuBjm0aycQ0vj1rGE2q4w,1370
47
47
  learning_loop_node/tests/detector/inbox_filter/test_relevance_group.py,sha256=XjiMsS0LgvM0OkPf5-s2rjFbG7C42LTmz_rDVMGHKoY,7603
48
48
  learning_loop_node/tests/detector/inbox_filter/test_unexpected_observations_count.py,sha256=MWC7PbaCy14jjRw0_oilkXj6gymAsUZXHJdzNW5m2D4,1639
49
49
  learning_loop_node/tests/detector/pytest.ini,sha256=8QdjmawLy1zAzXrJ88or1kpFDhJw0W5UOnDfGGs_igU,262
50
50
  learning_loop_node/tests/detector/test.jpg,sha256=msA-vHPmvPiro_D102Qmn1fn4vNfooqYYEXPxZUmYpk,161390
51
- learning_loop_node/tests/detector/test_client_communication.py,sha256=2gJARodJSDuJHgeN1_xLMbvDcPQkXpBXEefu7MOyePk,8998
52
- learning_loop_node/tests/detector/test_detector_node.py,sha256=oLmmMu0EEiatAlpEA5rqPXA4VGYctAAspgVHdxV05_k,2924
51
+ learning_loop_node/tests/detector/test_client_communication.py,sha256=NAOUrHWxoI4yG6oy3BGxWWXX794IOODEj9QBKF3CyrY,9375
52
+ learning_loop_node/tests/detector/test_detector_node.py,sha256=KX2RcFpdIbpPEmcyYM0YMs-6wwTpbOOZONoiwIWryUI,2922
53
53
  learning_loop_node/tests/detector/test_outbox.py,sha256=5RMKQfuu1-rvpVCpEtt_D70bYgma-sIrTHWxHdTdU9Y,3001
54
54
  learning_loop_node/tests/detector/test_relevance_filter.py,sha256=3VLhHKaxPzLYmiNZagvgg9ZHkPhWk4_-qpmkJw36wBU,2046
55
55
  learning_loop_node/tests/detector/testing_detector.py,sha256=FeQroV85IvsT8dmalQBqf1FLNt_buCtZK3-lgtmbrBI,542
@@ -64,32 +64,32 @@ learning_loop_node/tests/general/test_downloader.py,sha256=C6b_wG3TfQX53lmuanpH1
64
64
  learning_loop_node/tests/general/test_learning_loop_node.py,sha256=SZd-VChpWnnsPN46pr4E_LL3ZevYx6psU-AWdVeOFpQ,770
65
65
  learning_loop_node/tests/test_helper.py,sha256=nTynYtuUaK2hKh87pk7t7AIJaOiD3wJ5d6nCPqnwRMk,3012
66
66
  learning_loop_node/tests/trainer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
- learning_loop_node/tests/trainer/conftest.py,sha256=7nncnC2ZApakdyta9TMUfk0sgr_nyZhGFYCHuUlBlFI,3356
67
+ learning_loop_node/tests/trainer/conftest.py,sha256=E3SQL_CGFJ_sNjEfVJbxbvH0g6hjI5753ndAFUbnkQk,3366
68
68
  learning_loop_node/tests/trainer/pytest.ini,sha256=8QdjmawLy1zAzXrJ88or1kpFDhJw0W5UOnDfGGs_igU,262
69
69
  learning_loop_node/tests/trainer/state_helper.py,sha256=MDe9opeKruip74FoRFff8MSWGiQNFqDpPtIEIbgPnFc,919
70
70
  learning_loop_node/tests/trainer/states/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
71
  learning_loop_node/tests/trainer/states/test_state_cleanup.py,sha256=gZNxSSwnj9f0esExNnQzqadM6-sE3IsF5sNbD0bZNu8,1250
72
- learning_loop_node/tests/trainer/states/test_state_detecting.py,sha256=nvhkjBnGZgGVyomvPDcXuBR_WLqC4-QD2qE5enWiJ-g,3724
73
- learning_loop_node/tests/trainer/states/test_state_download_train_model.py,sha256=aJBh0ksz9g2kQ3qqk8yrX-50kdRS4I7ZaeeFkr9CR3c,2863
74
- learning_loop_node/tests/trainer/states/test_state_prepare.py,sha256=yaDhT-1676asGfgqhK2mfdoktAgeyWUwc2h0xyOoWzQ,2340
75
- learning_loop_node/tests/trainer/states/test_state_sync_confusion_matrix.py,sha256=0Ak5bU2uiqRDSEzVSTBEoT9GN5UgL7SqBqJRyyxjmWE,4727
76
- learning_loop_node/tests/trainer/states/test_state_train.py,sha256=dQDUvVI_FUYdLWm11F_7WxGge30l0OvML40Hil9r--k,3149
77
- learning_loop_node/tests/trainer/states/test_state_upload_detections.py,sha256=U2qK1Vbzb0yBMBYNvB815AYBD6MsII7wxzYJrwFnUHQ,7554
78
- learning_loop_node/tests/trainer/states/test_state_upload_model.py,sha256=u_YXELOWOXoaU4v41jgr3jHPWVFeVbe330k8GCEL9K0,3659
79
- learning_loop_node/tests/trainer/test_errors.py,sha256=Z3BWvUkVKxMGe_RNYeVbrhPps7ylek3qS1zK2FpdMmU,2165
72
+ learning_loop_node/tests/trainer/states/test_state_detecting.py,sha256=KGXTR69J_1pJoT8S0ceC3vSyHLw52mIpjbawH4c-8JA,3696
73
+ learning_loop_node/tests/trainer/states/test_state_download_train_model.py,sha256=AuTY63sgrlKT0awS6o38fF3mTkDguAFJtcX7J7WhjgQ,2855
74
+ learning_loop_node/tests/trainer/states/test_state_prepare.py,sha256=3hzRo9ycM802QUZO2Zs_rJRZ23hxTi3XjRCwL9M9m9o,2315
75
+ learning_loop_node/tests/trainer/states/test_state_sync_confusion_matrix.py,sha256=6s4A2d5ahD9UJJiHGK1VtmOrhumzuqTlOwyc_8Oc1vk,5073
76
+ learning_loop_node/tests/trainer/states/test_state_train.py,sha256=HYe1O6zcdtD4dnmfX3cyM1_iF7eGig7dQI4M4Xat7YU,2916
77
+ learning_loop_node/tests/trainer/states/test_state_upload_detections.py,sha256=0Qkavl4i2tZmCOxKkNsQUqa1JWhAgcOsbrW3_eYHfxo,7417
78
+ learning_loop_node/tests/trainer/states/test_state_upload_model.py,sha256=y2o4WBo7kBG_JWSWmt4icjrwya5hQ30zCWC-YMVEwEk,3621
79
+ learning_loop_node/tests/trainer/test_errors.py,sha256=khWCTzi-JW4nSz9QnsRh9wDPmiuE_zdxXukh59qixuY,2109
80
80
  learning_loop_node/tests/trainer/test_trainer_states.py,sha256=djYCs5ieajQHRjk8QcUVBUkQEG8UGYFoNGwSX0z2oGk,1067
81
81
  learning_loop_node/tests/trainer/testing_trainer_logic.py,sha256=KslqDJDntkgH4Yd_z-guiVPvzi5Q-l-Bqc3fUjT5N7U,3883
82
82
  learning_loop_node/trainer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
83
  learning_loop_node/trainer/downloader.py,sha256=Qk-oBcrGCVuWTVs3hvAJzQSqCIHPGZ7NXLJ_fAqvCoY,1469
84
- learning_loop_node/trainer/exceptions.py,sha256=hLLDGncC6PLZjKg4lZBpu-QA8itQIxiuxExz1uptgnw,40
84
+ learning_loop_node/trainer/exceptions.py,sha256=vbuoE6kssLQuA8zd3LiDHmZglP6E2IJJwEi5AZtWXxY,420
85
85
  learning_loop_node/trainer/executor.py,sha256=-0BxDqmAI1NCiISi7Rw8McJQfgxxVy1gSa1epYuL3U0,3942
86
86
  learning_loop_node/trainer/io_helpers.py,sha256=hGEtNAQBSBbVB56U1ndwfP8qK5K4YIwMQrjCDcaMy9I,7218
87
87
  learning_loop_node/trainer/rest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
- learning_loop_node/trainer/rest/backdoor_controls.py,sha256=YQcG0KwxzKDNYeMtHrSwr26q__N7ty0o6Kar6CLWAd0,5869
88
+ learning_loop_node/trainer/rest/backdoor_controls.py,sha256=-pU4iHheBWf0SW2QzBVBsLiCMZBRz9CDdVZv6414Ts8,5134
89
89
  learning_loop_node/trainer/test_executor.py,sha256=6BVGDN_6f5GEMMEvDLSG1yzMybSvgXaP5uYpSfsVPP0,2224
90
90
  learning_loop_node/trainer/trainer_logic.py,sha256=PlYExIskU9pWJO0e9m_0KJnUdOI10GtW0oDOevYmg1o,8461
91
- learning_loop_node/trainer/trainer_logic_generic.py,sha256=7ueRSkiViIRfX2T4RM7yIqrzKkqFICCyDjuj40-Y-LE,25826
92
- learning_loop_node/trainer/trainer_node.py,sha256=0baQKXVUJjf8KRu0pH8i2o_01bRQWmUGooTptC-ZSsE,5334
93
- learning_loop_node-0.10.13.dist-info/METADATA,sha256=kV-BUra2MEWVvJlQ_tW0-JbHzdrdXTZFEV4AWC5bx4A,11907
94
- learning_loop_node-0.10.13.dist-info/WHEEL,sha256=WGfLGfLX43Ei_YORXSnT54hxFygu34kMpcQdmgmEwCQ,88
95
- learning_loop_node-0.10.13.dist-info/RECORD,,
91
+ learning_loop_node/trainer/trainer_logic_generic.py,sha256=ERfuGhHGNvIPRyd_QOGavylPDXTCC8qCOO1eJXAwEO8,25957
92
+ learning_loop_node/trainer/trainer_node.py,sha256=8ANS9iy-swdTLvt9wEFixE6YlmqvqBl17A-R4tVYD-I,5384
93
+ learning_loop_node-0.10.15.dist-info/METADATA,sha256=lc7PJ83IwA8dU5N6UFWw_W35eKT21nevCsVU8bzaeU8,11907
94
+ learning_loop_node-0.10.15.dist-info/WHEEL,sha256=WGfLGfLX43Ei_YORXSnT54hxFygu34kMpcQdmgmEwCQ,88
95
+ learning_loop_node-0.10.15.dist-info/RECORD,,