| 471 | return SSEDecoder() |
| 472 | |
| 473 | def _build_request( |
| 474 | self, |
| 475 | options: FinalRequestOptions, |
| 476 | *, |
| 477 | retries_taken: int = 0, |
| 478 | ) -> httpx.Request: |
| 479 | if log.isEnabledFor(logging.DEBUG): |
| 480 | log.debug("Request options: %s", model_dump(options, exclude_unset=True)) |
| 481 | |
| 482 | kwargs: dict[str, Any] = {} |
| 483 | |
| 484 | json_data = options.json_data |
| 485 | if options.extra_json is not None: |
| 486 | if json_data is None: |
| 487 | json_data = cast(Body, options.extra_json) |
| 488 | elif is_mapping(json_data): |
| 489 | json_data = _merge_mappings(json_data, options.extra_json) |
| 490 | else: |
| 491 | raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`") |
| 492 | |
| 493 | headers = self._build_headers(options, retries_taken=retries_taken) |
| 494 | params = _merge_mappings(self.default_query, options.params) |
| 495 | content_type = headers.get("Content-Type") |
| 496 | files = options.files |
| 497 | |
| 498 | # If the given Content-Type header is multipart/form-data then it |
| 499 | # has to be removed so that httpx can generate the header with |
| 500 | # additional information for us as it has to be in this form |
| 501 | # for the server to be able to correctly parse the request: |
| 502 | # multipart/form-data; boundary=---abc-- |
| 503 | if content_type is not None and content_type.startswith("multipart/form-data"): |
| 504 | if "boundary" not in content_type: |
| 505 | # only remove the header if the boundary hasn't been explicitly set |
| 506 | # as the caller doesn't want httpx to come up with their own boundary |
| 507 | headers.pop("Content-Type") |
| 508 | |
| 509 | # As we are now sending multipart/form-data instead of application/json |
| 510 | # we need to tell httpx to use it, https://www.python-httpx.org/advanced/clients/#multipart-file-encoding |
| 511 | if json_data: |
| 512 | if not is_dict(json_data): |
| 513 | raise TypeError( |
| 514 | f"Expected query input to be a dictionary for multipart requests but got {type(json_data)} instead." |
| 515 | ) |
| 516 | kwargs["data"] = self._serialize_multipartform(json_data) |
| 517 | |
| 518 | # httpx determines whether or not to send a "multipart/form-data" |
| 519 | # request based on the truthiness of the "files" argument. |
| 520 | # This gets around that issue by generating a dict value that |
| 521 | # evaluates to true. |
| 522 | # |
| 523 | # https://github.com/encode/httpx/discussions/2399#discussioncomment-3814186 |
| 524 | if not files: |
| 525 | files = cast(HttpxRequestFiles, ForceMultipartDict()) |
| 526 | |
| 527 | prepared_url = self._prepare_url(options.url) |
| 528 | if "_" in prepared_url.host: |
| 529 | # work around https://github.com/encode/httpx/discussions/2880 |
| 530 | kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} |