specklia 1.9.37__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.
- specklia/chunked_transfer.py +28 -48
- specklia/client.py +11 -6
- {specklia-1.9.37.dist-info → specklia-1.9.39.dist-info}/METADATA +1 -1
- specklia-1.9.39.dist-info/RECORD +9 -0
- specklia-1.9.37.dist-info/RECORD +0 -9
- {specklia-1.9.37.dist-info → specklia-1.9.39.dist-info}/LICENCE +0 -0
- {specklia-1.9.37.dist-info → specklia-1.9.39.dist-info}/WHEEL +0 -0
- {specklia-1.9.37.dist-info → specklia-1.9.39.dist-info}/top_level.txt +0 -0
specklia/chunked_transfer.py
CHANGED
|
@@ -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]]
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
109
|
-
|
|
107
|
+
num_chunks : int
|
|
108
|
+
The number of chunks to download.
|
|
110
109
|
|
|
111
110
|
Returns
|
|
112
111
|
-------
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
145
|
+
log.error(error_message)
|
|
153
146
|
raise RuntimeError(error_message)
|
|
154
|
-
|
|
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.
|
|
235
|
-
|
|
236
|
-
|
|
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,
|
|
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
|
|
|
@@ -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,,
|
specklia-1.9.37.dist-info/RECORD
DELETED
|
@@ -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.37.dist-info/LICENCE,sha256=kjWTA-TtT_rJtsWuAgWvesvu01BytVXgt_uCbeQgjOg,1061
|
|
6
|
-
specklia-1.9.37.dist-info/METADATA,sha256=-7lkPsGmAMUNNkBSUtTQQuKmDavxW82C1S0HLpiKrpw,3082
|
|
7
|
-
specklia-1.9.37.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
8
|
-
specklia-1.9.37.dist-info/top_level.txt,sha256=XgU53UpAJbqEni5EjJaPdQPYuNx16Geg2I5A9lo1BQw,9
|
|
9
|
-
specklia-1.9.37.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|