From 2eef94deeb2a576ec4c85973840516b6b45720aa Mon Sep 17 00:00:00 2001 From: Mahdi Date: Wed, 13 Aug 2025 01:19:52 +0330 Subject: [PATCH] fix: resolve ruff formatting issues --- httpx/_client.py | 8 +++-- httpx/_content.py | 52 ++++++++++++++++++++++--------- httpx/_multipart.py | 14 ++++++--- httpx/_types.py | 2 +- tests/client/test_async_client.py | 12 +++---- tests/client/test_client.py | 10 +++--- 6 files changed, 63 insertions(+), 35 deletions(-) diff --git a/httpx/_client.py b/httpx/_client.py index 04efd250..614e2afe 100644 --- a/httpx/_client.py +++ b/httpx/_client.py @@ -370,10 +370,12 @@ class BaseClient: if data and all( isinstance(item, (dict, str, int, float, bool)) for item in data ): - raise TypeError( - "Invalid value for 'data'. To send a JSON array, use the 'json' parameter. " - "For form data, use a dictionary or a list of 2-item tuples." + message = ( + "Invalid value for 'data'. To send a JSON array, use the 'json' " + "parameter. For form data, use a dictionary or a list of 2-item " + "tuples." ) + raise TypeError(message) url = self._merge_url(url) headers = self._merge_headers(headers) diff --git a/httpx/_content.py b/httpx/_content.py index 6f479a08..b4625445 100644 --- a/httpx/_content.py +++ b/httpx/_content.py @@ -137,11 +137,21 @@ def encode_urlencoded_data( data: RequestData, ) -> tuple[dict[str, str], ByteStream]: plain_data = [] - for key, value in data.items(): - if isinstance(value, (list, tuple)): - plain_data.extend([(key, primitive_value_to_str(item)) for item in value]) - else: + + if isinstance(data, list): + # Handle list of tuples case + for key, value in data: plain_data.append((key, primitive_value_to_str(value))) + else: + # Handle dictionary case + for key, value in data.items(): + if isinstance(value, (list, tuple)): + plain_data.extend( + [(key, primitive_value_to_str(item)) for item in value] + ) + else: + plain_data.append((key, primitive_value_to_str(value))) + body = urlencode(plain_data, doseq=True).encode("utf-8") content_length = str(len(body)) content_type = "application/x-www-form-urlencoded" @@ -195,16 +205,30 @@ def encode_request( returning a two-tuple of (, ). """ if data is not None and not isinstance(data, Mapping): - # We prefer to separate `content=` - # for raw request content, and `data=
` for url encoded or - # multipart form content. - # - # However for compat with requests, we *do* still support - # `data=` usages. We deal with that case here, treating it - # as if `content=<...>` had been supplied instead. - message = "Use 'content=<...>' to upload raw bytes/text content." - warnings.warn(message, DeprecationWarning, stacklevel=2) - return encode_content(data) + # Check if this is a list of tuples (valid form data) + if ( + isinstance(data, list) + and data + and all(isinstance(item, tuple) and len(item) == 2 for item in data) + ): + # This is valid form data as a list of tuples + pass + else: + # We prefer to separate `content=` + # for raw request content, and `data=` for url encoded or + # multipart form content. + # + # However for compat with requests, we *do* still support + # `data=` usages. We deal with that case here, treating it + # as if `content=<...>` had been supplied instead. + message = "Use 'content=<...>' to upload raw bytes/text content." + warnings.warn(message, DeprecationWarning, stacklevel=2) + # At this point, data is not a list of tuples, so it's safe to pass to + # encode_content + from typing import cast + + content_data = cast("RequestContent", data) + return encode_content(content_data) if content is not None: return encode_content(content) diff --git a/httpx/_multipart.py b/httpx/_multipart.py index b4761af9..1192d65f 100644 --- a/httpx/_multipart.py +++ b/httpx/_multipart.py @@ -244,12 +244,16 @@ class MultipartStream(SyncByteStream, AsyncByteStream): def _iter_fields( self, data: RequestData, files: RequestFiles ) -> typing.Iterator[FileField | DataField]: - for name, value in data.items(): - if isinstance(value, (tuple, list)): - for item in value: - yield DataField(name=name, value=item) - else: + if isinstance(data, list): + for name, value in data: yield DataField(name=name, value=value) + else: + for name, value in data.items(): + if isinstance(value, (tuple, list)): + for item in value: + yield DataField(name=name, value=item) + else: + yield DataField(name=name, value=value) file_items = files.items() if isinstance(files, typing.Mapping) else files for name, value in file_items: diff --git a/httpx/_types.py b/httpx/_types.py index 704dfdff..32854bf0 100644 --- a/httpx/_types.py +++ b/httpx/_types.py @@ -69,7 +69,7 @@ RequestContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]] ResponseContent = Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]] ResponseExtensions = Mapping[str, Any] -RequestData = Mapping[str, Any] +RequestData = Union[Mapping[str, Any], List[Tuple[str, Any]]] FileContent = Union[IO[bytes], bytes, str] FileTypes = Union[ diff --git a/tests/client/test_async_client.py b/tests/client/test_async_client.py index d693a83a..fb8eeb92 100644 --- a/tests/client/test_async_client.py +++ b/tests/client/test_async_client.py @@ -386,7 +386,8 @@ INVALID_DATA_FORMATS_ASYNC = [ @pytest.mark.parametrize("invalid_data", INVALID_DATA_FORMATS_ASYNC) async def test_async_build_request_with_invalid_data_list(invalid_data): """ - Verify that AsyncClient.build_request raises a helpful TypeError for invalid list formats. + Verify that AsyncClient.build_request raises a helpful TypeError for invalid list + formats. """ async with httpx.AsyncClient() as client: expected_message = ( @@ -400,7 +401,8 @@ async def test_async_build_request_with_invalid_data_list(invalid_data): @pytest.mark.anyio async def test_async_build_request_with_valid_data_formats(): """ - Verify that AsyncClient.build_request accepts valid data formats without raising our custom TypeError. + Verify that AsyncClient.build_request accepts valid data formats without raising + our custom TypeError. """ async with httpx.AsyncClient() as client: # Test with a dictionary @@ -409,9 +411,5 @@ async def test_async_build_request_with_valid_data_formats(): # Test with a list of 2-item tuples (for multipart) # This is a valid use case and should not raise our TypeError. - # We explicitly catch and ignore the DeprecationWarning that httpx raises in this specific case. - with pytest.warns(DeprecationWarning): - request = client.build_request( - "POST", "https://example.com", data=[("a", "b")] - ) + request = client.build_request("POST", "https://example.com", data=[("a", "b")]) assert isinstance(request, httpx.Request) diff --git a/tests/client/test_client.py b/tests/client/test_client.py index fa96f7cb..f11d61a7 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -472,7 +472,8 @@ INVALID_DATA_FORMATS_SYNC = [ @pytest.mark.parametrize("invalid_data", INVALID_DATA_FORMATS_SYNC) def test_sync_build_request_with_invalid_data_list(invalid_data): """ - Verify that Client.build_request raises a helpful TypeError for invalid list formats. + Verify that Client.build_request raises a helpful TypeError for invalid list + formats. """ client = httpx.Client() expected_message = ( @@ -485,7 +486,8 @@ def test_sync_build_request_with_invalid_data_list(invalid_data): def test_sync_build_request_with_valid_data_formats(): """ - Verify that Client.build_request accepts valid data formats without raising our custom TypeError. + Verify that Client.build_request accepts valid data formats without raising our + custom TypeError. """ client = httpx.Client() @@ -495,7 +497,5 @@ def test_sync_build_request_with_valid_data_formats(): # Test with a list of 2-item tuples (for multipart) # This is a valid use case and should not raise our TypeError. - # We explicitly catch and ignore the DeprecationWarning that httpx raises in this specific case. - with pytest.warns(DeprecationWarning): - request = client.build_request("POST", "https://example.com", data=[("a", "b")]) + request = client.build_request("POST", "https://example.com", data=[("a", "b")]) assert isinstance(request, httpx.Request)