learning-loop-node 0.10.9__py3-none-any.whl → 0.10.10__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.

@@ -14,9 +14,9 @@ from multiprocessing import Event
14
14
  from multiprocessing.synchronize import Event as SyncEvent
15
15
  from typing import List, Optional, Tuple, Union
16
16
 
17
+ import aiohttp
17
18
  import PIL
18
19
  import PIL.Image # type: ignore
19
- import requests
20
20
  from fastapi.encoders import jsonable_encoder
21
21
 
22
22
  from ..data_classes import Detections
@@ -64,7 +64,10 @@ class Outbox():
64
64
  detections = Detections()
65
65
  if not tags:
66
66
  tags = []
67
- identifier = datetime.now().isoformat(sep='_', timespec='milliseconds')
67
+ identifier = datetime.now().isoformat(sep='_', timespec='microseconds')
68
+ if os.path.exists(self.path + '/' + identifier):
69
+ self.log.error('Directory with identifier %s already exists', identifier)
70
+ return
68
71
  tmp = f'{GLOBALS.data_folder}/tmp/{identifier}'
69
72
  detections.tags = tags
70
73
  detections.date = identifier
@@ -97,11 +100,11 @@ class Outbox():
97
100
  self.log.info('continuous upload started')
98
101
  assert self.shutdown_event is not None
99
102
  while not self.shutdown_event.is_set():
100
- self.upload()
103
+ await self.upload()
101
104
  await asyncio.sleep(5)
102
105
  self.log.info('continuous upload ended')
103
106
 
104
- def upload(self):
107
+ async def upload(self):
105
108
  items = self.get_data_files()
106
109
  if not items:
107
110
  self.log.debug('No images found to upload')
@@ -113,11 +116,11 @@ class Outbox():
113
116
  if self.shutdown_event.is_set():
114
117
  break
115
118
  try:
116
- self._upload_batch(batch_items)
119
+ await self._upload_batch(batch_items)
117
120
  except Exception:
118
121
  self.log.exception('Could not upload files')
119
122
 
120
- def _upload_batch(self, items: List[str]):
123
+ async def _upload_batch(self, items: List[str]):
121
124
 
122
125
  # NOTE: keys are not relevant for the server, but using a fixed key like 'files'
123
126
  # results in a post failure on the first run of the test in a docker environment (WTF)
@@ -127,7 +130,8 @@ class Outbox():
127
130
  data += [('files', open(f'{item}/image.jpg', 'rb')) for item in items]
128
131
 
129
132
  try:
130
- response = requests.post(self.target_uri, files=data, timeout=self.UPLOAD_TIMEOUT_S)
133
+ async with aiohttp.ClientSession() as session:
134
+ response = await session.post(self.target_uri, data=data, timeout=self.UPLOAD_TIMEOUT_S)
131
135
  except Exception:
132
136
  self.log.exception('Could not upload images')
133
137
  return
@@ -136,21 +140,21 @@ class Outbox():
136
140
  for _, file in data:
137
141
  file.close()
138
142
 
139
- if response.status_code == 200:
143
+ if response.status == 200:
140
144
  self.upload_counter += len(items)
141
145
  for item in items:
142
146
  shutil.rmtree(item, ignore_errors=True)
143
147
  self.log.info('Uploaded %s images successfully', len(items))
144
148
 
145
- elif response.status_code == 422:
149
+ elif response.status == 422:
146
150
  if len(items) == 1:
147
151
  self.log.error('Broken content in image: %s\n Skipping.', items[0])
148
152
  shutil.rmtree(items[0], ignore_errors=True)
149
153
  return
150
154
 
151
155
  self.log.exception('Broken content in batch. Splitting and retrying')
152
- self._upload_batch(items[:len(items)//2])
153
- self._upload_batch(items[len(items)//2:])
156
+ await self._upload_batch(items[:len(items)//2])
157
+ await self._upload_batch(items[len(items)//2:])
154
158
  else:
155
159
  self.log.error('Could not upload images: %s', response.content)
156
160
 
@@ -121,3 +121,24 @@ async def test_rest_outbox_mode(test_detector_node: DetectorNode):
121
121
  check_switch_to_mode('stopped')
122
122
  check_switch_to_mode('continuous_upload')
123
123
  check_switch_to_mode('stopped')
124
+
125
+
126
+ async def test_api_responsive_during_large_upload(test_detector_node: DetectorNode):
127
+ assert len(get_outbox_files(test_detector_node.outbox)) == 0
128
+
129
+ with open(test_image_path, 'rb') as f:
130
+ image_bytes = f.read()
131
+
132
+ for _ in range(100):
133
+ test_detector_node.outbox.save(image_bytes)
134
+
135
+ outbox_size_early = len(get_outbox_files(test_detector_node.outbox))
136
+ await asyncio.sleep(5) # NOTE: we wait 5 seconds because the continuous upload is running every 5 seconds
137
+
138
+ # check if api is still responsive
139
+ response = requests.get(f'http://localhost:{GLOBALS.detector_port}/outbox_mode', timeout=2)
140
+ assert response.status_code == 200
141
+
142
+ await asyncio.sleep(5)
143
+ outbox_size_late = len(get_outbox_files(test_detector_node.outbox))
144
+ assert outbox_size_early > outbox_size_late > 0, 'The outbox should have been partially emptied.'
@@ -52,7 +52,7 @@ async def test_outbox_upload_is_successful(test_outbox: Outbox):
52
52
  await asyncio.sleep(1)
53
53
  test_outbox.save(get_test_image_binary())
54
54
  assert await wait_for_outbox_count(test_outbox, 2)
55
- test_outbox.upload()
55
+ await test_outbox.upload()
56
56
  assert await wait_for_outbox_count(test_outbox, 0)
57
57
  assert test_outbox.upload_counter == 2
58
58
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: learning-loop-node
3
- Version: 0.10.9
3
+ Version: 0.10.10
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
@@ -15,7 +15,7 @@ learning_loop_node/detector/detector_node.py,sha256=jaz4TiHNVFd8p7NQ6Zcrsro9c-X9
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=s2FuwZ-tD_5obkSutstjc8pE_hLGbrv9WjrEO9t8rJ8,1011
18
- learning_loop_node/detector/outbox.py,sha256=kRC_ZSvNNxEeJg0qmImBWNiKYjcgHiPAmRQB4VlQ-Uk,7828
18
+ learning_loop_node/detector/outbox.py,sha256=MHHP4rnGaV8JxDSig96KZN4hSQ3i9z6-7WYhTTrMtp0,8082
19
19
  learning_loop_node/detector/rest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  learning_loop_node/detector/rest/about.py,sha256=-PNqlQI_tzRvoSI_UR9rX8-5GeiENNpRDQ4Ylw3wYVs,607
21
21
  learning_loop_node/detector/rest/backdoor_controls.py,sha256=38axRG66Z3_Q6bYKa7Hw-ldChEAu-dJcBM_Sl_17Ozo,1725
@@ -46,8 +46,8 @@ learning_loop_node/tests/detector/inbox_filter/test_relevance_group.py,sha256=Xj
46
46
  learning_loop_node/tests/detector/inbox_filter/test_unexpected_observations_count.py,sha256=MWC7PbaCy14jjRw0_oilkXj6gymAsUZXHJdzNW5m2D4,1639
47
47
  learning_loop_node/tests/detector/pytest.ini,sha256=8QdjmawLy1zAzXrJ88or1kpFDhJw0W5UOnDfGGs_igU,262
48
48
  learning_loop_node/tests/detector/test.jpg,sha256=msA-vHPmvPiro_D102Qmn1fn4vNfooqYYEXPxZUmYpk,161390
49
- learning_loop_node/tests/detector/test_client_communication.py,sha256=fnTyzP3XV1i6YwB9-GEDj2ifc_Fr_FtnjDhPoRYX2Ec,5261
50
- learning_loop_node/tests/detector/test_outbox.py,sha256=1sqTLRrCkUsoYz67Z1L03wIet1N3Nxlil3b5VLEK644,2995
49
+ learning_loop_node/tests/detector/test_client_communication.py,sha256=9r1LULGmRqKihRgG0v7-CkQryRCsyyahoLy8QyjsOTU,6128
50
+ learning_loop_node/tests/detector/test_outbox.py,sha256=5RMKQfuu1-rvpVCpEtt_D70bYgma-sIrTHWxHdTdU9Y,3001
51
51
  learning_loop_node/tests/detector/test_relevance_filter.py,sha256=3VLhHKaxPzLYmiNZagvgg9ZHkPhWk4_-qpmkJw36wBU,2046
52
52
  learning_loop_node/tests/detector/testing_detector.py,sha256=FeQroV85IvsT8dmalQBqf1FLNt_buCtZK3-lgtmbrBI,542
53
53
  learning_loop_node/tests/general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -88,6 +88,6 @@ learning_loop_node/trainer/test_executor.py,sha256=6BVGDN_6f5GEMMEvDLSG1yzMybSvg
88
88
  learning_loop_node/trainer/trainer_logic.py,sha256=PJxiO1chPdvpq8UTtzv_nVam9CouCswX9b1FnRwT2Tw,8411
89
89
  learning_loop_node/trainer/trainer_logic_generic.py,sha256=AzllMMiUPP_CMkjIVqse8wY50Cg5RDnk5y5ERVUjtZg,25801
90
90
  learning_loop_node/trainer/trainer_node.py,sha256=dV-kcTIxhHsep_xIXdGc_AaeJM1mFlQNnwUpTkG4btg,4110
91
- learning_loop_node-0.10.9.dist-info/METADATA,sha256=DaJ_D49CGjlIbcX1xagMN3DPRxasYxFWiZBfDJqph5A,10383
92
- learning_loop_node-0.10.9.dist-info/WHEEL,sha256=WGfLGfLX43Ei_YORXSnT54hxFygu34kMpcQdmgmEwCQ,88
93
- learning_loop_node-0.10.9.dist-info/RECORD,,
91
+ learning_loop_node-0.10.10.dist-info/METADATA,sha256=d4e08fjXLZ4nieGSIE97nLYl1sapDta_W4mA-Uh05oM,10384
92
+ learning_loop_node-0.10.10.dist-info/WHEEL,sha256=WGfLGfLX43Ei_YORXSnT54hxFygu34kMpcQdmgmEwCQ,88
93
+ learning_loop_node-0.10.10.dist-info/RECORD,,