specklia 1.9.38__py3-none-any.whl → 1.9.39__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.
@@ -41,6 +41,8 @@ MAX_CHUNK_SIZE_BYTES = 5 * 1024 ** 2 # must be small enough to fit into an HTTP
41
41
  CHUNK_DOWNLOAD_RETRIES = 10
42
42
  CHUNK_DOWNLOAD_TIMEOUT_S = 10
43
43
 
44
+ log = Logger(__name__)
45
+
44
46
 
45
47
  class ChunkSetStatus(Enum):
46
48
  """
@@ -53,7 +55,7 @@ class ChunkSetStatus(Enum):
53
55
  EMPTYING = 1
54
56
 
55
57
 
56
- def upload_chunks(api_address: str, chunks: List[Tuple[int, bytes]], logger: Logger) -> str:
58
+ def upload_chunks(api_address: str, chunks: List[Tuple[int, bytes]]) -> str:
57
59
  """
58
60
  Upload data chunks.
59
61
 
@@ -66,8 +68,6 @@ def upload_chunks(api_address: str, chunks: List[Tuple[int, bytes]], logger: Log
66
68
  The full URL of the API, including port but not including endpoint, e.g. "http://127.0.0.1:9999"
67
69
  chunks : List[Tuple[int, bytes]]
68
70
  A list of tuples containing the ordinal number of the chunk and each chunk
69
- logger : Logger
70
- A logger with which to log the upload.
71
71
 
72
72
  Returns
73
73
  -------
@@ -78,7 +78,7 @@ def upload_chunks(api_address: str, chunks: List[Tuple[int, bytes]], logger: Log
78
78
  response = requests.post(
79
79
  api_address + f"/chunk/upload/{chunks[0][0]}-of-{len(chunks)}",
80
80
  data=chunks[0][1])
81
- logger.info("response from very first /chunk/upload was '%s'", response.json())
81
+ log.info("response from very first /chunk/upload was '%s'", response.json())
82
82
  assert response.status_code == HTTPStatus.OK, response.text
83
83
  chunk_set_uuid = response.json()['chunk_set_uuid']
84
84
 
@@ -86,18 +86,17 @@ def upload_chunks(api_address: str, chunks: List[Tuple[int, bytes]], logger: Log
86
86
  for i, chunk in chunks[1:]:
87
87
  response = requests.post(
88
88
  api_address + f"/chunk/upload/{chunk_set_uuid}/{i}-of-{len(chunks)}", data=chunk)
89
- logger.info("response from subsequent /chunk/upload/uuid call was '%s'", response.text)
89
+ log.info("response from subsequent /chunk/upload/uuid call was '%s'", response.text)
90
90
  assert response.status_code == HTTPStatus.OK, response.text
91
91
 
92
92
  return chunk_set_uuid
93
93
 
94
94
 
95
- def download_chunks(api_address: str, chunk_set_uuid: str, logger: Logger) -> List[Tuple[int, bytes]]:
95
+ def download_chunks(api_address: str, chunk_set_uuid: str, num_chunks: int) -> bytes:
96
96
  """
97
97
  Download data chunks.
98
98
 
99
- Download a series of data chunks through the chunked transfer mechanism.
100
- This method is for use on the client, not the server.
99
+ Download a series of data chunks sequentially through the chunked transfer mechanism.
101
100
 
102
101
  Parameters
103
102
  ----------
@@ -105,53 +104,51 @@ def download_chunks(api_address: str, chunk_set_uuid: str, logger: Logger) -> Li
105
104
  The full URL of the API, including port but not including endpoint, e.g. "http://127.0.0.1:9999"
106
105
  chunk_set_uuid : str
107
106
  The uuid of the chunk set to download.
108
- logger : Logger
109
- A logger with which to log the download.
107
+ num_chunks : int
108
+ The number of chunks to download.
110
109
 
111
110
  Returns
112
111
  -------
113
- chunks : List[Tuple[int, bytes]]
114
- A list of tuples containing the ordinal number of the chunk and each chunk
112
+ bytes
113
+ The concatenated data from all the chunks.
115
114
 
116
115
  Raises
117
116
  ------
118
117
  RuntimeError
119
118
  If the download fails after a number of retries.
120
119
  """
121
- # fetch the data
122
- data_chunks = []
123
- finished = False
124
-
125
- while not finished:
120
+ chunks = []
121
+ for chunk_ordinal in range(1, num_chunks + 1):
126
122
  retries = 0
127
123
  success = False
128
-
129
124
  while retries < CHUNK_DOWNLOAD_RETRIES and not success:
130
125
  try:
131
126
  this_chunk_response = requests.get(
132
- f"{api_address}/chunk/download/{chunk_set_uuid}",
127
+ f"{api_address}/chunk/download/{chunk_set_uuid}/{chunk_ordinal}",
133
128
  timeout=CHUNK_DOWNLOAD_TIMEOUT_S
134
129
  )
135
- if this_chunk_response.status_code == HTTPStatus.NO_CONTENT:
136
- finished = True
137
- else:
138
- data_chunks.append((
139
- struct.unpack('i', this_chunk_response.content[:4])[0],
140
- this_chunk_response.content[4:]))
130
+ this_chunk_response.raise_for_status()
131
+ ordinal = struct.unpack('i', this_chunk_response.content[:4])[0]
132
+ chunk = this_chunk_response.content[4:]
133
+ assert ordinal == chunk_ordinal, (
134
+ f"Chunk ordinal mismatch: expected {chunk_ordinal}, got {ordinal}")
135
+ chunks.append(chunk)
141
136
  success = True
142
-
143
137
  except (requests.Timeout, requests.ConnectionError) as e:
144
138
  retries += 1
145
- logger.warning(
139
+ log.warning(
146
140
  "Request failed with %s. Retrying (%s/%s)...", e, retries, CHUNK_DOWNLOAD_RETRIES)
147
141
  time.sleep(1) # Small backoff before retrying
148
-
149
142
  if not success:
150
143
  error_message = (
151
144
  f"Failed to download from chunk set {chunk_set_uuid} after {CHUNK_DOWNLOAD_TIMEOUT_S} attempts.")
152
- logger.error(error_message)
145
+ log.error(error_message)
153
146
  raise RuntimeError(error_message)
154
- return data_chunks
147
+
148
+ # Let the server know that we are done with this data and it can be deleted.
149
+ requests.delete(f'{api_address}/chunk/delete/{chunk_set_uuid}')
150
+
151
+ return b''.join(chunks)
155
152
 
156
153
 
157
154
  def split_into_chunks(data: bytes, chunk_size: int = MAX_CHUNK_SIZE_BYTES) -> List[Tuple[int, bytes]]:
@@ -174,23 +171,6 @@ def split_into_chunks(data: bytes, chunk_size: int = MAX_CHUNK_SIZE_BYTES) -> Li
174
171
  enumerate((data[i:i + chunk_size] for i in range(0, len(data), chunk_size)), start=1))
175
172
 
176
173
 
177
- def merge_from_chunks(chunks: List[Tuple[int, bytes]]) -> bytes:
178
- """
179
- Merge data that has been split into compressed chunks back into a single message.
180
-
181
- Parameters
182
- ----------
183
- chunks : List[Tuple[int, bytes]]
184
- A list of tuples containing the ordinal number of the chunk and each chunk
185
-
186
- Returns
187
- -------
188
- bytes
189
- The merged data
190
- """
191
- return b''.join([dc[1] for dc in sorted(chunks, key=lambda x: x[0])])
192
-
193
-
194
174
  def deserialise_dataframe(data: bytes) -> Union[DataFrame, GeoDataFrame]:
195
175
  """
196
176
  Convert a binary serialised feather table to pandas dataframe.
@@ -223,7 +203,7 @@ def deserialise_dataframe(data: bytes) -> Union[DataFrame, GeoDataFrame]:
223
203
  raise ValueError("Couldn't deserialise table format") from e
224
204
  else:
225
205
  raise ValueError("Couldn't deserialise table format") from e
226
- return df
206
+ return df # type: ignore
227
207
 
228
208
 
229
209
  def serialise_dataframe(df: Union[DataFrame, GeoDataFrame]) -> bytes:
specklia/client.py CHANGED
@@ -231,9 +231,12 @@ class Specklia:
231
231
  # stream and deserialise the results
232
232
  if response_dict['num_chunks'] > 0:
233
233
  gdf = chunked_transfer.deserialise_dataframe(
234
- chunked_transfer.merge_from_chunks(
235
- chunked_transfer.download_chunks(
236
- self.server_url, response_dict['chunk_set_uuid'], _log)))
234
+ chunked_transfer.download_chunks(
235
+ self.server_url,
236
+ response_dict['chunk_set_uuid'],
237
+ response_dict['num_chunks'],
238
+ )
239
+ )
237
240
  else:
238
241
  gdf = gpd.GeoDataFrame()
239
242
 
@@ -312,12 +315,14 @@ class Specklia:
312
315
  # serialise and upload each dataframe
313
316
  upload_points = []
314
317
  for n in new_points:
318
+ chunks = chunked_transfer.split_into_chunks(
319
+ chunked_transfer.serialise_dataframe(n['gdf']))
315
320
  chunk_set_uuid = chunked_transfer.upload_chunks(
316
- self.server_url, chunked_transfer.split_into_chunks(
317
- chunked_transfer.serialise_dataframe(n['gdf'])), _log)
321
+ self.server_url, chunks)
318
322
  upload_points.append({
319
323
  'source': n['source'],
320
- 'chunk_set_uuid': chunk_set_uuid
324
+ 'chunk_set_uuid': chunk_set_uuid,
325
+ 'num_chunks': len(chunks),
321
326
  })
322
327
  del n
323
328
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: specklia
3
- Version: 1.9.38
3
+ Version: 1.9.39
4
4
  Summary: Python client for Specklia, a geospatial point cloud database by Earthwave.
5
5
  Home-page: https://specklia.earthwave.co.uk/
6
6
  Author: Earthwave Ltd
@@ -0,0 +1,9 @@
1
+ specklia/__init__.py,sha256=ePVHqq642NocoE8tS0cNTd0B5wJdUB7r3y815oQXD6A,51
2
+ specklia/chunked_transfer.py,sha256=H7JUJqi_e8ftSPURG-B59900-S23PXFGXBrH3_lfRBw,7910
3
+ specklia/client.py,sha256=ujSkx62VIuOJ3FfTon7rAztXkzZCQl5J6QEmFwK8aP8,42183
4
+ specklia/utilities.py,sha256=fs9DOSq-0hdgOlGAnPY_og5QngDcu3essVAupz6ychM,5170
5
+ specklia-1.9.39.dist-info/LICENCE,sha256=kjWTA-TtT_rJtsWuAgWvesvu01BytVXgt_uCbeQgjOg,1061
6
+ specklia-1.9.39.dist-info/METADATA,sha256=rBIu805Ndrde3QgrZIIvwWHaf4mC3cdmubphHfujJj8,3082
7
+ specklia-1.9.39.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
8
+ specklia-1.9.39.dist-info/top_level.txt,sha256=XgU53UpAJbqEni5EjJaPdQPYuNx16Geg2I5A9lo1BQw,9
9
+ specklia-1.9.39.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- specklia/__init__.py,sha256=ePVHqq642NocoE8tS0cNTd0B5wJdUB7r3y815oQXD6A,51
2
- specklia/chunked_transfer.py,sha256=sQGh_0M3DcJw4Uu1Upb7zA9L1syupI9JXVL_94OoNEs,8343
3
- specklia/client.py,sha256=IlOr3CP_0BJpB0oOvkvHFgtiqmmFynpUriDM3NHUVeU,42088
4
- specklia/utilities.py,sha256=fs9DOSq-0hdgOlGAnPY_og5QngDcu3essVAupz6ychM,5170
5
- specklia-1.9.38.dist-info/LICENCE,sha256=kjWTA-TtT_rJtsWuAgWvesvu01BytVXgt_uCbeQgjOg,1061
6
- specklia-1.9.38.dist-info/METADATA,sha256=Tc8LwvdAQPpbxOSzcZiTQmICzBo_jhuMvzbMvYfdOCA,3082
7
- specklia-1.9.38.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
8
- specklia-1.9.38.dist-info/top_level.txt,sha256=XgU53UpAJbqEni5EjJaPdQPYuNx16Geg2I5A9lo1BQw,9
9
- specklia-1.9.38.dist-info/RECORD,,