specklia 1.9.16__tar.gz → 1.9.17__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.
- {specklia-1.9.16 → specklia-1.9.17}/PKG-INFO +1 -1
- {specklia-1.9.16 → specklia-1.9.17}/specklia/chunked_transfer.py +43 -10
- {specklia-1.9.16 → specklia-1.9.17}/specklia/client.py +1 -1
- {specklia-1.9.16 → specklia-1.9.17}/specklia.egg-info/PKG-INFO +1 -1
- {specklia-1.9.16 → specklia-1.9.17}/tests/test_chunked_transfer.py +2 -1
- {specklia-1.9.16 → specklia-1.9.17}/LICENCE +0 -0
- {specklia-1.9.16 → specklia-1.9.17}/README.md +0 -0
- {specklia-1.9.16 → specklia-1.9.17}/setup.cfg +0 -0
- {specklia-1.9.16 → specklia-1.9.17}/setup.py +0 -0
- {specklia-1.9.16 → specklia-1.9.17}/specklia/__init__.py +0 -0
- {specklia-1.9.16 → specklia-1.9.17}/specklia/utilities.py +0 -0
- {specklia-1.9.16 → specklia-1.9.17}/specklia.egg-info/SOURCES.txt +0 -0
- {specklia-1.9.16 → specklia-1.9.17}/specklia.egg-info/dependency_links.txt +0 -0
- {specklia-1.9.16 → specklia-1.9.17}/specklia.egg-info/requires.txt +0 -0
- {specklia-1.9.16 → specklia-1.9.17}/specklia.egg-info/top_level.txt +0 -0
- {specklia-1.9.16 → specklia-1.9.17}/tests/test_client.py +0 -0
- {specklia-1.9.16 → specklia-1.9.17}/tests/test_utilities.py +0 -0
|
@@ -25,16 +25,21 @@ from http import HTTPStatus
|
|
|
25
25
|
from io import BytesIO
|
|
26
26
|
from logging import Logger
|
|
27
27
|
import struct
|
|
28
|
+
import time
|
|
28
29
|
from typing import List, Tuple, Union
|
|
29
30
|
|
|
30
|
-
from geopandas import GeoDataFrame
|
|
31
|
-
from
|
|
31
|
+
from geopandas import GeoDataFrame
|
|
32
|
+
from geopandas import read_feather as read_geofeather
|
|
33
|
+
from pandas import DataFrame
|
|
34
|
+
from pandas import read_feather
|
|
32
35
|
import requests
|
|
33
36
|
|
|
34
37
|
CHUNK_DB_NAME = "data_transfer_chunks"
|
|
35
38
|
CHUNK_METADATA_COLLECTION_NAME = "chunk_metadata"
|
|
36
39
|
MAX_CHUNK_AGE_SECONDS = 3600
|
|
37
40
|
MAX_CHUNK_SIZE_BYTES = 5 * 1024 ** 2 # must be small enough to fit into an HTTP GET Request
|
|
41
|
+
CHUNK_DOWNLOAD_RETRIES = 10
|
|
42
|
+
CHUNK_DOWNLOAD_TIMEOUT_S = 10
|
|
38
43
|
|
|
39
44
|
|
|
40
45
|
class ChunkSetStatus(Enum):
|
|
@@ -87,7 +92,7 @@ def upload_chunks(api_address: str, chunks: List[Tuple[int, bytes]], logger: Log
|
|
|
87
92
|
return chunk_set_uuid
|
|
88
93
|
|
|
89
94
|
|
|
90
|
-
def download_chunks(api_address: str, chunk_set_uuid: str) -> List[Tuple[int, bytes]]:
|
|
95
|
+
def download_chunks(api_address: str, chunk_set_uuid: str, logger: Logger) -> List[Tuple[int, bytes]]:
|
|
91
96
|
"""
|
|
92
97
|
Download data chunks.
|
|
93
98
|
|
|
@@ -100,24 +105,52 @@ def download_chunks(api_address: str, chunk_set_uuid: str) -> List[Tuple[int, by
|
|
|
100
105
|
The full URL of the API, including port but not including endpoint, e.g. "http://127.0.0.1:9999"
|
|
101
106
|
chunk_set_uuid : str
|
|
102
107
|
The uuid of the chunk set to download.
|
|
108
|
+
logger : Logger
|
|
109
|
+
A logger with which to log the download.
|
|
103
110
|
|
|
104
111
|
Returns
|
|
105
112
|
-------
|
|
106
113
|
chunks : List[Tuple[int, bytes]]
|
|
107
114
|
A list of tuples containing the ordinal number of the chunk and each chunk
|
|
115
|
+
|
|
116
|
+
Raises
|
|
117
|
+
------
|
|
118
|
+
RuntimeError
|
|
119
|
+
If the download fails after a number of retries.
|
|
108
120
|
"""
|
|
109
121
|
# fetch the data
|
|
110
122
|
data_chunks = []
|
|
111
123
|
finished = False
|
|
124
|
+
|
|
112
125
|
while not finished:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
finished = True
|
|
116
|
-
else:
|
|
117
|
-
data_chunks.append((
|
|
118
|
-
struct.unpack('i', this_chunk_response.content[:4])[0],
|
|
119
|
-
this_chunk_response.content[4:]))
|
|
126
|
+
retries = 0
|
|
127
|
+
success = False
|
|
120
128
|
|
|
129
|
+
while retries < CHUNK_DOWNLOAD_RETRIES and not success:
|
|
130
|
+
try:
|
|
131
|
+
this_chunk_response = requests.get(
|
|
132
|
+
f"{api_address}/chunk/download/{chunk_set_uuid}",
|
|
133
|
+
timeout=CHUNK_DOWNLOAD_TIMEOUT_S
|
|
134
|
+
)
|
|
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:]))
|
|
141
|
+
success = True
|
|
142
|
+
|
|
143
|
+
except (requests.Timeout, requests.ConnectionError) as e:
|
|
144
|
+
retries += 1
|
|
145
|
+
logger.warning(
|
|
146
|
+
"Request failed with %s. Retrying (%s/%s)...", e, retries, CHUNK_DOWNLOAD_RETRIES)
|
|
147
|
+
time.sleep(1) # Small backoff before retrying
|
|
148
|
+
|
|
149
|
+
if not success:
|
|
150
|
+
error_message = (
|
|
151
|
+
f"Failed to download from chunk set {chunk_set_uuid} after {CHUNK_DOWNLOAD_TIMEOUT_S} attempts.")
|
|
152
|
+
logger.error(error_message)
|
|
153
|
+
raise RuntimeError(error_message)
|
|
121
154
|
return data_chunks
|
|
122
155
|
|
|
123
156
|
|
|
@@ -232,7 +232,7 @@ class Specklia:
|
|
|
232
232
|
gdf = chunked_transfer.deserialise_dataframe(
|
|
233
233
|
chunked_transfer.merge_from_chunks(
|
|
234
234
|
chunked_transfer.download_chunks(
|
|
235
|
-
self.server_url, response_dict['chunk_set_uuid'])))
|
|
235
|
+
self.server_url, response_dict['chunk_set_uuid'], _log)))
|
|
236
236
|
else:
|
|
237
237
|
gdf = gpd.GeoDataFrame()
|
|
238
238
|
|
|
@@ -40,7 +40,8 @@ def test_download_chunks():
|
|
|
40
40
|
MagicMock(name="mock_response_2", status_code=HTTPStatus.OK, content=struct.pack('i', 2) + b'wobble'),
|
|
41
41
|
MagicMock(name="mock_response_3", status_code=HTTPStatus.NO_CONTENT, content=b'')]
|
|
42
42
|
|
|
43
|
-
assert chunked_transfer.download_chunks(
|
|
43
|
+
assert chunked_transfer.download_chunks(
|
|
44
|
+
api_address='wibble', chunk_set_uuid='rawr', logger=MagicMock(name="mock_logger")) == [
|
|
44
45
|
(1, b'wibble'), (2, b'wobble')]
|
|
45
46
|
|
|
46
47
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|