Skip to content

Commit cb5d6ba

Browse files
committed
fix: filter None values from form-encoded body in async httpx requests
1 parent c5848e8 commit cb5d6ba

File tree

1 file changed

+19
-15
lines changed

1 file changed

+19
-15
lines changed

src/keycloak/connection.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ async def a_raw_get(self, path: str, **kwargs: Any) -> AsyncResponse: # noqa: A
459459
try:
460460
return await self.async_s.get(
461461
urljoin(self.base_url, path),
462-
params=self._filter_query_params(kwargs),
462+
params=self._filter_none_values(kwargs),
463463
headers=self.headers,
464464
timeout=self.timeout,
465465
)
@@ -494,7 +494,7 @@ async def a_raw_post(
494494
return await self.async_s.request(
495495
method="POST",
496496
url=urljoin(self.base_url, path),
497-
params=self._filter_query_params(kwargs),
497+
params=self._filter_none_values(kwargs),
498498
**self._prepare_httpx_request_content(data),
499499
headers=self.headers,
500500
timeout=self.timeout,
@@ -529,7 +529,7 @@ async def a_raw_put(
529529
try:
530530
return await self.async_s.put(
531531
urljoin(self.base_url, path),
532-
params=self._filter_query_params(kwargs),
532+
params=self._filter_none_values(kwargs),
533533
**self._prepare_httpx_request_content(data),
534534
headers=self.headers,
535535
timeout=self.timeout,
@@ -566,16 +566,16 @@ async def a_raw_delete(
566566
method="DELETE",
567567
url=urljoin(self.base_url, path),
568568
**self._prepare_httpx_request_content(data or {}),
569-
params=self._filter_query_params(kwargs),
569+
params=self._filter_none_values(kwargs),
570570
headers=self.headers,
571571
timeout=self.timeout,
572572
)
573573
except Exception as e:
574574
msg = "Can't connect to server"
575575
raise KeycloakConnectionError(msg) from e
576576

577-
@staticmethod
578-
def _prepare_httpx_request_content(data: dict | str | None | MultipartEncoder) -> dict:
577+
@classmethod
578+
def _prepare_httpx_request_content(cls, data: dict | str | None | MultipartEncoder) -> dict:
579579
"""
580580
Create the correct request content kwarg to `httpx.AsyncClient.request()`.
581581
@@ -593,19 +593,23 @@ def _prepare_httpx_request_content(data: dict | str | None | MultipartEncoder) -
593593
# Note: this could also accept bytes, Iterable[bytes], or AsyncIterable[bytes]
594594
return {"content": data}
595595

596+
if isinstance(data, dict):
597+
return {"data": cls._filter_none_values(data)}
598+
596599
return {"data": data}
597600

598-
@staticmethod
599-
def _filter_query_params(query_params: dict) -> dict:
601+
@classmethod
602+
def _filter_none_values(cls, data: dict) -> dict:
600603
"""
601-
Explicitly filter query params with None values for compatibility.
604+
Explicitly filter items with None values for compatibility.
602605
603-
Httpx and requests differ in the way they handle query params with the value None,
604-
requests does not include params with the value None while httpx includes them as-is.
606+
Httpx and requests differ in the way they handle None values:
607+
requests silently drops keys with None values from both query params and form-encoded bodies, while
608+
httpx serializes them as empty strings.
605609
606-
:param query_params: the query params
607-
:type query_params: dict
608-
:returns: the filtered query params
610+
:param data: the dict to filter
611+
:type data: dict
612+
:returns: the filtered dict
609613
:rtype: dict
610614
"""
611-
return {k: v for k, v in query_params.items() if v is not None}
615+
return {k: v for k, v in data.items() if v is not None}

0 commit comments

Comments
 (0)