44from typing import Any , Dict , Optional
55import httpx
66import subprocess as sp
7+ from warnings import warn
78
89from httpx import ConnectError , Response
910
11+ from pyDataverse .auth import ApiTokenAuth
1012from pyDataverse .exceptions import (
1113 ApiAuthorizationError ,
1214 ApiUrlError ,
@@ -41,6 +43,8 @@ def __init__(
4143 base_url : str ,
4244 api_token : Optional [str ] = None ,
4345 api_version : str = "latest" ,
46+ * ,
47+ auth : Optional [httpx .Auth ] = None ,
4448 ):
4549 """Init an Api() class.
4650
@@ -51,17 +55,55 @@ def __init__(
5155 ----------
5256 base_url : str
5357 Base url for Dataverse api.
54- api_token : str
55- Api token for Dataverse api.
56-
58+ api_token : str | None
59+ API token for Dataverse API. If you provide an :code:`api_token`, we
60+ assume it is an API token as retrieved via your Dataverse instance
61+ user profile.
62+ We recommend using the :code:`auth` argument instead.
63+ To retain the current behaviour with the :code:`auth` argument, change
64+
65+ .. code-block:: python
66+
67+ Api("https://demo.dataverse.org", "my_token")
68+
69+ to
70+
71+ .. code-block:: python
72+
73+ from pyDataverse.auth import ApiTokenAuth
74+
75+ Api("https://demo.dataverse.org", auth=ApiTokenAuth("my_token"))
76+
77+ If you are using an OIDC/OAuth 2.0 Bearer token, please use the :code:`auth`
78+ parameter with the :py:class:`.auth.BearerTokenAuth`.
79+ api_version : str
80+ The version string of the Dataverse API or :code:`latest`, e.g.,
81+ :code:`v1`. Defaults to :code:`latest`, which drops the version from
82+ the API urls.
83+ auth : httpx.Auth | None
84+ You can provide any authentication mechanism you like to connect to
85+ your Dataverse instance. The most common mechanisms are implemented
86+ in :py:mod:`.auth`, but if one is missing, you can use your own
87+ `httpx.Auth`-compatible class. For more information, have a look at
88+ `httpx' Authentication docs
89+ <https://www.python-httpx.org/advanced/authentication/>`_.
5790 Examples
5891 -------
5992 Create an Api connection::
6093
94+ .. code-block::
95+
6196 >>> from pyDataverse.api import Api
6297 >>> base_url = 'http://demo.dataverse.org'
6398 >>> api = Api(base_url)
6499
100+ .. code-block::
101+
102+ >>> from pyDataverse.api import Api
103+ >>> from pyDataverse.auth import ApiTokenAuth
104+ >>> base_url = 'http://demo.dataverse.org'
105+ >>> api = Api(base_url, ApiTokenAuth('my_api_token'))
106+
65107 """
66108 if not isinstance (base_url , str ):
67109 raise ApiUrlError ("base_url {0} is not a string." .format (base_url ))
@@ -73,10 +115,19 @@ def __init__(
73115 raise ApiUrlError ("api_version {0} is not a string." .format (api_version ))
74116 self .api_version = api_version
75117
76- if api_token :
77- if not isinstance (api_token , str ):
78- raise ApiAuthorizationError ("Api token passed is not a string." )
118+ self .auth = auth
79119 self .api_token = api_token
120+ if api_token is not None :
121+ if auth is None :
122+ self .auth = ApiTokenAuth (api_token )
123+ else :
124+ self .api_token = None
125+ warn (
126+ UserWarning (
127+ "You provided both, an api_token and a custom auth "
128+ "method. We will only use the auth method."
129+ )
130+ )
80131
81132 if self .base_url :
82133 if self .api_version == "latest" :
@@ -119,21 +170,21 @@ def get_request(self, url, params=None, auth=False):
119170 Response object of requests library.
120171
121172 """
122- params = {}
123- params ["User-Agent" ] = "pydataverse"
124- if self .api_token :
125- params ["key" ] = str (self .api_token )
173+ headers = {}
174+ headers ["User-Agent" ] = "pydataverse"
126175
127176 if self .client is None :
128177 return self ._sync_request (
129178 method = httpx .get ,
130179 url = url ,
180+ headers = headers ,
131181 params = params ,
132182 )
133183 else :
134184 return self ._async_request (
135185 method = self .client .get ,
136186 url = url ,
187+ headers = headers ,
137188 params = params ,
138189 )
139190
@@ -162,10 +213,8 @@ def post_request(self, url, data=None, auth=False, params=None, files=None):
162213 Response object of requests library.
163214
164215 """
165- params = {}
166- params ["User-Agent" ] = "pydataverse"
167- if self .api_token :
168- params ["key" ] = self .api_token
216+ headers = {}
217+ headers ["User-Agent" ] = "pydataverse"
169218
170219 if isinstance (data , str ):
171220 data = json .loads (data )
@@ -175,6 +224,7 @@ def post_request(self, url, data=None, auth=False, params=None, files=None):
175224 method = httpx .post ,
176225 url = url ,
177226 json = data ,
227+ headers = headers ,
178228 params = params ,
179229 files = files ,
180230 )
@@ -183,6 +233,7 @@ def post_request(self, url, data=None, auth=False, params=None, files=None):
183233 method = self .client .post ,
184234 url = url ,
185235 json = data ,
236+ headers = headers ,
186237 params = params ,
187238 files = files ,
188239 )
@@ -208,10 +259,8 @@ def put_request(self, url, data=None, auth=False, params=None):
208259 Response object of requests library.
209260
210261 """
211- params = {}
212- params ["User-Agent" ] = "pydataverse"
213- if self .api_token :
214- params ["key" ] = self .api_token
262+ headers = {}
263+ headers ["User-Agent" ] = "pydataverse"
215264
216265 if isinstance (data , str ):
217266 data = json .loads (data )
@@ -221,13 +270,15 @@ def put_request(self, url, data=None, auth=False, params=None):
221270 method = httpx .put ,
222271 url = url ,
223272 json = data ,
273+ headers = headers ,
224274 params = params ,
225275 )
226276 else :
227277 return self ._async_request (
228278 method = self .client .put ,
229279 url = url ,
230280 json = data ,
281+ headers = headers ,
231282 params = params ,
232283 )
233284
@@ -250,21 +301,21 @@ def delete_request(self, url, auth=False, params=None):
250301 Response object of requests library.
251302
252303 """
253- params = {}
254- params ["User-Agent" ] = "pydataverse"
255- if self .api_token :
256- params ["key" ] = self .api_token
304+ headers = {}
305+ headers ["User-Agent" ] = "pydataverse"
257306
258307 if self .client is None :
259308 return self ._sync_request (
260309 method = httpx .delete ,
261310 url = url ,
311+ headers = headers ,
262312 params = params ,
263313 )
264314 else :
265315 return self ._async_request (
266316 method = self .client .delete ,
267317 url = url ,
318+ headers = headers ,
268319 params = params ,
269320 )
270321
@@ -292,7 +343,7 @@ def _sync_request(
292343 kwargs = self ._filter_kwargs (kwargs )
293344
294345 try :
295- resp = method (** kwargs , follow_redirects = True , timeout = None )
346+ resp = method (** kwargs , auth = self . auth , follow_redirects = True , timeout = None )
296347 if resp .status_code == 401 :
297348 error_msg = resp .json ()["message" ]
298349 raise ApiAuthorizationError (
@@ -335,7 +386,7 @@ async def _async_request(
335386 kwargs = self ._filter_kwargs (kwargs )
336387
337388 try :
338- resp = await method (** kwargs )
389+ resp = await method (** kwargs , auth = self . auth )
339390
340391 if resp .status_code == 401 :
341392 error_msg = resp .json ()["message" ]
@@ -408,9 +459,9 @@ class DataAccessApi(Api):
408459
409460 """
410461
411- def __init__ (self , base_url , api_token = None ):
462+ def __init__ (self , base_url , api_token = None , * , auth = None ):
412463 """Init an DataAccessApi() class."""
413- super ().__init__ (base_url , api_token )
464+ super ().__init__ (base_url , api_token , auth = auth )
414465 if base_url :
415466 self .base_url_api_data_access = "{0}/access" .format (self .base_url_api )
416467 else :
@@ -628,9 +679,9 @@ class MetricsApi(Api):
628679
629680 """
630681
631- def __init__ (self , base_url , api_token = None , api_version = "latest" ):
682+ def __init__ (self , base_url , api_token = None , api_version = "latest" , * , auth = None ):
632683 """Init an MetricsApi() class."""
633- super ().__init__ (base_url , api_token , api_version )
684+ super ().__init__ (base_url , api_token , api_version , auth = auth )
634685 if base_url :
635686 self .base_url_api_metrics = "{0}/api/info/metrics" .format (self .base_url )
636687 else :
@@ -729,7 +780,7 @@ class NativeApi(Api):
729780
730781 """
731782
732- def __init__ (self , base_url : str , api_token = None , api_version = "v1" ):
783+ def __init__ (self , base_url : str , api_token = None , api_version = "v1" , * , auth = None ):
733784 """Init an Api() class.
734785
735786 Scheme, host and path combined create the base-url for the api.
@@ -741,7 +792,7 @@ def __init__(self, base_url: str, api_token=None, api_version="v1"):
741792 Api version of Dataverse native api. Default is `v1`.
742793
743794 """
744- super ().__init__ (base_url , api_token , api_version )
795+ super ().__init__ (base_url , api_token , api_version , auth = auth )
745796 self .base_url_api_native = self .base_url_api
746797
747798 def get_dataverse (self , identifier , auth = False ):
@@ -2402,9 +2453,9 @@ class SearchApi(Api):
24022453
24032454 """
24042455
2405- def __init__ (self , base_url , api_token = None , api_version = "latest" ):
2456+ def __init__ (self , base_url , api_token = None , api_version = "latest" , * , auth = None ):
24062457 """Init an SearchApi() class."""
2407- super ().__init__ (base_url , api_token , api_version )
2458+ super ().__init__ (base_url , api_token , api_version , auth = auth )
24082459 if base_url :
24092460 self .base_url_api_search = "{0}/search?q=" .format (self .base_url_api )
24102461 else :
@@ -2479,7 +2530,13 @@ class SwordApi(Api):
24792530 """
24802531
24812532 def __init__ (
2482- self , base_url , api_version = "v1.1" , api_token = None , sword_api_version = "v1.1"
2533+ self ,
2534+ base_url ,
2535+ api_version = "v1.1" ,
2536+ api_token = None ,
2537+ sword_api_version = "v1.1" ,
2538+ * ,
2539+ auth = None ,
24832540 ):
24842541 """Init a :class:`SwordApi <pyDataverse.api.SwordApi>` instance.
24852542
@@ -2489,7 +2546,7 @@ def __init__(
24892546 Api version of Dataverse SWORD API.
24902547
24912548 """
2492- super ().__init__ (base_url , api_token , api_version )
2549+ super ().__init__ (base_url , api_token , api_version , auth = auth )
24932550 if not isinstance (sword_api_version , ("" .__class__ , "" .__class__ )):
24942551 raise ApiUrlError (
24952552 "sword_api_version {0} is not a string." .format (sword_api_version )
0 commit comments