@@ -105,7 +105,7 @@ def __init__(
105105 # Public attributes (set via public properties)
106106 self .download_dir = download_dir
107107 self .api_rate_limit = TDMClient .API_RATE_LIMIT
108- self .download_results_errors_only : bool = False
108+ self .only_record_errors : bool = False
109109 self .skip_existing_files : bool = True
110110
111111 # Private attributes
@@ -114,7 +114,7 @@ def __init__(
114114 self ._api_token : str
115115 self ._api_headers : dict [str , str ] = {}
116116 self ._api_session : requests .Session
117- self ._download_results : List [DownloadResult ] = []
117+ self ._results : List [DownloadResult ] = []
118118
119119 self ._validate_api_token (api_token )
120120 self ._create_api_session ()
@@ -260,9 +260,57 @@ def download_pdf(self, doi: str) -> DownloadResult:
260260 duration = time .perf_counter () - start_time
261261 result .duration = duration
262262
263- self ._add_download_result (result )
263+ self ._add_result (result )
264264 return result
265265
266+ def download_pdfs (
267+ self ,
268+ dois : Union [List [str ], StrPath ],
269+ on_result : Optional [Callable [[DownloadResult ], None ]] = None ,
270+ ) -> List [DownloadResult ]:
271+ """Download multiple PDFs from either a list of DOIs or a file containing DOIs.
272+
273+ Args:
274+ dois: Either a list of DOI strings or a path to a file containing DOIs (one per line)
275+ on_result: Optional callback function called after each download
276+ Relative paths are resolved from the current working directory
277+
278+ Returns:
279+ List[DownloadResult]: List of download results
280+
281+ Raises:
282+ FileNotFoundError: If dois is a file path that doesn't exist
283+ ValueError: If dois list is empty
284+ """
285+ # Check if `dois` is a string or Path (matches StrPath type alias)
286+ if isinstance (dois , (str , Path )):
287+ dois_list : List [str ] = FileUtils .read_lines_from_file (dois )
288+ self .logger .info ("Found %d DOIs in %s" , len (dois_list ), dois )
289+ # Explicitly check that `dois` is a list of strings
290+ elif isinstance (dois , list ):
291+ dois_list : List [str ] = dois
292+ else :
293+ raise TypeError ("Invalid type for `dois`. Expected List[str] or StrPath." )
294+
295+ if not dois_list :
296+ raise ValueError ("DOIs list cannot be empty" )
297+
298+ unique_dois : List [str ] = DOIUtils .dedupe (dois_list )
299+ results : List [DownloadResult ] = []
300+
301+ for doi in unique_dois :
302+ result = self .download_pdf (doi )
303+ if result :
304+ results .append (result )
305+ if on_result :
306+ on_result (result )
307+ if result .status != DownloadStatus .EXISTING_FILE :
308+ self .logger .debug (
309+ "Rate limiting: sleeping for %s seconds" , self .api_rate_limit
310+ )
311+ time .sleep (self .api_rate_limit )
312+ return results
313+
266314 def _download_pdf (self , doi : str ) -> DownloadResult :
267315 """Download an article PDF given its DOI.
268316
@@ -358,54 +406,7 @@ def _save_pdf(self, response: requests.Response, doi: str) -> DownloadResult:
358406 except IOError as e :
359407 return DownloadResult (doi , DownloadStatus .STORAGE_ERROR , str (e ), pdf_path )
360408
361- def download_pdfs (
362- self ,
363- dois : Union [List [str ], StrPath ],
364- on_result : Optional [Callable [[DownloadResult ], None ]] = None ,
365- ) -> List [DownloadResult ]:
366- """Download multiple PDFs from either a list of DOIs or a file containing DOIs.
367-
368- Args:
369- dois: Either a list of DOI strings or a path to a file containing DOIs (one per line)
370- on_result: Optional callback function called after each download
371-
372- Returns:
373- List[DownloadResult]: List of download results
374-
375- Raises:
376- FileNotFoundError: If dois is a file path that doesn't exist
377- ValueError: If dois list is empty
378- """
379- # Check if `dois` is a string or Path (matches StrPath type alias)
380- if isinstance (dois , (str , Path )):
381- dois_list : List [str ] = FileUtils .read_lines_from_file (dois )
382- self .logger .info ("Found %d DOIs in %s" , len (dois_list ), dois )
383- # Explicitly check that `dois` is a list of strings
384- elif isinstance (dois , list ):
385- dois_list : List [str ] = dois
386- else :
387- raise TypeError ("Invalid type for `dois`. Expected List[str] or StrPath." )
388-
389- if not dois_list :
390- raise ValueError ("DOIs list cannot be empty" )
391-
392- unique_dois : List [str ] = DOIUtils .dedupe (dois_list )
393- results : List [DownloadResult ] = []
394-
395- for doi in unique_dois :
396- result = self .download_pdf (doi )
397- if result :
398- results .append (result )
399- if on_result :
400- on_result (result )
401- if result .status != DownloadStatus .EXISTING_FILE :
402- self .logger .debug (
403- "Rate limiting: sleeping for %s seconds" , self .api_rate_limit
404- )
405- time .sleep (self .api_rate_limit )
406- return results
407-
408- def _add_download_result (self , result : DownloadResult ) -> None :
409+ def _add_result (self , result : DownloadResult ) -> None :
409410 """Record download result.
410411
411412 Args:
@@ -415,37 +416,35 @@ def _add_download_result(self, result: DownloadResult) -> None:
415416 message = f"DOI: { result .doi } - { result .status } "
416417
417418 if result .status in (DownloadStatus .SUCCESS , DownloadStatus .EXISTING_FILE ):
418- if self .download_results_errors_only :
419+ if self .only_record_errors :
419420 return
420421 self .logger .info (message )
421422 else :
422423 self .logger .error (message )
423424
424- self ._download_results .append (result )
425+ self ._results .append (result )
425426
426427 @property
427- def download_results (self ) -> List [DownloadResult ]:
428+ def results (self ) -> List [DownloadResult ]:
428429 """Get the current download results.
429430
430431 Returns:
431432 List[DownloadResult]: List of all download attempts and their results
432433 """
433- return (
434- self ._download_results .copy ()
435- ) # Return copy to prevent external modification
434+ return self ._results .copy () # Return copy to prevent external modification
436435
437- def save_download_results (self , csv_path : StrPath = RESULTS_FILE ) -> None :
436+ def save_results (self , csv_path : StrPath = RESULTS_FILE ) -> None :
438437 """Save download results to CSV file. Defaults to 'results.csv'
439438
440439 Args:
441440 csv_path: Path where CSV file will be saved. Can be either:
442441 - A string path
443442 - A PathLike object (recommended)
444- Relative paths are resolved from the current working directory.
443+ Relative paths are resolved from the current working directory
445444 Returns:
446445 None
447446
448447 Raises:
449448 OSError: If the file cannot be written due to permissions or other OS issues
450449 """
451- TDMReporting .save_download_results (self ._download_results , csv_path )
450+ TDMReporting .save_results (self ._results , csv_path )
0 commit comments