From fd45d365b49e2b705ee7d7e66730b77cfba7a419 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 25 Feb 2026 10:01:02 +0800 Subject: [PATCH 1/9] docs: clarify Http.File request body compatibility Add a compact design note explaining why TypeSpec Http.File emitting IO[bytes] | bytes is backward compatible with legacy Swagger file APIs that accepted bytes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../developer/http_file_body_compatibility.md | 40 +++++++++++++++++++ docs/developer/readme.md | 4 ++ 2 files changed, 44 insertions(+) create mode 100644 docs/developer/http_file_body_compatibility.md diff --git a/docs/developer/http_file_body_compatibility.md b/docs/developer/http_file_body_compatibility.md new file mode 100644 index 00000000000..1abf893e03b --- /dev/null +++ b/docs/developer/http_file_body_compatibility.md @@ -0,0 +1,40 @@ +# `Http.File` request body compatibility for Python + +## Problem + +When a TypeSpec operation uses `Http.File` as the request body type, the Python emitter generates an SDK method input type of `IO[bytes] | bytes`. + +For migrated Swagger APIs, this must remain compatible with previous SDKs where Swagger `type: file` was represented as `bytes`. + +## Compact example + +TypeSpec: + +```typespec +import "@typespec/http"; + +using TypeSpec.Http; + +@route("/files") +namespace Files; + +@post +op upload(@body body: Http.File): void; +``` + +Python SDK (generated shape): + +```python +from typing import IO, Union + +def upload(self, body: Union[IO[bytes], bytes], **kwargs) -> None: + ... +``` + +## Compatibility statement + +- Swagger `type: file` callers pass `bytes`. +- `bytes` is still accepted by `Union[IO[bytes], bytes]`. +- Existing `bytes`-based callsites continue to work without code changes. + +Therefore, migrating this shape from Swagger to TypeSpec is **non-breaking** for existing Python SDK consumers. diff --git a/docs/developer/readme.md b/docs/developer/readme.md index cd16dc30f3b..81524fcf984 100644 --- a/docs/developer/readme.md +++ b/docs/developer/readme.md @@ -12,3 +12,7 @@ To test an unpublished branded emitter tarball, please follow these steps: 6. Paste the url as the version for the `"@azure-tools/typespec-python"` package 7. Run `tsp-client generate-lock-file` from the `azure-sdk-for-python/eng` folder 8. Go to the sdk you'd like to regenerate with the tarball, then run `tsp-client update` how you normally would + +## Design Notes + +- [`Http.File` request body compatibility for Python](./http_file_body_compatibility.md) From 25971fdaeada51a30063d8e7da0282c45fb17f75 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 25 Feb 2026 10:14:20 +0800 Subject: [PATCH 2/9] docs: fix design doc wording, IO type, union syntax, and add overloads Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/developer/http_file_body_compatibility.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/developer/http_file_body_compatibility.md b/docs/developer/http_file_body_compatibility.md index 1abf893e03b..37162c37e01 100644 --- a/docs/developer/http_file_body_compatibility.md +++ b/docs/developer/http_file_body_compatibility.md @@ -1,8 +1,8 @@ # `Http.File` request body compatibility for Python -## Problem +## Overview -When a TypeSpec operation uses `Http.File` as the request body type, the Python emitter generates an SDK method input type of `IO[bytes] | bytes`. +When a TypeSpec operation uses `Http.File` as the request body type, the Python emitter generates an SDK method input type of `IO | bytes`. For migrated Swagger APIs, this must remain compatible with previous SDKs where Swagger `type: file` was represented as `bytes`. @@ -25,16 +25,24 @@ op upload(@body body: Http.File): void; Python SDK (generated shape): ```python -from typing import IO, Union +from typing import IO, overload -def upload(self, body: Union[IO[bytes], bytes], **kwargs) -> None: +@overload +def upload(self, body: IO, **kwargs) -> None: + ... + +@overload +def upload(self, body: bytes, **kwargs) -> None: + ... + +def upload(self, body: IO | bytes, **kwargs) -> None: ... ``` ## Compatibility statement - Swagger `type: file` callers pass `bytes`. -- `bytes` is still accepted by `Union[IO[bytes], bytes]`. +- `bytes` is still accepted by `IO | bytes`. - Existing `bytes`-based callsites continue to work without code changes. Therefore, migrating this shape from Swagger to TypeSpec is **non-breaking** for existing Python SDK consumers. From e19f1441ae5b4f6a431c1897be0e12a0f5ecfa28 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 25 Feb 2026 10:17:52 +0800 Subject: [PATCH 3/9] docs: improve design doc structure and wording Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../developer/http_file_body_compatibility.md | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/docs/developer/http_file_body_compatibility.md b/docs/developer/http_file_body_compatibility.md index 37162c37e01..7ffe5fcca13 100644 --- a/docs/developer/http_file_body_compatibility.md +++ b/docs/developer/http_file_body_compatibility.md @@ -1,14 +1,10 @@ -# `Http.File` request body compatibility for Python +# `Http.File` as Python Request Body ## Overview -When a TypeSpec operation uses `Http.File` as the request body type, the Python emitter generates an SDK method input type of `IO | bytes`. +TypeSpec `Http.File` maps to `IO | bytes` in generated Python SDK methods. This widens the Swagger `type: file` signature (previously `bytes` only) while keeping full backward compatibility. -For migrated Swagger APIs, this must remain compatible with previous SDKs where Swagger `type: file` was represented as `bytes`. - -## Compact example - -TypeSpec: +## TypeSpec Definition ```typespec import "@typespec/http"; @@ -22,7 +18,7 @@ namespace Files; op upload(@body body: Http.File): void; ``` -Python SDK (generated shape): +## Generated Python SDK ```python from typing import IO, overload @@ -39,10 +35,6 @@ def upload(self, body: IO | bytes, **kwargs) -> None: ... ``` -## Compatibility statement - -- Swagger `type: file` callers pass `bytes`. -- `bytes` is still accepted by `IO | bytes`. -- Existing `bytes`-based callsites continue to work without code changes. +## Backward Compatibility -Therefore, migrating this shape from Swagger to TypeSpec is **non-breaking** for existing Python SDK consumers. +Swagger `type: file` generated `bytes`-only input. After migrating to TypeSpec, the input type widens to `IO | bytes`. Since `bytes` is still accepted, existing callers require **no code changes**, making this migration **non-breaking**. From 272eb64907ddd5b5d5be11bd6c8237f56147fe47 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 25 Feb 2026 11:13:39 +0800 Subject: [PATCH 4/9] docs: add concrete examples to backward compatibility section --- docs/developer/http_file_body_compatibility.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/developer/http_file_body_compatibility.md b/docs/developer/http_file_body_compatibility.md index 7ffe5fcca13..7236e17920d 100644 --- a/docs/developer/http_file_body_compatibility.md +++ b/docs/developer/http_file_body_compatibility.md @@ -37,4 +37,5 @@ def upload(self, body: IO | bytes, **kwargs) -> None: ## Backward Compatibility -Swagger `type: file` generated `bytes`-only input. After migrating to TypeSpec, the input type widens to `IO | bytes`. Since `bytes` is still accepted, existing callers require **no code changes**, making this migration **non-breaking**. +Swagger `type: file`(e.g. [`operationId: "WebApps_GetWebSiteContainerLogs"`](https://github.com/Azure/azure-rest-api-specs/blob/main/specification/web/resource-manager/Microsoft.Web/AppService/stable/2024-11-01/WebApps.json#L2675)) generated `bytes`-only input (e.g. [`WebAppsOperations.get_web_site_container_logs(...)`](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/appservice/azure-mgmt-web/azure/mgmt/web/operations/_web_apps_operations.py#L22070)). After migrating to TypeSpec, the input type widens to `IO | bytes`. Since `bytes` is still accepted, existing callers require **no code changes**, making this migration **non-breaking**. + From ec80958b371f0fdef8985e1b1da3db5deeb07d07 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 25 Feb 2026 11:56:45 +0800 Subject: [PATCH 5/9] docs: add Http.File response body section Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../developer/http_file_body_compatibility.md | 40 ++++++++++++++++--- docs/developer/readme.md | 2 +- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/docs/developer/http_file_body_compatibility.md b/docs/developer/http_file_body_compatibility.md index 7236e17920d..c4d19b624c2 100644 --- a/docs/developer/http_file_body_compatibility.md +++ b/docs/developer/http_file_body_compatibility.md @@ -1,10 +1,17 @@ -# `Http.File` as Python Request Body +# `Http.File` Compatibility for Python ## Overview -TypeSpec `Http.File` maps to `IO | bytes` in generated Python SDK methods. This widens the Swagger `type: file` signature (previously `bytes` only) while keeping full backward compatibility. +TypeSpec `Http.File` is emitted differently depending on whether it appears as a request body or a response body: -## TypeSpec Definition +- **Request body**: emits `IO | bytes` (widens legacy `bytes`-only signature) +- **Response body**: emits `bytes` (identical to legacy signature) + +Both cases preserve full backward compatibility with Swagger `type: file`. + +## Request Body + +### TypeSpec Definition ```typespec import "@typespec/http"; @@ -18,7 +25,7 @@ namespace Files; op upload(@body body: Http.File): void; ``` -## Generated Python SDK +### Generated Python SDK ```python from typing import IO, overload @@ -35,7 +42,30 @@ def upload(self, body: IO | bytes, **kwargs) -> None: ... ``` -## Backward Compatibility +### Backward Compatibility Swagger `type: file`(e.g. [`operationId: "WebApps_GetWebSiteContainerLogs"`](https://github.com/Azure/azure-rest-api-specs/blob/main/specification/web/resource-manager/Microsoft.Web/AppService/stable/2024-11-01/WebApps.json#L2675)) generated `bytes`-only input (e.g. [`WebAppsOperations.get_web_site_container_logs(...)`](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/appservice/azure-mgmt-web/azure/mgmt/web/operations/_web_apps_operations.py#L22070)). After migrating to TypeSpec, the input type widens to `IO | bytes`. Since `bytes` is still accepted, existing callers require **no code changes**, making this migration **non-breaking**. +## Response Body + +### TypeSpec Definition + +```typespec +@route("/files") +namespace Files; + +@get +op download(): Http.File; +``` + +### Generated Python SDK + +```python +def download(self, **kwargs) -> bytes: + ... +``` + +### Backward Compatibility + +Swagger `type: file` responses also generated `bytes`. The TypeSpec `Http.File` response emits the same `bytes` return type, so existing callers are **completely unaffected**. + diff --git a/docs/developer/readme.md b/docs/developer/readme.md index 81524fcf984..d9f4926f6c4 100644 --- a/docs/developer/readme.md +++ b/docs/developer/readme.md @@ -15,4 +15,4 @@ To test an unpublished branded emitter tarball, please follow these steps: ## Design Notes -- [`Http.File` request body compatibility for Python](./http_file_body_compatibility.md) +- [`Http.File` compatibility for Python](./http_file_body_compatibility.md) From d1535cc0de10562ba312ab5e3c3d605cc4a05235 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 25 Feb 2026 13:24:25 +0800 Subject: [PATCH 6/9] docs: update Http.File request body compatibility and overview --- docs/developer/http_file_body_compatibility.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/developer/http_file_body_compatibility.md b/docs/developer/http_file_body_compatibility.md index c4d19b624c2..4974f9b6a44 100644 --- a/docs/developer/http_file_body_compatibility.md +++ b/docs/developer/http_file_body_compatibility.md @@ -1,13 +1,13 @@ -# `Http.File` Compatibility for Python +# Python SDK design for `Http.File` when it appears in non-multipart request body and response body. ## Overview -TypeSpec `Http.File` is emitted differently depending on whether it appears as a request body or a response body: +TypeSpec `Http.File` is emitted differently depending on whether it appears as a non-multipart request body or a response body: -- **Request body**: emits `IO | bytes` (widens legacy `bytes`-only signature) -- **Response body**: emits `bytes` (identical to legacy signature) +- **Request body**: emits `IO | bytes` (new pattern; Swagger `type: file` was never used in non-multipart request bodies) +- **Response body**: emits `bytes` (identical to legacy Swagger `type: file` signature) -Both cases preserve full backward compatibility with Swagger `type: file`. +Since `type: file` was not used in non-multipart request bodies, the request body change is purely additive. The response body remains fully backward compatible. ## Request Body @@ -42,10 +42,6 @@ def upload(self, body: IO | bytes, **kwargs) -> None: ... ``` -### Backward Compatibility - -Swagger `type: file`(e.g. [`operationId: "WebApps_GetWebSiteContainerLogs"`](https://github.com/Azure/azure-rest-api-specs/blob/main/specification/web/resource-manager/Microsoft.Web/AppService/stable/2024-11-01/WebApps.json#L2675)) generated `bytes`-only input (e.g. [`WebAppsOperations.get_web_site_container_logs(...)`](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/appservice/azure-mgmt-web/azure/mgmt/web/operations/_web_apps_operations.py#L22070)). After migrating to TypeSpec, the input type widens to `IO | bytes`. Since `bytes` is still accepted, existing callers require **no code changes**, making this migration **non-breaking**. - ## Response Body ### TypeSpec Definition @@ -67,5 +63,5 @@ def download(self, **kwargs) -> bytes: ### Backward Compatibility -Swagger `type: file` responses also generated `bytes`. The TypeSpec `Http.File` response emits the same `bytes` return type, so existing callers are **completely unaffected**. +Swagger `type: file` (e.g. [`operationId: "WebApps_GetWebSiteContainerLogs"`](https://github.com/Azure/azure-rest-api-specs/blob/main/specification/web/resource-manager/Microsoft.Web/AppService/stable/2024-11-01/WebApps.json#L2675)) responses also generated `bytes` (e.g. [`WebAppsOperations.get_web_site_container_logs(...)`](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/appservice/azure-mgmt-web/azure/mgmt/web/operations/_web_apps_operations.py#L22070)). The TypeSpec `Http.File` response emits the same `bytes` return type, so existing callers are **completely unaffected**. From 13a9ce4943ea0436817a0ab7ccb52e51e11c1465 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 25 Feb 2026 13:25:40 +0800 Subject: [PATCH 7/9] revert: undo changes to docs/developer/readme.md --- docs/developer/readme.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/developer/readme.md b/docs/developer/readme.md index d9f4926f6c4..cd16dc30f3b 100644 --- a/docs/developer/readme.md +++ b/docs/developer/readme.md @@ -12,7 +12,3 @@ To test an unpublished branded emitter tarball, please follow these steps: 6. Paste the url as the version for the `"@azure-tools/typespec-python"` package 7. Run `tsp-client generate-lock-file` from the `azure-sdk-for-python/eng` folder 8. Go to the sdk you'd like to regenerate with the tarball, then run `tsp-client update` how you normally would - -## Design Notes - -- [`Http.File` compatibility for Python](./http_file_body_compatibility.md) From 17877e8633c88197f15459a0cc39490d42338596 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 25 Feb 2026 13:34:58 +0800 Subject: [PATCH 8/9] docs: add references to TypeSpec Http.File docs and Spector tests --- docs/developer/http_file_body_compatibility.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/developer/http_file_body_compatibility.md b/docs/developer/http_file_body_compatibility.md index 4974f9b6a44..1815a4ad044 100644 --- a/docs/developer/http_file_body_compatibility.md +++ b/docs/developer/http_file_body_compatibility.md @@ -65,3 +65,8 @@ def download(self, **kwargs) -> bytes: Swagger `type: file` (e.g. [`operationId: "WebApps_GetWebSiteContainerLogs"`](https://github.com/Azure/azure-rest-api-specs/blob/main/specification/web/resource-manager/Microsoft.Web/AppService/stable/2024-11-01/WebApps.json#L2675)) responses also generated `bytes` (e.g. [`WebAppsOperations.get_web_site_container_logs(...)`](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/appservice/azure-mgmt-web/azure/mgmt/web/operations/_web_apps_operations.py#L22070)). The TypeSpec `Http.File` response emits the same `bytes` return type, so existing callers are **completely unaffected**. +## References + +- [TypeSpec `Http.File` documentation](https://typespec.io/docs/libraries/http/files/#using-httpfile-in-operations) — explains how `Http.File` works in operations (uploading, downloading, multipart payloads, custom file models, and when a model is effectively a `File`). +- [Spector test cases for `Http.File`](https://github.com/microsoft/typespec/blob/main/packages/http-specs/specs/type/file/main.tsp) — conformance tests covering file upload/download with specific, JSON, multiple, and default content types. + From 7e031768b4049437026b3c4ca2cc8467528fb77d Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 25 Feb 2026 13:36:41 +0800 Subject: [PATCH 9/9] docs: clean up title and simplify TypeSpec examples --- docs/developer/http_file_body_compatibility.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/docs/developer/http_file_body_compatibility.md b/docs/developer/http_file_body_compatibility.md index 1815a4ad044..c8909456085 100644 --- a/docs/developer/http_file_body_compatibility.md +++ b/docs/developer/http_file_body_compatibility.md @@ -1,4 +1,4 @@ -# Python SDK design for `Http.File` when it appears in non-multipart request body and response body. +# Python SDK design for `Http.File` when it appears in non-multipart request body and response body ## Overview @@ -14,13 +14,6 @@ Since `type: file` was not used in non-multipart request bodies, the request bod ### TypeSpec Definition ```typespec -import "@typespec/http"; - -using TypeSpec.Http; - -@route("/files") -namespace Files; - @post op upload(@body body: Http.File): void; ``` @@ -47,9 +40,6 @@ def upload(self, body: IO | bytes, **kwargs) -> None: ### TypeSpec Definition ```typespec -@route("/files") -namespace Files; - @get op download(): Http.File; ```