Skip to content

Commit 2932f34

Browse files
authored
Standardize requests made by DOIDownloaders (#514)
Respect user's decisions when defining the `DOIDownloader` with respect to arguments passed to `requests.get` whenever we call that function. This way, all calls made by `DOIDownloaders` and the repository classes make use of the same arguments, including `timeout`, `headers`, etc.
1 parent d2b547e commit 2932f34

2 files changed

Lines changed: 64 additions & 30 deletions

File tree

pooch/core.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,12 @@ def load_registry_from_doi(self) -> None:
707707

708708
# Create a repository instance
709709
doi = self.base_url.replace("doi:", "")
710-
repository = doi_to_repository(doi)
710+
repository = doi_to_repository(
711+
doi,
712+
headers=downloader.headers,
713+
timeout=downloader.timeout,
714+
**downloader.kwargs,
715+
)
711716

712717
# Call registry population for this repository
713718
return repository.populate_registry(self)

pooch/downloaders.py

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -603,9 +603,17 @@ class DOIDownloader: # pylint: disable=too-few-public-methods
603603
604604
"""
605605

606-
def __init__(self, progressbar=False, chunk_size=1024, headers=None, **kwargs):
606+
def __init__(
607+
self,
608+
progressbar=False,
609+
chunk_size=1024,
610+
headers=None,
611+
timeout=DEFAULT_TIMEOUT,
612+
**kwargs,
613+
):
607614
self.kwargs = kwargs
608615
self.headers = headers if headers is not None else REQUESTS_HEADERS
616+
self.timeout = timeout
609617
self.progressbar = progressbar
610618
self.chunk_size = chunk_size
611619

@@ -630,7 +638,13 @@ def __call__(self, url, output_file, pooch):
630638
"""
631639

632640
parsed_url = parse_url(url)
633-
data_repository = doi_to_repository(parsed_url["netloc"])
641+
642+
data_repository = doi_to_repository(
643+
parsed_url["netloc"],
644+
headers=self.headers,
645+
timeout=self.timeout,
646+
**self.kwargs,
647+
)
634648

635649
# Resolve the URL
636650
file_name = parsed_url["path"]
@@ -644,19 +658,22 @@ def __call__(self, url, output_file, pooch):
644658
progressbar=self.progressbar,
645659
chunk_size=self.chunk_size,
646660
headers=self.headers,
661+
timeout=self.timeout,
647662
**self.kwargs,
648663
)
649664
downloader(download_url, output_file, pooch)
650665

651666

652-
def doi_to_url(doi):
667+
def doi_to_url(doi, **kwargs):
653668
"""
654669
Follow a DOI link to resolve the URL of the archive.
655670
656671
Parameters
657672
----------
658673
doi : str
659674
The DOI of the archive.
675+
**kwargs
676+
All keyword arguments will be passed to :func:`requests.get`.
660677
661678
Returns
662679
-------
@@ -669,15 +686,16 @@ def doi_to_url(doi):
669686

670687
# Use doi.org to resolve the DOI to the repository website.
671688
response = requests.get(
672-
f"https://doi.org/{doi}", headers=REQUESTS_HEADERS, timeout=DEFAULT_TIMEOUT
689+
f"https://doi.org/{doi}",
690+
**kwargs,
673691
)
674692
url = response.url
675693
if 400 <= response.status_code < 600:
676694
response.raise_for_status()
677695
return url
678696

679697

680-
def doi_to_repository(doi):
698+
def doi_to_repository(doi, **kwargs):
681699
"""
682700
Instantiate a data repository instance from a given DOI.
683701
@@ -688,6 +706,10 @@ def doi_to_repository(doi):
688706
----------
689707
doi : str
690708
The DOI of the archive.
709+
**kwargs
710+
All keyword arguments will be passed also as ``**kwargs`` to the
711+
:meth:`DataRepository.initialize` method, that will ultimately get
712+
passed to :func:`requests.get`.
691713
692714
Returns
693715
-------
@@ -708,7 +730,7 @@ def doi_to_repository(doi):
708730
]
709731

710732
# Extract the DOI and the repository information
711-
archive_url = doi_to_url(doi)
733+
archive_url = doi_to_url(doi, **kwargs)
712734

713735
# Try the converters one by one until one of them returned a URL
714736
data_repository = None
@@ -717,6 +739,7 @@ def doi_to_repository(doi):
717739
data_repository = repo.initialize(
718740
archive_url=archive_url,
719741
doi=doi,
742+
**kwargs,
720743
)
721744

722745
if data_repository is None:
@@ -732,7 +755,7 @@ def doi_to_repository(doi):
732755

733756
class DataRepository: # pylint: disable=too-few-public-methods, missing-class-docstring
734757
@classmethod
735-
def initialize(cls, doi, archive_url): # pylint: disable=unused-argument
758+
def initialize(cls, doi, archive_url, **kwargs): # pylint: disable=unused-argument
736759
"""
737760
Initialize the data repository if the given URL points to a
738761
corresponding repository.
@@ -786,14 +809,15 @@ def populate_registry(self, pooch):
786809
class ZenodoRepository(DataRepository): # pylint: disable=missing-class-docstring
787810
base_api_url = "https://zenodo.org/api/records"
788811

789-
def __init__(self, doi, archive_url):
812+
def __init__(self, doi, archive_url, **kwargs):
790813
self.archive_url = archive_url
791814
self.doi = doi
792815
self._api_response = None
793816
self._api_version = None
817+
self.kwargs = kwargs
794818

795819
@classmethod
796-
def initialize(cls, doi, archive_url):
820+
def initialize(cls, doi, archive_url, **kwargs):
797821
"""
798822
Initialize the data repository if the given URL points to a
799823
corresponding repository.
@@ -809,14 +833,17 @@ def initialize(cls, doi, archive_url):
809833
The DOI that identifies the repository
810834
archive_url : str
811835
The resolved URL for the DOI
836+
**kwargs
837+
All keyword arguments given when creating an instance of this class
838+
will be passed to :func:`requests.get`.
812839
"""
813840

814841
# Check whether this is a Zenodo URL
815842
parsed_archive_url = parse_url(archive_url)
816843
if parsed_archive_url["netloc"] != "zenodo.org":
817844
return None
818845

819-
return cls(doi, archive_url)
846+
return cls(doi, archive_url, **kwargs)
820847

821848
@property
822849
def api_response(self):
@@ -827,9 +854,7 @@ def api_response(self):
827854

828855
article_id = self.archive_url.split("/")[-1]
829856
self._api_response = requests.get(
830-
f"{self.base_api_url}/{article_id}",
831-
headers=REQUESTS_HEADERS,
832-
timeout=DEFAULT_TIMEOUT,
857+
f"{self.base_api_url}/{article_id}", **self.kwargs
833858
).json()
834859

835860
return self._api_response
@@ -938,13 +963,14 @@ def populate_registry(self, pooch):
938963

939964

940965
class FigshareRepository(DataRepository): # pylint: disable=missing-class-docstring
941-
def __init__(self, doi, archive_url):
966+
def __init__(self, doi, archive_url, **kwargs):
942967
self.archive_url = archive_url
943968
self.doi = doi
944969
self._api_response = None
970+
self.kwargs = kwargs
945971

946972
@classmethod
947-
def initialize(cls, doi, archive_url):
973+
def initialize(cls, doi, archive_url, **kwargs):
948974
"""
949975
Initialize the data repository if the given URL points to a
950976
corresponding repository.
@@ -960,14 +986,17 @@ def initialize(cls, doi, archive_url):
960986
The DOI that identifies the repository
961987
archive_url : str
962988
The resolved URL for the DOI
989+
**kwargs
990+
All keyword arguments given when creating an instance of this class
991+
will be passed to :func:`requests.get`.
963992
"""
964993

965994
# Check whether this is a Figshare URL
966995
parsed_archive_url = parse_url(archive_url)
967996
if parsed_archive_url["netloc"] != "figshare.com":
968997
return None
969998

970-
return cls(doi, archive_url)
999+
return cls(doi, archive_url, **kwargs)
9711000

9721001
def _parse_version_from_doi(self):
9731002
"""
@@ -995,8 +1024,7 @@ def api_response(self):
9951024
# Use the figshare API to find the article ID from the DOI
9961025
article = requests.get(
9971026
f"https://api.figshare.com/v2/articles?doi={self.doi}",
998-
headers=REQUESTS_HEADERS,
999-
timeout=DEFAULT_TIMEOUT,
1027+
**self.kwargs,
10001028
).json()[0]
10011029
article_id = article["id"]
10021030
# Parse desired version from the doi
@@ -1023,9 +1051,7 @@ def api_response(self):
10231051
f"{article_id}/versions/{version}"
10241052
)
10251053
# Make the request and return the files in the figshare repository
1026-
response = requests.get(
1027-
api_url, headers=REQUESTS_HEADERS, timeout=DEFAULT_TIMEOUT
1028-
)
1054+
response = requests.get(api_url, **self.kwargs)
10291055
response.raise_for_status()
10301056
self._api_response = response.json()["files"]
10311057

@@ -1069,13 +1095,14 @@ def populate_registry(self, pooch):
10691095

10701096

10711097
class DataverseRepository(DataRepository): # pylint: disable=missing-class-docstring
1072-
def __init__(self, doi, archive_url):
1098+
def __init__(self, doi, archive_url, **kwargs):
10731099
self.archive_url = archive_url
10741100
self.doi = doi
10751101
self._api_response = None
1102+
self.kwargs = kwargs
10761103

10771104
@classmethod
1078-
def initialize(cls, doi, archive_url):
1105+
def initialize(cls, doi, archive_url, **kwargs):
10791106
"""
10801107
Initialize the data repository if the given URL points to a
10811108
corresponding repository.
@@ -1091,21 +1118,24 @@ def initialize(cls, doi, archive_url):
10911118
The DOI that identifies the repository
10921119
archive_url : str
10931120
The resolved URL for the DOI
1121+
**kwargs
1122+
All keyword arguments given when creating an instance of this class
1123+
will be passed to :func:`requests.get`.
10941124
"""
10951125
# Access the DOI as if this was a DataVerse instance
1096-
response = cls._get_api_response(doi, archive_url)
1126+
response = cls._get_api_response(doi, archive_url, **kwargs)
10971127

10981128
# If we failed, this is probably not a DataVerse instance
10991129
if 400 <= response.status_code < 600:
11001130
return None
11011131

11021132
# Initialize the repository and overwrite the api response
1103-
repository = cls(doi, archive_url)
1133+
repository = cls(doi, archive_url, **kwargs)
11041134
repository.api_response = response
11051135
return repository
11061136

11071137
@classmethod
1108-
def _get_api_response(cls, doi, archive_url):
1138+
def _get_api_response(cls, doi, archive_url, **kwargs):
11091139
"""
11101140
Perform the actual API request
11111141
@@ -1119,8 +1149,7 @@ def _get_api_response(cls, doi, archive_url):
11191149
response = requests.get(
11201150
f"{parsed['protocol']}://{parsed['netloc']}/api/datasets/"
11211151
f":persistentId?persistentId=doi:{doi}",
1122-
headers=REQUESTS_HEADERS,
1123-
timeout=DEFAULT_TIMEOUT,
1152+
**kwargs,
11241153
)
11251154
return response
11261155

@@ -1130,7 +1159,7 @@ def api_response(self):
11301159

11311160
if self._api_response is None:
11321161
self._api_response = self._get_api_response(
1133-
self.doi, self.archive_url
1162+
self.doi, self.archive_url, **self.kwargs
11341163
) # pragma: no cover
11351164

11361165
return self._api_response

0 commit comments

Comments
 (0)