Skip to content

Commit 3d17ffa

Browse files
committed
add bulk file registration
1 parent 22ec052 commit 3d17ffa

1 file changed

Lines changed: 81 additions & 55 deletions

File tree

dvuploader/directupload.py

Lines changed: 81 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@
66
from urllib.parse import urljoin
77
import aiofiles
88
import aiohttp
9-
import aiohttp_retry
109

1110
from dvuploader.file import File
12-
from dvuploader.nativeupload import file_sender
1311
from dvuploader.utils import build_url
1412

1513
TESTING = bool(os.environ.get("DVUPLOADER_TESTING", False))
@@ -24,8 +22,8 @@
2422

2523
TICKET_ENDPOINT = "/api/datasets/:persistentId/uploadurls"
2624
ADD_FILE_ENDPOINT = "/api/datasets/:persistentId/addFiles"
27-
UPLOAD_ENDPOINT = "/api/datasets/:persistentId/add?persistentId="
28-
REPLACE_ENDPOINT = "/api/files/{FILE_ID}/replace"
25+
UPLOAD_ENDPOINT = "/api/datasets/:persistentId/addFiles?persistentId="
26+
REPLACE_ENDPOINT = "/api/datasets/:persistentId/replaceFiles?persistentId="
2927

3028

3129
async def direct_upload(
@@ -84,31 +82,20 @@ async def direct_upload(
8482
"x-amz-tagging": "dv-state=temp",
8583
}
8684

87-
connector = aiohttp.TCPConnector(limit=10, force_close=True)
88-
pbar = progress.add_task("╰── [bold white]Registering files", total=len(files))
89-
results = []
85+
pbar = progress.add_task("╰── [bold white]Registering files", total=1)
86+
connector = aiohttp.TCPConnector(limit=2)
9087
async with aiohttp.ClientSession(
9188
headers=headers,
9289
connector=connector,
9390
) as session:
94-
95-
register_tasks = [
96-
_add_file_to_ds(
97-
session=session,
98-
file=file,
99-
dataverse_url=dataverse_url,
100-
pid=persistent_id,
101-
progress=progress,
102-
pbar=pbar,
103-
)
104-
for file in files
105-
]
106-
107-
results = await asyncio.gather(*register_tasks)
108-
109-
for file, status in zip(files, results):
110-
if status is False:
111-
print(f"❌ Failed to register file '{file.fileName}' at Dataverse")
91+
await _add_files_to_ds(
92+
session=session,
93+
files=files,
94+
dataverse_url=dataverse_url,
95+
pid=persistent_id,
96+
progress=progress,
97+
pbar=pbar,
98+
)
11299

113100

114101
async def _upload_to_store(
@@ -488,14 +475,14 @@ async def _abort_upload(
488475
response.raise_for_status()
489476

490477

491-
async def _add_file_to_ds(
478+
async def _add_files_to_ds(
492479
session: aiohttp.ClientSession,
493480
dataverse_url: str,
494481
pid: str,
495-
file: File,
482+
files: List[File],
496483
progress,
497484
pbar,
498-
) -> bool:
485+
) -> None:
499486
"""
500487
Adds a file to a Dataverse dataset.
501488
@@ -508,38 +495,77 @@ async def _add_file_to_ds(
508495
Returns:
509496
bool: True if the file was added successfully, False otherwise.
510497
"""
511-
if not file.to_replace:
512-
url = urljoin(dataverse_url, UPLOAD_ENDPOINT + pid)
513-
else:
514-
url = build_url(
515-
dataverse_url=dataverse_url,
516-
endpoint=urljoin(
517-
dataverse_url,
518-
REPLACE_ENDPOINT.format(FILE_ID=file.file_id),
519-
),
520-
)
521498

522-
json_data = file.model_dump_json(
523-
by_alias=True,
524-
exclude={"to_replace", "file_id"},
499+
novel_url = urljoin(dataverse_url, UPLOAD_ENDPOINT + pid)
500+
replace_url = urljoin(dataverse_url, REPLACE_ENDPOINT + pid)
501+
502+
novel_json_data = _prepare_registration(files, use_replace=False)
503+
replace_json_data = _prepare_registration(files, use_replace=True)
504+
505+
await _multipart_json_data_request(
506+
session=session,
507+
json_data=novel_json_data,
508+
url=novel_url,
509+
)
510+
511+
await _multipart_json_data_request(
512+
session=session,
513+
json_data=replace_json_data,
514+
url=replace_url,
515+
)
516+
517+
progress.update(pbar, advance=1)
518+
519+
520+
def _prepare_registration(files: List[File], use_replace: bool) -> str:
521+
"""
522+
Prepares the files for registration at the Dataverse instance.
523+
524+
Args:
525+
files (List[File]): The list of files to prepare.
526+
527+
Returns:
528+
str: A JSON string containing the file data.
529+
"""
530+
531+
exclude = {"to_replace"} if use_replace else {"to_replace", "file_id"}
532+
533+
return json.dumps(
534+
[
535+
file.model_dump(
536+
by_alias=True,
537+
exclude=exclude,
538+
exclude_none=True,
539+
)
540+
for file in files
541+
if file.to_replace is use_replace
542+
],
525543
indent=2,
526-
exclude_none=True,
527544
)
528545

546+
547+
async def _multipart_json_data_request(
548+
json_data: str,
549+
url: str,
550+
session: aiohttp.ClientSession,
551+
):
552+
"""
553+
Sends a multipart/form-data POST request with JSON data to the specified URL using the provided session.
554+
555+
Args:
556+
json_data (str): The JSON data to be sent in the request body.
557+
url (str): The URL to send the request to.
558+
session (aiohttp.ClientSession): The aiohttp client session to use for the request.
559+
560+
Raises:
561+
aiohttp.ClientResponseError: If the response status code is not successful.
562+
563+
Returns:
564+
None
565+
"""
529566
with aiohttp.MultipartWriter("form-data") as writer:
530567
json_part = writer.append(json_data)
531568
json_part.set_content_disposition("form-data", name="jsonData")
532569

533-
retry_options = aiohttp_retry.RandomRetry(attempts=MAX_RETRIES, statuses={400})
534-
retry_client = aiohttp_retry.RetryClient(
535-
client_session=session,
536-
retry_options=retry_options,
537-
)
538-
539-
async with retry_client.post(url, data=writer) as response:
540-
if response.status == 200:
541-
progress.update(pbar, advance=1)
542-
return True
543-
544-
await retry_client.close()
545-
return False
570+
async with session.post(url, data=writer) as response:
571+
response.raise_for_status()

0 commit comments

Comments
 (0)