learning-loop-node 0.13.6__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.6 → learning_loop_node-0.13.7}/PKG-INFO +5 -3
  2. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/README.md +4 -2
  3. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/data_exchanger.py +1 -1
  4. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/detector_node.py +25 -13
  5. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/loop_communication.py +18 -18
  6. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/pyproject.toml +1 -1
  7. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/__init__.py +0 -0
  8. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/annotation/__init__.py +0 -0
  9. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/annotation/annotator_logic.py +0 -0
  10. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/annotation/annotator_node.py +0 -0
  11. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/data_classes/__init__.py +0 -0
  12. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/data_classes/annotations.py +0 -0
  13. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/data_classes/detections.py +0 -0
  14. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/data_classes/general.py +0 -0
  15. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/data_classes/image_metadata.py +0 -0
  16. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/data_classes/socket_response.py +0 -0
  17. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/data_classes/training.py +0 -0
  18. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/__init__.py +0 -0
  19. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/detector_logic.py +0 -0
  20. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/exceptions.py +0 -0
  21. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/inbox_filter/__init__.py +0 -0
  22. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/inbox_filter/cam_observation_history.py +0 -0
  23. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/inbox_filter/relevance_filter.py +0 -0
  24. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/outbox.py +0 -0
  25. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/__init__.py +0 -0
  26. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/about.py +0 -0
  27. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/backdoor_controls.py +0 -0
  28. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/detect.py +0 -0
  29. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/model_version_control.py +0 -0
  30. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/operation_mode.py +0 -0
  31. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/outbox_mode.py +0 -0
  32. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/detector/rest/upload.py +0 -0
  33. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/enums/__init__.py +0 -0
  34. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/enums/annotator.py +0 -0
  35. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/enums/detector.py +0 -0
  36. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/enums/general.py +0 -0
  37. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/enums/trainer.py +0 -0
  38. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/examples/novelty_score_updater.py +0 -0
  39. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/globals.py +0 -0
  40. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/helpers/__init__.py +0 -0
  41. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/helpers/environment_reader.py +0 -0
  42. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/helpers/gdrive_downloader.py +0 -0
  43. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/helpers/log_conf.py +0 -0
  44. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/helpers/misc.py +0 -0
  45. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/node.py +0 -0
  46. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/py.typed +0 -0
  47. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/rest.py +0 -0
  48. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/__init__.py +0 -0
  49. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/annotator/__init__.py +0 -0
  50. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/annotator/conftest.py +0 -0
  51. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/annotator/pytest.ini +0 -0
  52. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/annotator/test_annotator_node.py +0 -0
  53. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/__init__.py +0 -0
  54. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/conftest.py +0 -0
  55. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/inbox_filter/__init__.py +0 -0
  56. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/inbox_filter/test_observation.py +0 -0
  57. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/inbox_filter/test_relevance_group.py +0 -0
  58. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/inbox_filter/test_unexpected_observations_count.py +0 -0
  59. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/pytest.ini +0 -0
  60. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/test.jpg +0 -0
  61. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/test_client_communication.py +0 -0
  62. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/test_detector_node.py +0 -0
  63. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/test_outbox.py +0 -0
  64. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/test_relevance_filter.py +0 -0
  65. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/detector/testing_detector.py +0 -0
  66. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/__init__.py +0 -0
  67. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/conftest.py +0 -0
  68. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/pytest.ini +0 -0
  69. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/test_data/file_1.txt +0 -0
  70. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/test_data/file_2.txt +0 -0
  71. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/test_data/model.json +0 -0
  72. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/test_data_classes.py +0 -0
  73. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/test_downloader.py +0 -0
  74. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/general/test_learning_loop_node.py +0 -0
  75. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/test_helper.py +0 -0
  76. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/__init__.py +0 -0
  77. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/conftest.py +0 -0
  78. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/pytest.ini +0 -0
  79. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/state_helper.py +0 -0
  80. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/states/__init__.py +0 -0
  81. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/states/test_state_cleanup.py +0 -0
  82. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/states/test_state_detecting.py +0 -0
  83. {learning_loop_node-0.13.6 → 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.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/states/test_state_prepare.py +0 -0
  85. {learning_loop_node-0.13.6 → 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.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/states/test_state_train.py +0 -0
  87. {learning_loop_node-0.13.6 → 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.6 → 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.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/test_errors.py +0 -0
  90. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/test_trainer_states.py +0 -0
  91. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/tests/trainer/testing_trainer_logic.py +0 -0
  92. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/trainer/__init__.py +0 -0
  93. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/trainer/downloader.py +0 -0
  94. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/trainer/exceptions.py +0 -0
  95. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/trainer/executor.py +0 -0
  96. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/trainer/io_helpers.py +0 -0
  97. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/trainer/rest/__init__.py +0 -0
  98. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/trainer/rest/backdoor_controls.py +0 -0
  99. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/trainer/test_executor.py +0 -0
  100. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/trainer/trainer_logic.py +0 -0
  101. {learning_loop_node-0.13.6 → learning_loop_node-0.13.7}/learning_loop_node/trainer/trainer_logic_generic.py +0 -0
  102. {learning_loop_node-0.13.6 → 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.6
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,
@@ -344,16 +344,21 @@ class DetectorNode(Node):
344
344
  with step_into(GLOBALS.data_folder):
345
345
  model_symlink = 'model'
346
346
  target_model_folder = f'models/{self.target_model.version}'
347
- if not os.path.exists(target_model_folder):
348
- os.makedirs(target_model_folder)
349
- await self.data_exchanger.download_model(target_model_folder,
350
- Context(organization=self.organization,
351
- project=self.project),
352
- self.target_model.id,
353
- self.detector_logic.model_format)
354
- self.log.info('Downloaded model %s', self.target_model.version)
355
- else:
347
+ if os.path.exists(target_model_folder) and len(os.listdir(target_model_folder)) > 0:
356
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
357
362
  try:
358
363
  os.unlink(model_symlink)
359
364
  os.remove(model_symlink)
@@ -422,16 +427,23 @@ class DetectorNode(Node):
422
427
  self.log_status_on_change(status.state or 'None', status)
423
428
 
424
429
  # NOTE: sending organization and project is no longer required!
425
- 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
+
426
437
  if not response:
427
438
  self.socket_connection_broken = True
428
- 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')
429
441
 
430
442
  socket_response = from_dict(data_class=SocketResponse, data=response)
431
443
  if not socket_response.success:
432
444
  self.socket_connection_broken = True
433
- self.log.error('Statusupdate failed: %s', response)
434
- 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}')
435
447
 
436
448
  assert socket_response.payload is not None
437
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)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "learning_loop_node"
3
- version = "v0.13.6"
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"