diff --git a/.chronus/changes/explode_allowreserved-2024-7-29-17-18-49.md b/.chronus/changes/explode_allowreserved-2024-7-29-17-18-49.md new file mode 100644 index 0000000000..d02c7c60a0 --- /dev/null +++ b/.chronus/changes/explode_allowreserved-2024-7-29-17-18-49.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@azure-tools/typespec-python" +--- + +support query `explode` and path `allowReserved`, also change the logic of generating spread body parameter \ No newline at end of file diff --git a/packages/typespec-python/generator/pygen/codegen/serializers/general_serializer.py b/packages/typespec-python/generator/pygen/codegen/serializers/general_serializer.py index 69da33ee31..2a2c1c24d6 100644 --- a/packages/typespec-python/generator/pygen/codegen/serializers/general_serializer.py +++ b/packages/typespec-python/generator/pygen/codegen/serializers/general_serializer.py @@ -182,12 +182,13 @@ def serialize_validation_file(self) -> str: def serialize_cross_language_definition_file(self) -> str: cross_langauge_def_dict = { f"{self.code_model.namespace}.models.{model.name}": model.cross_language_definition_id - for model in self.code_model.model_types + for model in self.code_model.public_model_types } cross_langauge_def_dict.update( { f"{self.code_model.namespace}.models.{enum.name}": enum.cross_language_definition_id for enum in self.code_model.enums + if not enum.internal } ) cross_langauge_def_dict.update( diff --git a/packages/typespec-python/package.json b/packages/typespec-python/package.json index 1892b00acb..d892c3976e 100644 --- a/packages/typespec-python/package.json +++ b/packages/typespec-python/package.json @@ -48,7 +48,7 @@ "@azure-tools/typespec-azure-core": ">=0.45.0 <1.0.0", "@azure-tools/typespec-azure-resource-manager": ">=0.45.0 <1.0.0", "@azure-tools/typespec-autorest": ">=0.45.0 <1.0.0", - "@azure-tools/typespec-client-generator-core": ">=0.45.1 <1.0.0", + "@azure-tools/typespec-client-generator-core": ">=0.45.4 <1.0.0", "@azure-tools/typespec-azure-rulesets": ">=0.45.0 <3.0.0", "@typespec/compiler": ">=0.59.1 <1.0.0", "@typespec/http": ">=0.59.0 <1.0.0", diff --git a/packages/typespec-python/scripts/eng/regenerate.ts b/packages/typespec-python/scripts/eng/regenerate.ts index ccef84a283..cf518fd521 100644 --- a/packages/typespec-python/scripts/eng/regenerate.ts +++ b/packages/typespec-python/scripts/eng/regenerate.ts @@ -239,7 +239,7 @@ function addOptions(spec: string, generatedFolder: string, flags: RegenerateFlag if (flags.flavor === "unbranded") { options["company-name"] = "Unbranded"; } - options["examples-directory"] = toPosix(join(dirname(spec), "examples")); + options["examples-dir"] = toPosix(join(dirname(spec), "examples")); const configs = Object.entries(options).flatMap(([k, v]) => { return `--option @azure-tools/typespec-python.${k}=${v}`; }); diff --git a/packages/typespec-python/src/code-model.ts b/packages/typespec-python/src/code-model.ts index 2affd518cf..90ee2f441c 100644 --- a/packages/typespec-python/src/code-model.ts +++ b/packages/typespec-python/src/code-model.ts @@ -227,7 +227,12 @@ export function emitCodeModel( } // loop through models and enums since there may be some orphaned models needs to be generated for (const model of sdkPackage.models) { - if (model.name === "" || (model.usage & UsageFlags.Spread) > 0) { + if ( + model.name === "" || + ((model.usage & UsageFlags.Spread) > 0 && + (model.usage & UsageFlags.Input) === 0 && + (model.usage & UsageFlags.Output) === 0) + ) { continue; } if (!disableGenerationMap.has(model)) { diff --git a/packages/typespec-python/src/http.ts b/packages/typespec-python/src/http.ts index 220438b7d4..c22c501700 100644 --- a/packages/typespec-python/src/http.ts +++ b/packages/typespec-python/src/http.ts @@ -19,7 +19,7 @@ import { camelToSnakeCase, emitParamBase, getAddedOn, - getDelimeterAndExplode, + getDelimiterAndExplode, getDescriptionAndSummary, getImplementation, isAbstract, @@ -160,7 +160,7 @@ function emitHttpOperation( rootClient: SdkClientType, operationGroupName: string, operation: SdkHttpOperation, - method?: SdkServiceMethod, + method: SdkServiceMethod, ): Record { const responses: Record[] = []; const exceptions: Record[] = []; @@ -188,13 +188,16 @@ function emitHttpOperation( crossLanguageDefinitionId: method?.crossLanguageDefintionId, samples: arrayToRecord(method?.operation.examples), }; - if ( - result.bodyParameter && - operation.bodyParam?.type.kind === "model" && - (operation.bodyParam?.type.usage & UsageFlags.Spread) > 0 - ) { + if (result.bodyParameter && isSpreadBody(operation.bodyParam)) { result.bodyParameter["propertyToParameterName"] = {}; result.bodyParameter["defaultToUnsetSentinel"] = true; + // if body type is not only used for this spread body, but also used in other input/output, we should clone it, then change the type base to json + if ( + (result.bodyParameter.type.usage & UsageFlags.Input) > 0 || + (result.bodyParameter.type.usage & UsageFlags.Output) > 0 + ) { + result.bodyParameter.type = { ...result.bodyParameter.type, name: `${method.name}Request` }; + } result.bodyParameter.type.base = "json"; for (const property of result.bodyParameter.type.properties) { result.bodyParameter["propertyToParameterName"][property["wireName"]] = property["clientName"]; @@ -204,6 +207,10 @@ function emitHttpOperation( return result; } +function isSpreadBody(bodyParam: SdkBodyParameter | undefined): boolean { + return bodyParam?.type.kind === "model" && bodyParam.type !== bodyParam.correspondingMethodParams[0]?.type; +} + function emitFlattenedParameter( bodyParameter: Record, property: Record, @@ -237,7 +244,7 @@ function emitHttpPathParameter(context: PythonSdkContext, para location: parameter.kind, implementation: getImplementation(context, parameter), clientDefaultValue: parameter.clientDefaultValue, - skipUrlEncoding: parameter.urlEncode === false, + skipUrlEncoding: parameter.allowReserved, }; } function emitHttpHeaderParameter( @@ -245,7 +252,7 @@ function emitHttpHeaderParameter( parameter: SdkHeaderParameter, ): Record { const base = emitParamBase(context, parameter); - const [delimiter, explode] = getDelimeterAndExplode(parameter); + const [delimiter, explode] = getDelimiterAndExplode(parameter); let clientDefaultValue = parameter.clientDefaultValue; if (isContentTypeParameter(parameter)) { // we switch to string type for content-type header @@ -270,7 +277,7 @@ function emitHttpQueryParameter( parameter: SdkQueryParameter, ): Record { const base = emitParamBase(context, parameter); - const [delimiter, explode] = getDelimeterAndExplode(parameter); + const [delimiter, explode] = getDelimiterAndExplode(parameter); return { ...base, wireName: parameter.serializedName, @@ -310,7 +317,7 @@ function emitHttpBodyParameter( ): Record | undefined { if (bodyParam === undefined) return undefined; return { - ...emitParamBase(context, bodyParam, true), + ...emitParamBase(context, bodyParam), contentTypes: bodyParam.contentTypes, location: bodyParam.kind, clientName: bodyParam.isGeneratedName ? "body" : camelToSnakeCase(bodyParam.name), diff --git a/packages/typespec-python/src/lib.ts b/packages/typespec-python/src/lib.ts index ba93d49efc..6704db56e5 100644 --- a/packages/typespec-python/src/lib.ts +++ b/packages/typespec-python/src/lib.ts @@ -16,6 +16,7 @@ export interface PythonEmitterOptions { "generate-test"?: boolean; "debug"?: boolean; "flavor"?: "azure"; + "examples-dir"?: string; } export interface PythonSdkContext @@ -41,6 +42,7 @@ const EmitterOptionsSchema: JSONSchemaType = { "generate-test": { type: "boolean", nullable: true }, "debug": { type: "boolean", nullable: true }, "flavor": { type: "string", nullable: true }, + "examples-dir": { type: "string", nullable: true, format: "absolute-path" }, }, required: [], }; diff --git a/packages/typespec-python/src/types.ts b/packages/typespec-python/src/types.ts index 6c69870cd4..d89dda5ad7 100644 --- a/packages/typespec-python/src/types.ts +++ b/packages/typespec-python/src/types.ts @@ -66,11 +66,10 @@ export function getSimpleTypeResult(result: Record): Record( context: PythonSdkContext, type: CredentialType | CredentialTypeUnion | Type | SdkType | MultiPartFileType, - fromBody = false, ): Record { switch (type.kind) { case "model": - return emitModel(context, type, fromBody); + return emitModel(context, type); case "union": return emitUnion(context, type); case "enum": @@ -239,7 +238,6 @@ function emitProperty( function emitModel( context: PythonSdkContext, type: SdkModelType, - fromBody: boolean, ): Record { if (isEmptyModel(type)) { return KnownTypes.any; @@ -257,7 +255,7 @@ function emitModel( discriminatedSubtypes: {} as Record>, properties: new Array>(), snakeCaseName: camelToSnakeCase(type.name), - base: type.isGeneratedName && fromBody ? "json" : "dpg", + base: "dpg", internal: type.access === "internal", crossLanguageDefinitionId: type.crossLanguageDefinitionId, usage: type.usage, @@ -476,7 +474,7 @@ export function emitEndpointType( location: "endpointPath", implementation: getImplementation(context, param), clientDefaultValue: param.clientDefaultValue, - skipUrlEncoding: param.urlEncode === false, + skipUrlEncoding: param.urlEncode === false, // eslint-disable-line deprecation/deprecation }); context.__endpointPathParameters!.push(params.at(-1)!); } diff --git a/packages/typespec-python/src/utils.ts b/packages/typespec-python/src/utils.ts index 7b25a68e26..a65bc25eed 100644 --- a/packages/typespec-python/src/utils.ts +++ b/packages/typespec-python/src/utils.ts @@ -43,13 +43,13 @@ export function isAbstract( return (method.operation.bodyParam?.contentTypes.length ?? 0) > 1 && method.access !== "internal"; } -export function getDelimeterAndExplode( +export function getDelimiterAndExplode( parameter: SdkQueryParameter | SdkHeaderParameter, ): [string | undefined, boolean] { if (parameter.type.kind !== "array") return [undefined, false]; let delimiter: string | undefined = undefined; - let explode = false; - if (parameter.collectionFormat === "csv") { + let explode = parameter.kind === "query" && parameter.explode; + if (parameter.collectionFormat === "csv" || parameter.collectionFormat === "simple") { delimiter = "comma"; } else if (parameter.collectionFormat === "ssv") { delimiter = "space"; @@ -90,9 +90,8 @@ export function getAddedOn( export function emitParamBase( context: PythonSdkContext, parameter: SdkParameter | SdkHttpParameter, - fromBody: boolean = false, ): ParamBase { - let type = getType(context, parameter.type, fromBody); + let type = getType(context, parameter.type); if (parameter.isApiVersionParam) { if (parameter.clientDefaultValue) { type = getSimpleTypeResult({ type: "constant", value: parameter.clientDefaultValue, valueType: type }); diff --git a/packages/typespec-python/test/azure/generated/azure-client-generator-core-access/apiview_mapping_python.json b/packages/typespec-python/test/azure/generated/azure-client-generator-core-access/apiview_mapping_python.json index 52c86eea8c..00f5a03148 100644 --- a/packages/typespec-python/test/azure/generated/azure-client-generator-core-access/apiview_mapping_python.json +++ b/packages/typespec-python/test/azure/generated/azure-client-generator-core-access/apiview_mapping_python.json @@ -1,16 +1,9 @@ { "CrossLanguagePackageId": "_Specs_.Azure.ClientGenerator.Core.Access", "CrossLanguageDefinitionId": { - "specs.azure.clientgenerator.core.access.models.AbstractModel": "_Specs_.Azure.ClientGenerator.Core.Access.RelativeModelInOperation.AbstractModel", - "specs.azure.clientgenerator.core.access.models.BaseModel": "_Specs_.Azure.ClientGenerator.Core.Access.RelativeModelInOperation.BaseModel", - "specs.azure.clientgenerator.core.access.models.InnerModel": "_Specs_.Azure.ClientGenerator.Core.Access.RelativeModelInOperation.InnerModel", - "specs.azure.clientgenerator.core.access.models.InternalDecoratorModelInInternal": "_Specs_.Azure.ClientGenerator.Core.Access.InternalOperation.InternalDecoratorModelInInternal", - "specs.azure.clientgenerator.core.access.models.NoDecoratorModelInInternal": "_Specs_.Azure.ClientGenerator.Core.Access.InternalOperation.NoDecoratorModelInInternal", "specs.azure.clientgenerator.core.access.models.NoDecoratorModelInPublic": "_Specs_.Azure.ClientGenerator.Core.Access.PublicOperation.NoDecoratorModelInPublic", - "specs.azure.clientgenerator.core.access.models.OuterModel": "_Specs_.Azure.ClientGenerator.Core.Access.RelativeModelInOperation.OuterModel", "specs.azure.clientgenerator.core.access.models.PublicDecoratorModelInInternal": "_Specs_.Azure.ClientGenerator.Core.Access.InternalOperation.PublicDecoratorModelInInternal", "specs.azure.clientgenerator.core.access.models.PublicDecoratorModelInPublic": "_Specs_.Azure.ClientGenerator.Core.Access.PublicOperation.PublicDecoratorModelInPublic", - "specs.azure.clientgenerator.core.access.models.RealModel": "_Specs_.Azure.ClientGenerator.Core.Access.RelativeModelInOperation.RealModel", "specs.azure.clientgenerator.core.access.models.SharedModel": "_Specs_.Azure.ClientGenerator.Core.Access.SharedModelInOperation.SharedModel", "specs.azure.clientgenerator.core.access.AccessClient.public_operation.no_decorator_in_public": "_Specs_.Azure.ClientGenerator.Core.Access.PublicOperation.noDecoratorInPublic", "specs.azure.clientgenerator.core.access.AccessClient.public_operation.public_decorator_in_public": "_Specs_.Azure.ClientGenerator.Core.Access.PublicOperation.publicDecoratorInPublic", diff --git a/packages/typespec-python/test/azure/generated/parameters-basic/apiview_mapping_python.json b/packages/typespec-python/test/azure/generated/parameters-basic/apiview_mapping_python.json index fc45432b56..3f5d96dd1c 100644 --- a/packages/typespec-python/test/azure/generated/parameters-basic/apiview_mapping_python.json +++ b/packages/typespec-python/test/azure/generated/parameters-basic/apiview_mapping_python.json @@ -1,7 +1,6 @@ { "CrossLanguagePackageId": "Parameters.Basic", "CrossLanguageDefinitionId": { - "parameters.basic.models.SimpleRequest": "Parameters.Basic.ImplicitBody.simple.Request.anonymous", "parameters.basic.models.User": "Parameters.Basic.ExplicitBody.User", "parameters.basic.BasicClient.explicit_body.simple": "Parameters.Basic.ExplicitBody.simple", "parameters.basic.BasicClient.implicit_body.simple": "Parameters.Basic.ImplicitBody.simple" diff --git a/packages/typespec-python/test/azure/generated/parameters-body-optionality/apiview_mapping_python.json b/packages/typespec-python/test/azure/generated/parameters-body-optionality/apiview_mapping_python.json index dc18c2eb6f..67bff97540 100644 --- a/packages/typespec-python/test/azure/generated/parameters-body-optionality/apiview_mapping_python.json +++ b/packages/typespec-python/test/azure/generated/parameters-body-optionality/apiview_mapping_python.json @@ -2,7 +2,6 @@ "CrossLanguagePackageId": "Parameters.BodyOptionality", "CrossLanguageDefinitionId": { "parameters.bodyoptionality.models.BodyModel": "Parameters.BodyOptionality.BodyModel", - "parameters.bodyoptionality.models.RequiredImplicitRequest": "Parameters.BodyOptionality.requiredImplicit.Request.anonymous", "parameters.bodyoptionality.BodyOptionalityClient.optional_explicit.set": "Parameters.BodyOptionality.OptionalExplicit.set", "parameters.bodyoptionality.BodyOptionalityClient.optional_explicit.omit": "Parameters.BodyOptionality.OptionalExplicit.omit", "parameters.bodyoptionality.BodyOptionalityClient.required_explicit": "Parameters.BodyOptionality.requiredExplicit", diff --git a/packages/typespec-python/test/azure/generated/parameters-spread/apiview_mapping_python.json b/packages/typespec-python/test/azure/generated/parameters-spread/apiview_mapping_python.json index 1e1f0ba40f..4ba87f686f 100644 --- a/packages/typespec-python/test/azure/generated/parameters-spread/apiview_mapping_python.json +++ b/packages/typespec-python/test/azure/generated/parameters-spread/apiview_mapping_python.json @@ -2,13 +2,6 @@ "CrossLanguagePackageId": "Parameters.Spread", "CrossLanguageDefinitionId": { "parameters.spread.models.BodyParameter": "Parameters.Spread.Model.BodyParameter", - "parameters.spread.models.SpreadAsRequestBodyRequest": "Parameters.Spread.Alias.spreadAsRequestBody.Request.anonymous", - "parameters.spread.models.SpreadAsRequestBodyRequest1": "Parameters.Spread.Model.spreadAsRequestBody.Request.anonymous", - "parameters.spread.models.SpreadAsRequestParameterRequest": "spreadAsRequestParameter.Request.anonymous", - "parameters.spread.models.SpreadCompositeRequestMixRequest": "spreadCompositeRequestMix.Request.anonymous", - "parameters.spread.models.SpreadParameterWithInnerAliasRequest": "spreadParameterWithInnerAlias.Request.anonymous", - "parameters.spread.models.SpreadParameterWithInnerModelRequest": "spreadParameterWithInnerModel.Request.anonymous", - "parameters.spread.models.SpreadWithMultipleParametersRequest": "spreadWithMultipleParameters.Request.anonymous", "parameters.spread.SpreadClient.model.spread_as_request_body": "Parameters.Spread.Model.spreadAsRequestBody", "parameters.spread.SpreadClient.model.spread_composite_request_only_with_body": "Parameters.Spread.Model.spreadCompositeRequestOnlyWithBody", "parameters.spread.SpreadClient.model.spread_composite_request_without_body": "Parameters.Spread.Model.spreadCompositeRequestWithoutBody", diff --git a/packages/typespec-python/test/azure/generated/payload-multipart/apiview_mapping_python.json b/packages/typespec-python/test/azure/generated/payload-multipart/apiview_mapping_python.json index 747b671dcb..a8229c80f2 100644 --- a/packages/typespec-python/test/azure/generated/payload-multipart/apiview_mapping_python.json +++ b/packages/typespec-python/test/azure/generated/payload-multipart/apiview_mapping_python.json @@ -2,7 +2,6 @@ "CrossLanguagePackageId": "Payload.MultiPart", "CrossLanguageDefinitionId": { "payload.multipart.models.Address": "Payload.MultiPart.Address", - "payload.multipart.models.AnonymousModelRequest": "anonymousModel.Request.anonymous", "payload.multipart.models.BinaryArrayPartsRequest": "Payload.MultiPart.BinaryArrayPartsRequest", "payload.multipart.models.ComplexHttpPartsModelRequest": "Payload.MultiPart.ComplexHttpPartsModelRequest", "payload.multipart.models.ComplexPartsRequest": "Payload.MultiPart.ComplexPartsRequest", diff --git a/packages/typespec-python/test/azure/generated/typetest-union/apiview_mapping_python.json b/packages/typespec-python/test/azure/generated/typetest-union/apiview_mapping_python.json index bf8eca1b0b..a4ddeed3ca 100644 --- a/packages/typespec-python/test/azure/generated/typetest-union/apiview_mapping_python.json +++ b/packages/typespec-python/test/azure/generated/typetest-union/apiview_mapping_python.json @@ -16,16 +16,6 @@ "typetest.union.models.GetResponse9": "Type.Union.get.Response.anonymous", "typetest.union.models.MixedLiteralsCases": "Type.Union.MixedLiteralsCases", "typetest.union.models.MixedTypesCases": "Type.Union.MixedTypesCases", - "typetest.union.models.SendRequest": "Type.Union.send.Request.anonymous", - "typetest.union.models.SendRequest1": "Type.Union.send.Request.anonymous", - "typetest.union.models.SendRequest2": "Type.Union.send.Request.anonymous", - "typetest.union.models.SendRequest3": "Type.Union.send.Request.anonymous", - "typetest.union.models.SendRequest4": "Type.Union.send.Request.anonymous", - "typetest.union.models.SendRequest5": "Type.Union.send.Request.anonymous", - "typetest.union.models.SendRequest6": "Type.Union.send.Request.anonymous", - "typetest.union.models.SendRequest7": "Type.Union.send.Request.anonymous", - "typetest.union.models.SendRequest8": "Type.Union.send.Request.anonymous", - "typetest.union.models.SendRequest9": "Type.Union.send.Request.anonymous", "typetest.union.models.StringAndArrayCases": "Type.Union.StringAndArrayCases", "typetest.union.models.StringExtensibleNamedUnion": "Type.Union.StringExtensibleNamedUnion", "typetest.union.UnionClient.strings_only.get": "Type.Union.StringsOnly.get",