]> git.ipfire.org Git - thirdparty/httpx.git/commitdiff
Move utility functions from _utils.py to _multipart.py (#3388)
authorRafaelWO <38643099+RafaelWO@users.noreply.github.com>
Fri, 1 Nov 2024 12:54:13 +0000 (13:54 +0100)
committerGitHub <noreply@github.com>
Fri, 1 Nov 2024 12:54:13 +0000 (12:54 +0000)
httpx/_multipart.py
httpx/_utils.py

index 8edb622778afe1d1c832cc9857a8acd4bf2125f4..b4761af9b2cf384de5189269927d781a700dbe46 100644 (file)
@@ -1,7 +1,9 @@
 from __future__ import annotations
 
 import io
+import mimetypes
 import os
+import re
 import typing
 from pathlib import Path
 
@@ -14,13 +16,42 @@ from ._types import (
     SyncByteStream,
 )
 from ._utils import (
-    format_form_param,
-    guess_content_type,
     peek_filelike_length,
     primitive_value_to_str,
     to_bytes,
 )
 
+_HTML5_FORM_ENCODING_REPLACEMENTS = {'"': "%22", "\\": "\\\\"}
+_HTML5_FORM_ENCODING_REPLACEMENTS.update(
+    {chr(c): "%{:02X}".format(c) for c in range(0x1F + 1) if c != 0x1B}
+)
+_HTML5_FORM_ENCODING_RE = re.compile(
+    r"|".join([re.escape(c) for c in _HTML5_FORM_ENCODING_REPLACEMENTS.keys()])
+)
+
+
+def _format_form_param(name: str, value: str) -> bytes:
+    """
+    Encode a name/value pair within a multipart form.
+    """
+
+    def replacer(match: typing.Match[str]) -> str:
+        return _HTML5_FORM_ENCODING_REPLACEMENTS[match.group(0)]
+
+    value = _HTML5_FORM_ENCODING_RE.sub(replacer, value)
+    return f'{name}="{value}"'.encode()
+
+
+def _guess_content_type(filename: str | None) -> str | None:
+    """
+    Guesses the mimetype based on a filename. Defaults to `application/octet-stream`.
+
+    Returns `None` if `filename` is `None` or empty.
+    """
+    if filename:
+        return mimetypes.guess_type(filename)[0] or "application/octet-stream"
+    return None
+
 
 def get_multipart_boundary_from_content_type(
     content_type: bytes | None,
@@ -58,7 +89,7 @@ class DataField:
 
     def render_headers(self) -> bytes:
         if not hasattr(self, "_headers"):
-            name = format_form_param("name", self.name)
+            name = _format_form_param("name", self.name)
             self._headers = b"".join(
                 [b"Content-Disposition: form-data; ", name, b"\r\n\r\n"]
             )
@@ -115,7 +146,7 @@ class FileField:
             fileobj = value
 
         if content_type is None:
-            content_type = guess_content_type(filename)
+            content_type = _guess_content_type(filename)
 
         has_content_type_header = any("content-type" in key.lower() for key in headers)
         if content_type is not None and not has_content_type_header:
@@ -156,10 +187,10 @@ class FileField:
         if not hasattr(self, "_headers"):
             parts = [
                 b"Content-Disposition: form-data; ",
-                format_form_param("name", self.name),
+                _format_form_param("name", self.name),
             ]
             if self.filename:
-                filename = format_form_param("filename", self.filename)
+                filename = _format_form_param("filename", self.filename)
                 parts.extend([b"; ", filename])
             for header_name, header_value in self.headers.items():
                 key, val = f"\r\n{header_name}: ".encode(), header_value.encode()
index 1c959e65285b8616d0828e5a7715386cb03e2f69..c873bdb2f0fca48b8ad4e210486462d4af35acc6 100644 (file)
@@ -3,7 +3,6 @@ from __future__ import annotations
 import codecs
 import email.message
 import ipaddress
-import mimetypes
 import os
 import re
 import typing
@@ -15,15 +14,6 @@ if typing.TYPE_CHECKING:  # pragma: no cover
     from ._urls import URL
 
 
-_HTML5_FORM_ENCODING_REPLACEMENTS = {'"': "%22", "\\": "\\\\"}
-_HTML5_FORM_ENCODING_REPLACEMENTS.update(
-    {chr(c): "%{:02X}".format(c) for c in range(0x1F + 1) if c != 0x1B}
-)
-_HTML5_FORM_ENCODING_RE = re.compile(
-    r"|".join([re.escape(c) for c in _HTML5_FORM_ENCODING_REPLACEMENTS.keys()])
-)
-
-
 def primitive_value_to_str(value: PrimitiveData) -> str:
     """
     Coerce a primitive data type into a string value.
@@ -50,18 +40,6 @@ def is_known_encoding(encoding: str) -> bool:
     return True
 
 
-def format_form_param(name: str, value: str) -> bytes:
-    """
-    Encode a name/value pair within a multipart form.
-    """
-
-    def replacer(match: typing.Match[str]) -> str:
-        return _HTML5_FORM_ENCODING_REPLACEMENTS[match.group(0)]
-
-    value = _HTML5_FORM_ENCODING_RE.sub(replacer, value)
-    return f'{name}="{value}"'.encode()
-
-
 def parse_header_links(value: str) -> list[dict[str, str]]:
     """
     Returns a list of parsed link headers, for more info see:
@@ -216,12 +194,6 @@ def unquote(value: str) -> str:
     return value[1:-1] if value[0] == value[-1] == '"' else value
 
 
-def guess_content_type(filename: str | None) -> str | None:
-    if filename:
-        return mimetypes.guess_type(filename)[0] or "application/octet-stream"
-    return None
-
-
 def peek_filelike_length(stream: typing.Any) -> int | None:
     """
     Given a file-like stream object, return its length in number of bytes