Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .chronus/changes/python-nightly-fix-2026-1-6-9-19-9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/http-client-python"
---

Fall back "Http.File" to "bytes" to avoid generation failure
51 changes: 47 additions & 4 deletions packages/http-client-python/emitter/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ function emitHttpHeaderParameter(
parameter: SdkHeaderParameter,
method: SdkServiceMethod<SdkHttpOperation>,
serviceApiVersions: string[],
bodyParam?: SdkBodyParameter,
): Record<string, any> {
const base = emitParamBase(context, parameter, method, serviceApiVersions);
const [delimiter, explode] = getDelimiterAndExplode(parameter);
Expand All @@ -489,6 +490,16 @@ function emitHttpHeaderParameter(
if (!clientDefaultValue && parameter.type.kind === "constant") {
clientDefaultValue = parameter.type.value;
}
// if still no default value, try to derive from body parameter's content types
if (!clientDefaultValue && bodyParam) {
const fileContentTypes = getHttpFileContentTypes(bodyParam);
const defaultContentType = fileContentTypes
? fileContentTypes[0]
: bodyParam.defaultContentType;
if (defaultContentType) {
clientDefaultValue = defaultContentType;
}
}
base.type = KnownTypes.string;
}
return {
Expand Down Expand Up @@ -571,7 +582,15 @@ function emitHttpParameters(
for (const parameter of httpParameters) {
switch (parameter.kind) {
case "header":
parameters.push(emitHttpHeaderParameter(context, parameter, method, serviceApiVersions));
parameters.push(
emitHttpHeaderParameter(
context,
parameter,
method,
serviceApiVersions,
operation.bodyParam,
),
);
break;
case "query":
parameters.push(
Expand All @@ -587,21 +606,40 @@ function emitHttpParameters(
return parameters;
}

function isHttpFileType(
type: { kind: string; crossLanguageDefinitionId?: string } | undefined,
): boolean {
return type?.kind === "model" && type?.crossLanguageDefinitionId === "TypeSpec.Http.File";
}

function getHttpFileContentTypes(bodyParam: SdkBodyParameter): string[] | undefined {
if (bodyParam.type.kind === "model" && isHttpFileType(bodyParam.type)) {
const contentTypeProp = bodyParam.type.properties.find((p) => p.name === "contentType");
if (contentTypeProp && contentTypeProp.type.kind === "constant" && contentTypeProp.type.value) {
return [String(contentTypeProp.type.value)];
}
}
return undefined;
}

function emitHttpBodyParameter(
context: PythonSdkContext,
bodyParam?: SdkBodyParameter,
serviceApiVersions: string[] = [],
): Record<string, any> | undefined {
if (bodyParam === undefined) return undefined;
const fileContentTypes = getHttpFileContentTypes(bodyParam);
const contentTypes = fileContentTypes ?? bodyParam.contentTypes;
const defaultContentType = fileContentTypes ? fileContentTypes[0] : bodyParam.defaultContentType;
return {
...emitParamBase(context, bodyParam, undefined, serviceApiVersions),
contentTypes: bodyParam.contentTypes,
contentTypes,
location: bodyParam.kind,
clientName: bodyParam.isGeneratedName ? "body" : camelToSnakeCase(bodyParam.name),
wireName: bodyParam.isGeneratedName ? "body" : bodyParam.name,
implementation: getImplementation(context, bodyParam),
clientDefaultValue: bodyParam.clientDefaultValue,
defaultContentType: bodyParam.defaultContentType,
defaultContentType,
};
}

Expand Down Expand Up @@ -637,8 +675,13 @@ function emitHttpResponse(
type["referredByOperationType"] |= referredBy;
}

const httpFile = isHttpFileType(response.type);
const headers = response.headers
.filter((x) => !httpFile || x.serializedName)
.map((x) => emitHttpResponseHeader(context, x));

return {
headers: response.headers.map((x) => emitHttpResponseHeader(context, x)),
headers,
statusCodes:
typeof statusCodes === "object"
? [[(statusCodes as HttpStatusCodeRange).start, (statusCodes as HttpStatusCodeRange).end]]
Expand Down
3 changes: 3 additions & 0 deletions packages/http-client-python/emitter/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ function emitModel(context: PythonSdkContext, type: SdkModelType): Record<string
if (typesMap.has(type)) {
return typesMap.get(type)!;
}
if (type.crossLanguageDefinitionId === "TypeSpec.Http.File") {
return getSimpleTypeResult({ type: "bytes" });
}
if (type.crossLanguageDefinitionId === "Azure.Core.Foundations.Error") {
return {
type: "sdkcore",
Expand Down
14 changes: 12 additions & 2 deletions packages/http-client-python/eng/scripts/ci/regenerate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,14 @@ const AZURE_EMITTER_OPTIONS: Record<string, Record<string, string> | Record<stri
"azure/client-generator-core/api-version": {
namespace: "specs.azure.clientgenerator.core.apiversion",
},
"azure/client-generator-core/client-initialization": {
namespace: "specs.azure.clientgenerator.core.clientinitialization",
"azure/client-generator-core/client-initialization/default": {
namespace: "specs.azure.clientgenerator.core.clientinitialization.default",
},
"azure/client-generator-core/client-initialization/individually": {
namespace: "specs.azure.clientgenerator.core.clientinitialization.individually",
},
"azure/client-generator-core/client-initialization/individuallyParent": {
namespace: "specs.azure.clientgenerator.core.clientinitialization.individuallyparent",
},
"azure/client-generator-core/client-location": {
namespace: "specs.azure.clientgenerator.core.clientlocation",
Expand Down Expand Up @@ -279,6 +285,10 @@ const EMITTER_OPTIONS: Record<string, Record<string, string> | Record<string, st
"package-name": "typetest-discriminatedunion",
namespace: "typetest.discriminatedunion",
},
"type/file": {
"package-name": "typetest-file",
namespace: "typetest.file",
},
documentation: {
"package-name": "specs-documentation",
namespace: "specs.documentation",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ class ClientMixinABC(ABC, Generic[TClient, TConfig]):
def raise_if_not_implemented(cls, abstract_methods):
not_implemented = [f for f in abstract_methods if not callable(getattr(cls, f, None))]
if not_implemented:
raise NotImplementedError("The following methods on operation group '{}' are not implemented: '{}'."
" Please refer to https://aka.ms/azsdk/python/dpcodegen/python/customize to learn how to customize.".format(
cls.__name__, '\', \''.join(not_implemented))
)
def _not_implemented(method_name, og_name):
def _raise(self, *args, **kwargs):
raise NotImplementedError("Method '{}' on operation group '{}' is not implemented."
" Please refer to https://aka.ms/azsdk/python/dpcodegen/python/customize to learn how to customize.".format(
method_name, og_name))
return _raise
for method in not_implemented:
setattr(cls, method, _not_implemented(method, cls.__name__))
{% endif %}

{% if code_model.need_utils_etag(client_namespace) %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
# license information.
# --------------------------------------------------------------------------
import pytest
from specs.azure.clientgenerator.core.clientinitialization.aio import (
from specs.azure.clientgenerator.core.clientinitialization.default.aio import (
HeaderParamClient,
MultipleParamsClient,
MixedParamsClient,
PathParamClient,
ParamAliasClient,
ParentClient,
)
from specs.azure.clientgenerator.core.clientinitialization.models import Input
from specs.azure.clientgenerator.core.clientinitialization.default.models import Input


@pytest.mark.asyncio
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
from specs.azure.clientgenerator.core.clientinitialization import (
from specs.azure.clientgenerator.core.clientinitialization.default import (
HeaderParamClient,
MultipleParamsClient,
MixedParamsClient,
PathParamClient,
ParamAliasClient,
ParentClient,
)
from specs.azure.clientgenerator.core.clientinitialization.models import Input
from specs.azure.clientgenerator.core.clientinitialization.default.models import Input


def test_header_param_client():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ azure-mgmt-core==1.6.0
-e ./generated/azure-client-generator-core-api-version-header
-e ./generated/azure-client-generator-core-api-version-path
-e ./generated/azure-client-generator-core-api-version-query
-e ./generated/azure-client-generator-core-client-initialization
-e ./generated/azure-client-generator-core-client-initialization-default
-e ./generated/azure-client-generator-core-client-initialization-individually
-e ./generated/azure-client-generator-core-client-initialization-individuallyparent
-e ./generated/azure-client-generator-core-deserialize-empty-string-as-null
-e ./generated/azure-client-generator-core-flatten-property
-e ./generated/azure-client-generator-core-hierarchy-building
Expand Down Expand Up @@ -82,6 +84,7 @@ azure-mgmt-core==1.6.0
-e ./generated/typetest-dictionary
-e ./generated/typetest-enum-extensible
-e ./generated/typetest-enum-fixed
-e ./generated/typetest-file
-e ./generated/typetest-model-enumdiscriminator
-e ./generated/typetest-model-nesteddiscriminator
-e ./generated/typetest-model-notdiscriminated
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
import json

import pytest
from typetest.file.aio import FileClient


@pytest.fixture
async def client():
async with FileClient(endpoint="http://localhost:3000") as client:
yield client


@pytest.mark.asyncio
async def test_upload_file_specific_content_type(client: FileClient, png_data: bytes):
await client.body.upload_file_specific_content_type(png_data)


# Do not support this case for now
# @pytest.mark.asyncio
# async def test_upload_file_json_content_type(client: FileClient):
# await client.body.upload_file_json_content_type(json.dumps({"message": "test file content"}).encode())


@pytest.mark.asyncio
async def test_download_file_json_content_type(client: FileClient):
result = await client.body.download_file_json_content_type()
assert result is not None


@pytest.mark.asyncio
async def test_download_file_specific_content_type(client: FileClient, png_data: bytes):
result = b"".join([d async for d in (await client.body.download_file_specific_content_type())])
assert result == png_data


@pytest.mark.asyncio
async def test_download_file_multiple_content_types(client: FileClient, png_data: bytes):
result = b"".join([d async for d in (await client.body.download_file_multiple_content_types())])
assert result == png_data


@pytest.mark.asyncio
async def test_upload_file_default_content_type(client: FileClient, png_data: bytes):
await client.body.upload_file_default_content_type(png_data, content_type="image/png")


@pytest.mark.asyncio
async def test_download_file_default_content_type(client: FileClient, png_data: bytes):
result = b"".join([d async for d in (await client.body.download_file_default_content_type())])
assert result == png_data
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
import json

import pytest
from typetest.file import FileClient


@pytest.fixture
def client():
with FileClient(endpoint="http://localhost:3000") as client:
yield client


def test_upload_file_specific_content_type(client: FileClient, png_data: bytes):
client.body.upload_file_specific_content_type(png_data)


# Do not support this case for now
# def test_upload_file_json_content_type(client: FileClient):
# client.body.upload_file_json_content_type(json.dumps({"message": "test file content"}).encode())


def test_download_file_json_content_type(client: FileClient):
result = client.body.download_file_json_content_type()
assert result is not None


def test_download_file_specific_content_type(client: FileClient, png_data: bytes):
result = b"".join(client.body.download_file_specific_content_type())
assert result == png_data


def test_download_file_multiple_content_types(client: FileClient, png_data: bytes):
result = b"".join(client.body.download_file_multiple_content_types())
assert result == png_data


def test_upload_file_default_content_type(client: FileClient, png_data: bytes):
client.body.upload_file_default_content_type(png_data, content_type="image/png")


def test_download_file_default_content_type(client: FileClient, png_data: bytes):
result = b"".join(client.body.download_file_default_content_type())
assert result == png_data
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
-e ./generated/typetest-dictionary
-e ./generated/typetest-enum-extensible
-e ./generated/typetest-enum-fixed
-e ./generated/typetest-file
-e ./generated/typetest-model-enumdiscriminator
-e ./generated/typetest-model-nesteddiscriminator
-e ./generated/typetest-model-notdiscriminated
Expand Down
16 changes: 8 additions & 8 deletions packages/http-client-python/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/http-client-python/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"@azure-tools/typespec-azure-resource-manager": "~0.64.1",
"@azure-tools/typespec-azure-rulesets": "~0.64.0",
"@azure-tools/typespec-client-generator-core": "~0.64.6",
"@azure-tools/azure-http-specs": "0.1.0-alpha.37-dev.1",
"@azure-tools/azure-http-specs": "0.1.0-alpha.37-dev.3",
"@typespec/compiler": "^1.8.0",
"@typespec/http": "^1.8.0",
"@typespec/openapi": "^1.8.0",
Expand All @@ -94,7 +94,7 @@
"@typespec/sse": "~0.78.0",
"@typespec/streams": "~0.78.0",
"@typespec/xml": "~0.78.0",
"@typespec/http-specs": "0.1.0-alpha.32-dev.5",
"@typespec/http-specs": "0.1.0-alpha.32-dev.6",
"@types/js-yaml": "~4.0.5",
"@types/node": "~24.1.0",
"@types/semver": "7.5.8",
Expand Down
Loading