futurehouse-client 0.3.19.dev111__tar.gz → 0.3.19.dev133__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.
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/PKG-INFO +1 -1
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client/clients/rest_client.py +179 -214
- futurehouse_client-0.3.19.dev133/futurehouse_client/utils/__init__.py +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client.egg-info/PKG-INFO +1 -1
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client.egg-info/SOURCES.txt +1 -0
- futurehouse_client-0.3.19.dev133/tests/test_rest.py +684 -0
- futurehouse_client-0.3.19.dev111/tests/test_rest.py +0 -235
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/LICENSE +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/README.md +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/docs/__init__.py +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/docs/client_notebook.ipynb +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client/__init__.py +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client/clients/__init__.py +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client/clients/job_client.py +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client/models/__init__.py +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client/models/app.py +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client/models/client.py +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client/models/rest.py +0 -0
- /futurehouse_client-0.3.19.dev111/futurehouse_client/utils/__init__.py → /futurehouse_client-0.3.19.dev133/futurehouse_client/py.typed +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client/utils/auth.py +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client/utils/general.py +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client/utils/module_utils.py +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client/utils/monitoring.py +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client.egg-info/dependency_links.txt +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client.egg-info/requires.txt +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/futurehouse_client.egg-info/top_level.txt +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/pyproject.toml +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/setup.cfg +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/tests/test_client.py +0 -0
- {futurehouse_client-0.3.19.dev111 → futurehouse_client-0.3.19.dev133}/uv.lock +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: futurehouse-client
|
3
|
-
Version: 0.3.19.
|
3
|
+
Version: 0.3.19.dev133
|
4
4
|
Summary: A client for interacting with endpoints of the FutureHouse service.
|
5
5
|
Author-email: FutureHouse technical staff <hello@futurehouse.org>
|
6
6
|
Classifier: Operating System :: OS Independent
|
@@ -444,37 +444,36 @@ class RestClient:
|
|
444
444
|
self, task_id: str | None = None, history: bool = False, verbose: bool = False
|
445
445
|
) -> "TaskResponse":
|
446
446
|
"""Get details for a specific task."""
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
full_url = f"{self.base_url}{url}"
|
451
|
-
|
452
|
-
with (
|
453
|
-
external_trace(
|
454
|
-
url=full_url,
|
455
|
-
method="GET",
|
456
|
-
library="httpx",
|
457
|
-
custom_params={
|
458
|
-
"operation": "get_job",
|
459
|
-
"job_id": task_id,
|
460
|
-
},
|
461
|
-
),
|
462
|
-
self.client.stream("GET", url, params={"history": history}) as response,
|
463
|
-
):
|
464
|
-
response.raise_for_status()
|
465
|
-
json_data = "".join(response.iter_text(chunk_size=1024))
|
466
|
-
data = json.loads(json_data)
|
467
|
-
if "id" not in data:
|
468
|
-
data["id"] = task_id
|
469
|
-
verbose_response = TaskResponseVerbose(**data)
|
447
|
+
task_id = task_id or self.trajectory_id
|
448
|
+
url = f"/v0.1/trajectories/{task_id}"
|
449
|
+
full_url = f"{self.base_url}{url}"
|
470
450
|
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
451
|
+
with (
|
452
|
+
external_trace(
|
453
|
+
url=full_url,
|
454
|
+
method="GET",
|
455
|
+
library="httpx",
|
456
|
+
custom_params={
|
457
|
+
"operation": "get_job",
|
458
|
+
"job_id": task_id,
|
459
|
+
},
|
460
|
+
),
|
461
|
+
self.client.stream("GET", url, params={"history": history}) as response,
|
462
|
+
):
|
463
|
+
if response.status_code in {401, 403}:
|
464
|
+
raise PermissionError(
|
465
|
+
f"Error getting task: Permission denied for task {task_id}"
|
466
|
+
)
|
467
|
+
response.raise_for_status()
|
468
|
+
json_data = "".join(response.iter_text(chunk_size=1024))
|
469
|
+
data = json.loads(json_data)
|
470
|
+
if "id" not in data:
|
471
|
+
data["id"] = task_id
|
472
|
+
verbose_response = TaskResponseVerbose(**data)
|
473
|
+
|
474
|
+
if verbose:
|
475
|
+
return verbose_response
|
476
|
+
return JobNames.get_response_object_from_job(verbose_response.job_name)(**data)
|
478
477
|
|
479
478
|
@retry(
|
480
479
|
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
@@ -485,39 +484,36 @@ class RestClient:
|
|
485
484
|
self, task_id: str | None = None, history: bool = False, verbose: bool = False
|
486
485
|
) -> "TaskResponse":
|
487
486
|
"""Get details for a specific task asynchronously."""
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
487
|
+
task_id = task_id or self.trajectory_id
|
488
|
+
url = f"/v0.1/trajectories/{task_id}"
|
489
|
+
full_url = f"{self.base_url}{url}"
|
490
|
+
|
491
|
+
with external_trace(
|
492
|
+
url=full_url,
|
493
|
+
method="GET",
|
494
|
+
library="httpx",
|
495
|
+
custom_params={
|
496
|
+
"operation": "get_job",
|
497
|
+
"job_id": task_id,
|
498
|
+
},
|
499
|
+
):
|
500
|
+
async with self.async_client.stream(
|
501
|
+
"GET", url, params={"history": history}
|
502
|
+
) as response:
|
503
|
+
if response.status_code in {401, 403}:
|
504
|
+
raise PermissionError(
|
505
|
+
f"Error getting task: Permission denied for task {task_id}"
|
506
|
+
)
|
507
|
+
response.raise_for_status()
|
508
|
+
json_data = "".join([chunk async for chunk in response.aiter_text()])
|
509
|
+
data = json.loads(json_data)
|
510
|
+
if "id" not in data:
|
511
|
+
data["id"] = task_id
|
512
|
+
verbose_response = TaskResponseVerbose(**data)
|
492
513
|
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
library="httpx",
|
497
|
-
custom_params={
|
498
|
-
"operation": "get_job",
|
499
|
-
"job_id": task_id,
|
500
|
-
},
|
501
|
-
):
|
502
|
-
async with self.async_client.stream(
|
503
|
-
"GET", url, params={"history": history}
|
504
|
-
) as response:
|
505
|
-
response.raise_for_status()
|
506
|
-
json_data = "".join([
|
507
|
-
chunk async for chunk in response.aiter_text()
|
508
|
-
])
|
509
|
-
data = json.loads(json_data)
|
510
|
-
if "id" not in data:
|
511
|
-
data["id"] = task_id
|
512
|
-
verbose_response = TaskResponseVerbose(**data)
|
513
|
-
|
514
|
-
if verbose:
|
515
|
-
return verbose_response
|
516
|
-
return JobNames.get_response_object_from_job(verbose_response.job_name)(
|
517
|
-
**data
|
518
|
-
)
|
519
|
-
except Exception as e:
|
520
|
-
raise TaskFetchError(f"Error getting task: {e!s}") from e
|
514
|
+
if verbose:
|
515
|
+
return verbose_response
|
516
|
+
return JobNames.get_response_object_from_job(verbose_response.job_name)(**data)
|
521
517
|
|
522
518
|
@retry(
|
523
519
|
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
@@ -535,15 +531,16 @@ class RestClient:
|
|
535
531
|
self.stage,
|
536
532
|
)
|
537
533
|
|
538
|
-
|
539
|
-
|
540
|
-
|
534
|
+
response = self.client.post(
|
535
|
+
"/v0.1/crows", json=task_data.model_dump(mode="json")
|
536
|
+
)
|
537
|
+
if response.status_code in {401, 403}:
|
538
|
+
raise PermissionError(
|
539
|
+
f"Error creating task: Permission denied for task {task_data.name}"
|
541
540
|
)
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
except Exception as e:
|
546
|
-
raise TaskFetchError(f"Error creating task: {e!s}") from e
|
541
|
+
response.raise_for_status()
|
542
|
+
trajectory_id = response.json()["trajectory_id"]
|
543
|
+
self.trajectory_id = trajectory_id
|
547
544
|
return trajectory_id
|
548
545
|
|
549
546
|
@retry(
|
@@ -561,16 +558,16 @@ class RestClient:
|
|
561
558
|
task_data.name.name,
|
562
559
|
self.stage,
|
563
560
|
)
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
561
|
+
response = await self.async_client.post(
|
562
|
+
"/v0.1/crows", json=task_data.model_dump(mode="json")
|
563
|
+
)
|
564
|
+
if response.status_code in {401, 403}:
|
565
|
+
raise PermissionError(
|
566
|
+
f"Error creating task: Permission denied for task {task_data.name}"
|
568
567
|
)
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
except Exception as e:
|
573
|
-
raise TaskFetchError(f"Error creating task: {e!s}") from e
|
568
|
+
response.raise_for_status()
|
569
|
+
trajectory_id = response.json()["trajectory_id"]
|
570
|
+
self.trajectory_id = trajectory_id
|
574
571
|
return trajectory_id
|
575
572
|
|
576
573
|
async def arun_tasks_until_done(
|
@@ -1056,24 +1053,11 @@ class RestClient:
|
|
1056
1053
|
status_url = None
|
1057
1054
|
|
1058
1055
|
try:
|
1059
|
-
|
1060
|
-
if total_chunks > 1:
|
1061
|
-
self._upload_chunks_parallel(
|
1062
|
-
job_name,
|
1063
|
-
file_path,
|
1064
|
-
file_name,
|
1065
|
-
upload_id,
|
1066
|
-
total_chunks - 1,
|
1067
|
-
total_chunks,
|
1068
|
-
)
|
1069
|
-
|
1070
|
-
# Upload the last chunk separately (handles assembly)
|
1071
|
-
status_url = self._upload_final_chunk(
|
1056
|
+
status_url = self._upload_chunks_parallel(
|
1072
1057
|
job_name,
|
1073
1058
|
file_path,
|
1074
1059
|
file_name,
|
1075
1060
|
upload_id,
|
1076
|
-
total_chunks - 1,
|
1077
1061
|
total_chunks,
|
1078
1062
|
)
|
1079
1063
|
|
@@ -1089,149 +1073,74 @@ class RestClient:
|
|
1089
1073
|
file_path: Path,
|
1090
1074
|
file_name: str,
|
1091
1075
|
upload_id: str,
|
1092
|
-
num_regular_chunks: int,
|
1093
1076
|
total_chunks: int,
|
1094
|
-
) -> None:
|
1095
|
-
"""Upload chunks in parallel batches.
|
1077
|
+
) -> str | None:
|
1078
|
+
"""Upload all chunks in parallel batches, including the final chunk.
|
1096
1079
|
|
1097
1080
|
Args:
|
1098
1081
|
job_name: The key of the crow to upload to.
|
1099
1082
|
file_path: The path to the file to upload.
|
1100
1083
|
file_name: The name to use for the file.
|
1101
1084
|
upload_id: The upload ID to use.
|
1102
|
-
num_regular_chunks: Number of regular chunks (excluding final chunk).
|
1103
1085
|
total_chunks: Total number of chunks.
|
1104
1086
|
|
1105
|
-
|
1106
|
-
|
1107
|
-
"""
|
1108
|
-
if num_regular_chunks <= 0:
|
1109
|
-
return
|
1110
|
-
|
1111
|
-
# Process chunks in batches
|
1112
|
-
for batch_start in range(0, num_regular_chunks, self.MAX_CONCURRENT_CHUNKS):
|
1113
|
-
batch_end = min(
|
1114
|
-
batch_start + self.MAX_CONCURRENT_CHUNKS, num_regular_chunks
|
1115
|
-
)
|
1116
|
-
|
1117
|
-
# Upload chunks in this batch concurrently
|
1118
|
-
with ThreadPoolExecutor(max_workers=self.MAX_CONCURRENT_CHUNKS) as executor:
|
1119
|
-
futures = {
|
1120
|
-
executor.submit(
|
1121
|
-
self._upload_single_chunk,
|
1122
|
-
job_name,
|
1123
|
-
file_path,
|
1124
|
-
file_name,
|
1125
|
-
upload_id,
|
1126
|
-
chunk_index,
|
1127
|
-
total_chunks,
|
1128
|
-
): chunk_index
|
1129
|
-
for chunk_index in range(batch_start, batch_end)
|
1130
|
-
}
|
1131
|
-
|
1132
|
-
for future in as_completed(futures):
|
1133
|
-
chunk_index = futures[future]
|
1134
|
-
try:
|
1135
|
-
future.result()
|
1136
|
-
logger.debug(
|
1137
|
-
f"Uploaded chunk {chunk_index + 1}/{total_chunks} of {file_name}"
|
1138
|
-
)
|
1139
|
-
except Exception as e:
|
1140
|
-
logger.error(f"Error uploading chunk {chunk_index}: {e}")
|
1141
|
-
raise FileUploadError(
|
1142
|
-
f"Error uploading chunk {chunk_index} of {file_name}: {e}"
|
1143
|
-
) from e
|
1144
|
-
|
1145
|
-
def _upload_single_chunk(
|
1146
|
-
self,
|
1147
|
-
job_name: str,
|
1148
|
-
file_path: Path,
|
1149
|
-
file_name: str,
|
1150
|
-
upload_id: str,
|
1151
|
-
chunk_index: int,
|
1152
|
-
total_chunks: int,
|
1153
|
-
) -> None:
|
1154
|
-
"""Upload a single chunk.
|
1155
|
-
|
1156
|
-
Args:
|
1157
|
-
job_name: The key of the crow to upload to.
|
1158
|
-
file_path: The path to the file to upload.
|
1159
|
-
file_name: The name to use for the file.
|
1160
|
-
upload_id: The upload ID to use.
|
1161
|
-
chunk_index: The index of this chunk.
|
1162
|
-
total_chunks: Total number of chunks.
|
1087
|
+
Returns:
|
1088
|
+
The status URL from the final chunk response, or None if no chunks.
|
1163
1089
|
|
1164
1090
|
Raises:
|
1165
|
-
|
1091
|
+
FileUploadError: If there's an error uploading any chunk.
|
1166
1092
|
"""
|
1167
|
-
|
1168
|
-
|
1169
|
-
f.seek(chunk_index * self.CHUNK_SIZE)
|
1170
|
-
chunk_data = f.read(self.CHUNK_SIZE)
|
1093
|
+
if total_chunks <= 0:
|
1094
|
+
return None
|
1171
1095
|
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1096
|
+
if total_chunks > 1:
|
1097
|
+
num_regular_chunks = total_chunks - 1
|
1098
|
+
for batch_start in range(0, num_regular_chunks, self.MAX_CONCURRENT_CHUNKS):
|
1099
|
+
batch_end = min(
|
1100
|
+
batch_start + self.MAX_CONCURRENT_CHUNKS, num_regular_chunks
|
1101
|
+
)
|
1176
1102
|
|
1177
|
-
#
|
1178
|
-
with
|
1179
|
-
|
1180
|
-
|
1103
|
+
# Upload chunks in this batch concurrently
|
1104
|
+
with ThreadPoolExecutor(
|
1105
|
+
max_workers=self.MAX_CONCURRENT_CHUNKS
|
1106
|
+
) as executor:
|
1107
|
+
futures = {
|
1108
|
+
executor.submit(
|
1109
|
+
self._upload_single_chunk,
|
1110
|
+
job_name,
|
1111
|
+
file_path,
|
1181
1112
|
file_name,
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
"file_name": file_name,
|
1188
|
-
"chunk_index": chunk_index,
|
1189
|
-
"total_chunks": total_chunks,
|
1190
|
-
"upload_id": upload_id,
|
1113
|
+
upload_id,
|
1114
|
+
chunk_index,
|
1115
|
+
total_chunks,
|
1116
|
+
): chunk_index
|
1117
|
+
for chunk_index in range(batch_start, batch_end)
|
1191
1118
|
}
|
1192
1119
|
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
total_chunks: int,
|
1209
|
-
) -> str | None:
|
1210
|
-
"""Upload the final chunk with retry logic for missing chunks.
|
1211
|
-
|
1212
|
-
Args:
|
1213
|
-
job_name: The key of the crow to upload to.
|
1214
|
-
file_path: The path to the file to upload.
|
1215
|
-
file_name: The name to use for the file.
|
1216
|
-
upload_id: The upload ID to use.
|
1217
|
-
chunk_index: The index of the final chunk.
|
1218
|
-
total_chunks: Total number of chunks.
|
1219
|
-
|
1220
|
-
Returns:
|
1221
|
-
The status URL from the response.
|
1222
|
-
|
1223
|
-
Raises:
|
1224
|
-
FileUploadError: If there's an error uploading the final chunk.
|
1225
|
-
"""
|
1120
|
+
for future in as_completed(futures):
|
1121
|
+
chunk_index = futures[future]
|
1122
|
+
try:
|
1123
|
+
future.result()
|
1124
|
+
logger.debug(
|
1125
|
+
f"Uploaded chunk {chunk_index + 1}/{total_chunks} of {file_name}"
|
1126
|
+
)
|
1127
|
+
except Exception as e:
|
1128
|
+
logger.error(f"Error uploading chunk {chunk_index}: {e}")
|
1129
|
+
raise FileUploadError(
|
1130
|
+
f"Error uploading chunk {chunk_index} of {file_name}: {e}"
|
1131
|
+
) from e
|
1132
|
+
|
1133
|
+
# Upload the final chunk with retry logic
|
1134
|
+
final_chunk_index = total_chunks - 1
|
1226
1135
|
retries = 0
|
1227
1136
|
max_retries = 3
|
1228
|
-
retry_delay = 2.0
|
1137
|
+
retry_delay = 2.0
|
1229
1138
|
|
1230
1139
|
while retries < max_retries:
|
1231
1140
|
try:
|
1232
1141
|
with open(file_path, "rb") as f:
|
1233
1142
|
# Read the final chunk from the file
|
1234
|
-
f.seek(
|
1143
|
+
f.seek(final_chunk_index * self.CHUNK_SIZE)
|
1235
1144
|
chunk_data = f.read(self.CHUNK_SIZE)
|
1236
1145
|
|
1237
1146
|
# Prepare and send the chunk
|
@@ -1250,7 +1159,7 @@ class RestClient:
|
|
1250
1159
|
}
|
1251
1160
|
data = {
|
1252
1161
|
"file_name": file_name,
|
1253
|
-
"chunk_index":
|
1162
|
+
"chunk_index": final_chunk_index,
|
1254
1163
|
"total_chunks": total_chunks,
|
1255
1164
|
"upload_id": upload_id,
|
1256
1165
|
}
|
@@ -1277,7 +1186,7 @@ class RestClient:
|
|
1277
1186
|
status_url = response_data.get("status_url")
|
1278
1187
|
|
1279
1188
|
logger.debug(
|
1280
|
-
f"Uploaded final chunk {
|
1189
|
+
f"Uploaded final chunk {final_chunk_index + 1}/{total_chunks} of {file_name}"
|
1281
1190
|
)
|
1282
1191
|
return status_url
|
1283
1192
|
|
@@ -1296,6 +1205,62 @@ class RestClient:
|
|
1296
1205
|
f"Failed to upload final chunk of {file_name} after {max_retries} retries"
|
1297
1206
|
)
|
1298
1207
|
|
1208
|
+
def _upload_single_chunk(
|
1209
|
+
self,
|
1210
|
+
job_name: str,
|
1211
|
+
file_path: Path,
|
1212
|
+
file_name: str,
|
1213
|
+
upload_id: str,
|
1214
|
+
chunk_index: int,
|
1215
|
+
total_chunks: int,
|
1216
|
+
) -> None:
|
1217
|
+
"""Upload a single chunk.
|
1218
|
+
|
1219
|
+
Args:
|
1220
|
+
job_name: The key of the crow to upload to.
|
1221
|
+
file_path: The path to the file to upload.
|
1222
|
+
file_name: The name to use for the file.
|
1223
|
+
upload_id: The upload ID to use.
|
1224
|
+
chunk_index: The index of this chunk.
|
1225
|
+
total_chunks: Total number of chunks.
|
1226
|
+
|
1227
|
+
Raises:
|
1228
|
+
Exception: If there's an error uploading the chunk.
|
1229
|
+
"""
|
1230
|
+
with open(file_path, "rb") as f:
|
1231
|
+
# Read the chunk from the file
|
1232
|
+
f.seek(chunk_index * self.CHUNK_SIZE)
|
1233
|
+
chunk_data = f.read(self.CHUNK_SIZE)
|
1234
|
+
|
1235
|
+
# Prepare and send the chunk
|
1236
|
+
with tempfile.NamedTemporaryFile() as temp_file:
|
1237
|
+
temp_file.write(chunk_data)
|
1238
|
+
temp_file.flush()
|
1239
|
+
|
1240
|
+
# Create form data
|
1241
|
+
with open(temp_file.name, "rb") as chunk_file_obj:
|
1242
|
+
files = {
|
1243
|
+
"chunk": (
|
1244
|
+
file_name,
|
1245
|
+
chunk_file_obj,
|
1246
|
+
"application/octet-stream",
|
1247
|
+
)
|
1248
|
+
}
|
1249
|
+
data = {
|
1250
|
+
"file_name": file_name,
|
1251
|
+
"chunk_index": chunk_index,
|
1252
|
+
"total_chunks": total_chunks,
|
1253
|
+
"upload_id": upload_id,
|
1254
|
+
}
|
1255
|
+
|
1256
|
+
# Send the chunk
|
1257
|
+
response = self.multipart_client.post(
|
1258
|
+
f"/v0.1/crows/{job_name}/upload-chunk",
|
1259
|
+
files=files,
|
1260
|
+
data=data,
|
1261
|
+
)
|
1262
|
+
response.raise_for_status()
|
1263
|
+
|
1299
1264
|
@retry(
|
1300
1265
|
stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
|
1301
1266
|
wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
|
File without changes
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: futurehouse-client
|
3
|
-
Version: 0.3.19.
|
3
|
+
Version: 0.3.19.dev133
|
4
4
|
Summary: A client for interacting with endpoints of the FutureHouse service.
|
5
5
|
Author-email: FutureHouse technical staff <hello@futurehouse.org>
|
6
6
|
Classifier: Operating System :: OS Independent
|