Skip to content

Commit

Permalink
support query explode and path allowReserved (#2775)
Browse files Browse the repository at this point in the history
* support query `explode` and path `allowReserved`

* update example related change

* support new spread logic change

* changelog

* format and lint
  • Loading branch information
tadelesh authored Aug 30, 2024
1 parent da6845c commit 3856eb4
Show file tree
Hide file tree
Showing 15 changed files with 44 additions and 52 deletions.
7 changes: 7 additions & 0 deletions .chronus/changes/explode_allowreserved-2024-7-29-17-18-49.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion packages/typespec-python/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/typespec-python/scripts/eng/regenerate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
});
Expand Down
7 changes: 6 additions & 1 deletion packages/typespec-python/src/code-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,12 @@ export function emitCodeModel<TServiceOperation extends SdkServiceOperation>(
}
// 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)) {
Expand Down
29 changes: 18 additions & 11 deletions packages/typespec-python/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
camelToSnakeCase,
emitParamBase,
getAddedOn,
getDelimeterAndExplode,
getDelimiterAndExplode,
getDescriptionAndSummary,
getImplementation,
isAbstract,
Expand Down Expand Up @@ -160,7 +160,7 @@ function emitHttpOperation(
rootClient: SdkClientType<SdkHttpOperation>,
operationGroupName: string,
operation: SdkHttpOperation,
method?: SdkServiceMethod<SdkHttpOperation>,
method: SdkServiceMethod<SdkHttpOperation>,
): Record<string, any> {
const responses: Record<string, any>[] = [];
const exceptions: Record<string, any>[] = [];
Expand Down Expand Up @@ -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"];
Expand All @@ -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<string, any>,
property: Record<string, any>,
Expand Down Expand Up @@ -237,15 +244,15 @@ function emitHttpPathParameter(context: PythonSdkContext<SdkHttpOperation>, para
location: parameter.kind,
implementation: getImplementation(context, parameter),
clientDefaultValue: parameter.clientDefaultValue,
skipUrlEncoding: parameter.urlEncode === false,
skipUrlEncoding: parameter.allowReserved,
};
}
function emitHttpHeaderParameter(
context: PythonSdkContext<SdkHttpOperation>,
parameter: SdkHeaderParameter,
): Record<string, any> {
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
Expand All @@ -270,7 +277,7 @@ function emitHttpQueryParameter(
parameter: SdkQueryParameter,
): Record<string, any> {
const base = emitParamBase(context, parameter);
const [delimiter, explode] = getDelimeterAndExplode(parameter);
const [delimiter, explode] = getDelimiterAndExplode(parameter);
return {
...base,
wireName: parameter.serializedName,
Expand Down Expand Up @@ -310,7 +317,7 @@ function emitHttpBodyParameter(
): Record<string, any> | 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),
Expand Down
2 changes: 2 additions & 0 deletions packages/typespec-python/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface PythonEmitterOptions {
"generate-test"?: boolean;
"debug"?: boolean;
"flavor"?: "azure";
"examples-dir"?: string;
}

export interface PythonSdkContext<TServiceOperation extends SdkServiceOperation>
Expand All @@ -41,6 +42,7 @@ const EmitterOptionsSchema: JSONSchemaType<PythonEmitterOptions> = {
"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: [],
};
Expand Down
8 changes: 3 additions & 5 deletions packages/typespec-python/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,10 @@ export function getSimpleTypeResult(result: Record<string, any>): Record<string,
export function getType<TServiceOperation extends SdkServiceOperation>(
context: PythonSdkContext<TServiceOperation>,
type: CredentialType | CredentialTypeUnion | Type | SdkType | MultiPartFileType,
fromBody = false,
): Record<string, any> {
switch (type.kind) {
case "model":
return emitModel(context, type, fromBody);
return emitModel(context, type);
case "union":
return emitUnion(context, type);
case "enum":
Expand Down Expand Up @@ -239,7 +238,6 @@ function emitProperty<TServiceOperation extends SdkServiceOperation>(
function emitModel<TServiceOperation extends SdkServiceOperation>(
context: PythonSdkContext<TServiceOperation>,
type: SdkModelType,
fromBody: boolean,
): Record<string, any> {
if (isEmptyModel(type)) {
return KnownTypes.any;
Expand All @@ -257,7 +255,7 @@ function emitModel<TServiceOperation extends SdkServiceOperation>(
discriminatedSubtypes: {} as Record<string, Record<string, any>>,
properties: new Array<Record<string, any>>(),
snakeCaseName: camelToSnakeCase(type.name),
base: type.isGeneratedName && fromBody ? "json" : "dpg",
base: "dpg",
internal: type.access === "internal",
crossLanguageDefinitionId: type.crossLanguageDefinitionId,
usage: type.usage,
Expand Down Expand Up @@ -476,7 +474,7 @@ export function emitEndpointType<TServiceOperation extends SdkServiceOperation>(
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)!);
}
Expand Down
9 changes: 4 additions & 5 deletions packages/typespec-python/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ export function isAbstract<TServiceOperation extends SdkServiceOperation>(
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";
Expand Down Expand Up @@ -90,9 +90,8 @@ export function getAddedOn<TServiceOperation extends SdkServiceOperation>(
export function emitParamBase<TServiceOperation extends SdkServiceOperation>(
context: PythonSdkContext<TServiceOperation>,
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 });
Expand Down
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 3856eb4

Please sign in to comment.