2727try :
2828 from urllib .parse import urljoin
2929except ImportError : # pragma: no cover
30- from urlparse import urljoin
30+ from urlparse import urljoin # pyright: ignore[reportMissingImports]
31+
32+ from typing import Any
3133
3234import httpx
3335import requests
3436from httpx import Response as AsyncResponse
3537from requests import Response
3638from requests .adapters import HTTPAdapter
39+ from requests_toolbelt import MultipartEncoder
3740
3841from .exceptions import KeycloakConnectionError
3942
@@ -67,8 +70,8 @@ def __init__(
6770 self ,
6871 base_url : str ,
6972 headers : dict | None = None ,
70- timeout : int = 60 ,
71- verify : bool = True ,
73+ timeout : int | None = 60 ,
74+ verify : bool | str = True ,
7275 proxies : dict | None = None ,
7376 cert : str | tuple | None = None ,
7477 max_retries : int = 1 ,
@@ -112,9 +115,13 @@ def __init__(
112115 adapter_kwargs = {"max_retries" : max_retries }
113116 if pool_maxsize is not None :
114117 adapter_kwargs ["pool_maxsize" ] = pool_maxsize
115- adapter = HTTPAdapter (** adapter_kwargs )
118+ adapter = HTTPAdapter (** adapter_kwargs ) # pyright: ignore[reportArgumentType]
116119 # adds POST to retry whitelist
117- allowed_methods = set (adapter .max_retries .allowed_methods )
120+ allowed_methods = (
121+ set (adapter .max_retries .allowed_methods )
122+ if adapter .max_retries .allowed_methods
123+ else set ()
124+ )
118125 allowed_methods .add ("POST" )
119126 adapter .max_retries .allowed_methods = frozenset (allowed_methods )
120127
@@ -132,8 +139,8 @@ def __init__(
132139 max_keepalive_connections = 20 ,
133140 ),
134141 )
135- self .async_s .auth = None # don't let requests add auth headers
136- self .async_s .transport = httpx .AsyncHTTPTransport (retries = 1 )
142+ self .async_s .auth = None # pyright: ignore[reportAttributeAccessIssue]
143+ self .async_s .transport = httpx .AsyncHTTPTransport (retries = 1 ) # pyright: ignore[reportAttributeAccessIssue]
137144
138145 async def aclose (self ) -> None :
139146 """Close the async connection on delete."""
@@ -146,7 +153,7 @@ def __del__(self) -> None:
146153 self ._s .close ()
147154
148155 @property
149- def base_url (self ) -> str :
156+ def base_url (self ) -> str | None :
150157 """
151158 Return base url in use for requests to the server.
152159
@@ -156,11 +163,11 @@ def base_url(self) -> str:
156163 return self ._base_url
157164
158165 @base_url .setter
159- def base_url (self , value : str ) -> None :
166+ def base_url (self , value : str | None ) -> None :
160167 self ._base_url = value
161168
162169 @property
163- def timeout (self ) -> int :
170+ def timeout (self ) -> int | None :
164171 """
165172 Return timeout in use for request to the server.
166173
@@ -170,11 +177,11 @@ def timeout(self) -> int:
170177 return self ._timeout
171178
172179 @timeout .setter
173- def timeout (self , value : int ) -> None :
180+ def timeout (self , value : int | None ) -> None :
174181 self ._timeout = value
175182
176183 @property
177- def verify (self ) -> bool :
184+ def verify (self ) -> bool | str :
178185 """
179186 Return verify in use for request to the server.
180187
@@ -184,11 +191,11 @@ def verify(self) -> bool:
184191 return self ._verify
185192
186193 @verify .setter
187- def verify (self , value : bool ) -> None :
194+ def verify (self , value : bool | str ) -> None :
188195 self ._verify = value
189196
190197 @property
191- def cert (self ) -> str | tuple :
198+ def cert (self ) -> str | tuple | None :
192199 """
193200 Return client certificates in use for request to the server.
194201
@@ -198,7 +205,7 @@ def cert(self) -> str | tuple:
198205 return self ._cert
199206
200207 @cert .setter
201- def cert (self , value : str | tuple ) -> None :
208+ def cert (self , value : str | tuple | None ) -> None :
202209 self ._cert = value
203210
204211 @property
@@ -216,7 +223,7 @@ def pool_maxsize(self, value: int | None) -> None:
216223 self ._pool_maxsize = value
217224
218225 @property
219- def headers (self ) -> dict :
226+ def headers (self ) -> dict | None :
220227 """
221228 Return header request to the server.
222229
@@ -226,7 +233,7 @@ def headers(self) -> dict:
226233 return self ._headers
227234
228235 @headers .setter
229- def headers (self , value : dict ) -> None :
236+ def headers (self , value : dict | None ) -> None :
230237 self ._headers = value or {}
231238
232239 def param_headers (self , key : str ) -> str | None :
@@ -238,7 +245,7 @@ def param_headers(self, key: str) -> str | None:
238245 :returns: If the header parameters exist, return its value.
239246 :rtype: str
240247 """
241- return self .headers .get (key )
248+ return ( self .headers or {}) .get (key )
242249
243250 def clean_headers (self ) -> None :
244251 """Clear header parameters."""
@@ -264,6 +271,9 @@ def add_param_headers(self, key: str, value: str) -> None:
264271 :param value: Value to be added.
265272 :type value: str
266273 """
274+ if self .headers is None :
275+ self .headers = {}
276+
267277 self .headers [key ] = value
268278
269279 def del_param_headers (self , key : str ) -> None :
@@ -273,9 +283,12 @@ def del_param_headers(self, key: str) -> None:
273283 :param key: Key of the header parameters.
274284 :type key: str
275285 """
286+ if self .headers is None :
287+ return
288+
276289 self .headers .pop (key , None )
277290
278- def raw_get (self , path : str , ** kwargs : dict ) -> Response :
291+ def raw_get (self , path : str , ** kwargs : Any ) -> Response : # noqa: ANN401
279292 """
280293 Submit get request to the path.
281294
@@ -287,6 +300,9 @@ def raw_get(self, path: str, **kwargs: dict) -> Response:
287300 :rtype: Response
288301 :raises KeycloakConnectionError: HttpError Can't connect to server.
289302 """
303+ if self .base_url is None :
304+ msg = "Unable to perform GET call with base_url missing."
305+ raise AttributeError (msg )
290306 try :
291307 return self ._s .get (
292308 urljoin (self .base_url , path ),
@@ -300,7 +316,7 @@ def raw_get(self, path: str, **kwargs: dict) -> Response:
300316 msg = "Can't connect to server"
301317 raise KeycloakConnectionError (msg ) from e
302318
303- def raw_post (self , path : str , data : dict , ** kwargs : dict ) -> Response :
319+ def raw_post (self , path : str , data : dict | str | MultipartEncoder , ** kwargs : Any ) -> Response : # noqa: ANN401
304320 """
305321 Submit post request to the path.
306322
@@ -314,6 +330,9 @@ def raw_post(self, path: str, data: dict, **kwargs: dict) -> Response:
314330 :rtype: Response
315331 :raises KeycloakConnectionError: HttpError Can't connect to server.
316332 """
333+ if self .base_url is None :
334+ msg = "Unable to perform POST call with base_url missing."
335+ raise AttributeError (msg )
317336 try :
318337 return self ._s .post (
319338 urljoin (self .base_url , path ),
@@ -328,7 +347,7 @@ def raw_post(self, path: str, data: dict, **kwargs: dict) -> Response:
328347 msg = "Can't connect to server"
329348 raise KeycloakConnectionError (msg ) from e
330349
331- def raw_put (self , path : str , data : dict , ** kwargs : dict ) -> Response :
350+ def raw_put (self , path : str , data : dict | str , ** kwargs : Any ) -> Response : # noqa: ANN401
332351 """
333352 Submit put request to the path.
334353
@@ -342,6 +361,10 @@ def raw_put(self, path: str, data: dict, **kwargs: dict) -> Response:
342361 :rtype: Response
343362 :raises KeycloakConnectionError: HttpError Can't connect to server.
344363 """
364+ if self .base_url is None :
365+ msg = "Unable to perform PUT call with base_url missing."
366+ raise AttributeError (msg )
367+
345368 try :
346369 return self ._s .put (
347370 urljoin (self .base_url , path ),
@@ -356,7 +379,7 @@ def raw_put(self, path: str, data: dict, **kwargs: dict) -> Response:
356379 msg = "Can't connect to server"
357380 raise KeycloakConnectionError (msg ) from e
358381
359- def raw_delete (self , path : str , data : dict | None = None , ** kwargs : dict ) -> Response :
382+ def raw_delete (self , path : str , data : dict | None = None , ** kwargs : Any ) -> Response : # noqa: ANN401
360383 """
361384 Submit delete request to the path.
362385
@@ -370,6 +393,10 @@ def raw_delete(self, path: str, data: dict | None = None, **kwargs: dict) -> Res
370393 :rtype: Response
371394 :raises KeycloakConnectionError: HttpError Can't connect to server.
372395 """
396+ if self .base_url is None :
397+ msg = "Unable to perform DELETE call with base_url missing."
398+ raise AttributeError (msg )
399+
373400 try :
374401 return self ._s .delete (
375402 urljoin (self .base_url , path ),
@@ -384,7 +411,7 @@ def raw_delete(self, path: str, data: dict | None = None, **kwargs: dict) -> Res
384411 msg = "Can't connect to server"
385412 raise KeycloakConnectionError (msg ) from e
386413
387- async def a_raw_get (self , path : str , ** kwargs : dict ) -> AsyncResponse :
414+ async def a_raw_get (self , path : str , ** kwargs : Any ) -> AsyncResponse : # noqa: ANN401
388415 """
389416 Submit get request to the path.
390417
@@ -396,6 +423,10 @@ async def a_raw_get(self, path: str, **kwargs: dict) -> AsyncResponse:
396423 :rtype: Response
397424 :raises KeycloakConnectionError: HttpError Can't connect to server.
398425 """
426+ if self .base_url is None :
427+ msg = "Unable to perform GET call with base_url missing."
428+ raise AttributeError (msg )
429+
399430 try :
400431 return await self .async_s .get (
401432 urljoin (self .base_url , path ),
@@ -407,7 +438,12 @@ async def a_raw_get(self, path: str, **kwargs: dict) -> AsyncResponse:
407438 msg = "Can't connect to server"
408439 raise KeycloakConnectionError (msg ) from e
409440
410- async def a_raw_post (self , path : str , data : dict , ** kwargs : dict ) -> AsyncResponse :
441+ async def a_raw_post (
442+ self ,
443+ path : str ,
444+ data : dict | str | MultipartEncoder ,
445+ ** kwargs : Any , # noqa: ANN401
446+ ) -> AsyncResponse :
411447 """
412448 Submit post request to the path.
413449
@@ -421,6 +457,10 @@ async def a_raw_post(self, path: str, data: dict, **kwargs: dict) -> AsyncRespon
421457 :rtype: Response
422458 :raises KeycloakConnectionError: HttpError Can't connect to server.
423459 """
460+ if self .base_url is None :
461+ msg = "Unable to perform POST call with base_url missing."
462+ raise AttributeError (msg )
463+
424464 try :
425465 return await self .async_s .request (
426466 method = "POST" ,
@@ -434,7 +474,7 @@ async def a_raw_post(self, path: str, data: dict, **kwargs: dict) -> AsyncRespon
434474 msg = "Can't connect to server"
435475 raise KeycloakConnectionError (msg ) from e
436476
437- async def a_raw_put (self , path : str , data : dict , ** kwargs : dict ) -> AsyncResponse :
477+ async def a_raw_put (self , path : str , data : dict | str , ** kwargs : Any ) -> AsyncResponse : # noqa: ANN401
438478 """
439479 Submit put request to the path.
440480
@@ -448,6 +488,10 @@ async def a_raw_put(self, path: str, data: dict, **kwargs: dict) -> AsyncRespons
448488 :rtype: Response
449489 :raises KeycloakConnectionError: HttpError Can't connect to server.
450490 """
491+ if self .base_url is None :
492+ msg = "Unable to perform PUT call with base_url missing."
493+ raise AttributeError (msg )
494+
451495 try :
452496 return await self .async_s .put (
453497 urljoin (self .base_url , path ),
@@ -464,7 +508,7 @@ async def a_raw_delete(
464508 self ,
465509 path : str ,
466510 data : dict | None = None ,
467- ** kwargs : dict ,
511+ ** kwargs : Any , # noqa: ANN401
468512 ) -> AsyncResponse :
469513 """
470514 Submit delete request to the path.
@@ -479,6 +523,10 @@ async def a_raw_delete(
479523 :rtype: Response
480524 :raises KeycloakConnectionError: HttpError Can't connect to server.
481525 """
526+ if self .base_url is None :
527+ msg = "Unable to perform DELETE call with base_url missing."
528+ raise AttributeError (msg )
529+
482530 try :
483531 return await self .async_s .request (
484532 method = "DELETE" ,
@@ -493,7 +541,7 @@ async def a_raw_delete(
493541 raise KeycloakConnectionError (msg ) from e
494542
495543 @staticmethod
496- def _prepare_httpx_request_content (data : dict | str | None ) -> dict :
544+ def _prepare_httpx_request_content (data : dict | str | None | MultipartEncoder ) -> dict :
497545 """
498546 Create the correct request content kwarg to `httpx.AsyncClient.request()`.
499547
@@ -504,9 +552,13 @@ def _prepare_httpx_request_content(data: dict | str | None) -> dict:
504552 :returns: A dict mapping the correct kwarg to the request content
505553 :rtype: dict
506554 """
555+ if isinstance (data , MultipartEncoder ):
556+ return {"content" : data .to_string ()}
557+
507558 if isinstance (data , str ):
508559 # Note: this could also accept bytes, Iterable[bytes], or AsyncIterable[bytes]
509560 return {"content" : data }
561+
510562 return {"data" : data }
511563
512564 @staticmethod
0 commit comments