import { SdkBasicServiceMethod, SdkClientType, SdkCredentialParameter, SdkCredentialType, SdkEndpointParameter, SdkEndpointType, SdkLroPagingServiceMethod, SdkLroServiceMethod, SdkMethodParameter, SdkPagingServiceMethod, SdkServiceMethod, SdkServiceOperation, SdkUnionType, UsageFlags, getCrossLanguagePackageId, isAzureCoreModel, } from "@azure-tools/typespec-client-generator-core"; import { ignoreDiagnostics } from "@typespec/compiler"; import { ReferredByOperationTypes, emitBasicHttpMethod, emitLroHttpMethod, emitLroPagingHttpMethod, emitPagingHttpMethod, } from "./http.js"; import { PythonSdkContext } from "./lib.js"; import { KnownTypes, emitEndpointType, getType } from "./types.js"; import { emitParamBase, getClientNamespace, getImplementation, getRootNamespace } from "./utils.js"; function emitBasicMethod( context: PythonSdkContext, rootClient: SdkClientType, method: SdkBasicServiceMethod, operationGroup: SdkClientType, serviceApiVersions: string[], ): Record[] { if (method.operation.kind !== "http") throw new Error("We only support HTTP operations right now"); switch (method.operation.kind) { case "http": return emitBasicHttpMethod( context, rootClient, method, operationGroup.name, serviceApiVersions, ); default: throw new Error("We only support HTTP operations right now"); } } function emitLroMethod( context: PythonSdkContext, rootClient: SdkClientType, method: SdkLroServiceMethod, operationGroup: SdkClientType, serviceApiVersions: string[], ): Record[] { if (method.operation.kind !== "http") throw new Error("We only support HTTP operations right now"); switch (method.operation.kind) { case "http": return emitLroHttpMethod( context, rootClient, method, operationGroup.name, serviceApiVersions, ); default: throw new Error("We only support HTTP operations right now"); } } function emitPagingMethod( context: PythonSdkContext, rootClient: SdkClientType, method: SdkPagingServiceMethod, operationGroup: SdkClientType, serviceApiVersions: string[], ): Record[] { if (method.operation.kind !== "http") throw new Error("We only support HTTP operations right now"); switch (method.operation.kind) { case "http": return emitPagingHttpMethod( context, rootClient, method, operationGroup.name, serviceApiVersions, ); default: throw new Error("We only support HTTP operations right now"); } } function emitLroPagingMethod( context: PythonSdkContext, rootClient: SdkClientType, method: SdkLroPagingServiceMethod, operationGroup: SdkClientType, serviceApiVersions: string[], ): Record[] { if (method.operation.kind !== "http") throw new Error("We only support HTTP operations right now"); switch (method.operation.kind) { case "http": return emitLroPagingHttpMethod( context, rootClient, method, operationGroup.name, serviceApiVersions, ); default: throw new Error("We only support HTTP operations right now"); } } function emitMethodParameter( context: PythonSdkContext, parameter: SdkEndpointParameter | SdkCredentialParameter | SdkMethodParameter, serviceApiVersions: string[] = [], ): Record[] { if (parameter.kind === "endpoint") { if (parameter.type.kind === "union") { for (const endpointVal of parameter.type.variantTypes) { return emitEndpointType(context, endpointVal as SdkEndpointType, serviceApiVersions); } } else { return emitEndpointType(context, parameter.type, serviceApiVersions); } } let optional = parameter.optional; // filter out credential that python does not support for now if (parameter.kind === "credential") { const filteredCredentialType = []; const originalCredentialType = parameter.type.kind === "union" ? parameter.type.variantTypes : [parameter.type]; for (const credentialType of originalCredentialType) { if ( credentialType.scheme.type === "oauth2" || credentialType.scheme.type === "http" || (credentialType.scheme.type === "apiKey" && credentialType.scheme.in === "header") ) { filteredCredentialType.push(credentialType); } else if (credentialType.scheme.type === "noAuth") { optional = true; } } if (filteredCredentialType.length === 0) { return []; } else if (filteredCredentialType.length === 1) { parameter.type = filteredCredentialType[0]; } else { (parameter.type as SdkUnionType).variantTypes = filteredCredentialType; } } const base = { ...emitParamBase(context, parameter, undefined, serviceApiVersions), implementation: getImplementation(context, parameter), clientDefaultValue: parameter.clientDefaultValue, location: parameter.kind, optional, }; if (parameter.isApiVersionParam) { return [ { ...base, location: "query", wireName: "api-version", in_docstring: false, }, ]; } return [base]; } function emitMethod( context: PythonSdkContext, rootClient: SdkClientType, operationGroup: SdkClientType, method: SdkServiceMethod, serviceApiVersions: string[], ): Record[] { switch (method.kind) { case "basic": return emitBasicMethod(context, rootClient, method, operationGroup, serviceApiVersions); case "lro": return emitLroMethod(context, rootClient, method, operationGroup, serviceApiVersions); case "paging": return emitPagingMethod(context, rootClient, method, operationGroup, serviceApiVersions); default: return emitLroPagingMethod(context, rootClient, method, operationGroup, serviceApiVersions); } } // TCGC does not supply apiVersions information for each type that could facilitate versioning at the service level, // therefore, it is necessary to provide it incrementally so that the `getAddedOn` function can use. function emitOperationGroups( context: PythonSdkContext, client: SdkClientType, rootClient: SdkClientType, prefix: string, serviceApiVersions: string[], ): Record[] | undefined { const operationGroups: Record[] = []; for (const operationGroup of client.children ?? []) { const name = `${prefix}${operationGroup.name}`; const operationGroupWithPrefixedName = { ...operationGroup, name, } as SdkClientType; let operations: Record[] = []; const apiVersions = serviceApiVersions.length > 0 ? serviceApiVersions : operationGroup.apiVersions; for (const method of operationGroup.methods) { operations = operations.concat( emitMethod(context, rootClient, operationGroupWithPrefixedName, method, apiVersions), ); } operationGroups.push({ name: name, className: name, propertyName: operationGroup.name, operations: operations, operationGroups: emitOperationGroups(context, operationGroup, rootClient, name, apiVersions), clientNamespace: getClientNamespace(context, operationGroup.namespace), }); } // root client should deal with mixin operation group if (prefix === "") { const mixinGroup = { ...client, name: "" } as SdkClientType; let operations: Record[] = []; for (const method of client.methods) { operations = operations.concat( emitMethod(context, rootClient, mixinGroup, method, serviceApiVersions), ); } if (operations.length > 0) { operationGroups.push({ name: "", className: "", propertyName: "", operations: operations, clientNamespace: getClientNamespace(context, client.namespace), }); } } // operation has same clientNamespace as the operation group for (const og of operationGroups) { for (const op of og.operations) { op.clientNamespace = og.clientNamespace; } } return operationGroups.length > 0 ? operationGroups : undefined; } function emitClient( context: PythonSdkContext, client: SdkClientType, ): Record { if (client.clientInitialization) { context.__endpointPathParameters = []; } // get all init parameters including children clients const initParameters: (SdkEndpointParameter | SdkCredentialParameter | SdkMethodParameter)[] = []; const paramNames = new Set(); const serviceApiVersions = client.apiVersions; function collectParameters(client: SdkClientType) { if (client.clientInitialization?.parameters) { for (const param of client.clientInitialization.parameters) { if (param.isApiVersionParam && serviceApiVersions.length === 0) { // for multi-service client with different api-versions, skip adding api-version param here continue; } else if (!paramNames.has(param.name)) { initParameters.push(param); paramNames.add(param.name); } } } for (const child of client.children ?? []) { collectParameters(child); } } collectParameters(client); const parameters = initParameters .map((x) => emitMethodParameter(context, x, client.apiVersions)) .reduce((a, b) => [...a, ...b]) ?? []; const endpointParameter = initParameters.find((x) => x.kind === "endpoint") as SdkEndpointParameter | undefined; const operationGroups = emitOperationGroups(context, client, client, "", client.apiVersions); let url: string | undefined; if (endpointParameter?.type.kind === "union") { url = (endpointParameter.type.variantTypes[0] as SdkEndpointType).serverUrl; } else { url = endpointParameter?.type.serverUrl; } return { name: client.name.endsWith("Client") ? client.name : `${client.name}Client`, description: (client.summary ? client.summary : client.doc) ?? "", parameters, operationGroups, url, apiVersions: client.apiVersions, arm: context.arm, clientNamespace: getClientNamespace(context, client.namespace), }; } function onlyUsedByPolling(usage: UsageFlags): boolean { return ( ((usage & UsageFlags.LroInitial) > 0 || (usage & UsageFlags.LroFinalEnvelope) > 0 || (usage & UsageFlags.LroPolling) > 0) && (usage & UsageFlags.Input) === 0 && (usage & UsageFlags.Output) === 0 ); } export function emitCodeModel(sdkContext: PythonSdkContext) { // Get types const sdkPackage = sdkContext.sdkPackage; const codeModel: Record = { namespace: getRootNamespace(sdkContext), clients: [], }; if (sdkPackage.licenseInfo) { codeModel["licenseInfo"] = sdkPackage.licenseInfo; } for (const client of sdkPackage.clients) { codeModel["clients"].push(emitClient(sdkContext, client)); } // loop through models and enums since there may be some orphaned models needs to be generated for (const model of sdkPackage.models) { // filter out spread models if ( model.name === "" || ((model.usage & UsageFlags.Spread) > 0 && (model.usage & UsageFlags.Input) === 0 && (model.usage & UsageFlags.Output) === 0) ) { continue; } // filter out models only used for polling and or envelope result if (onlyUsedByPolling(model.usage)) { continue; } // filter out specific models not used in python, e.g., pageable models if (sdkContext.__disableGenerationMap.has(model)) { continue; } // filter out core models if (isAzureCoreModel(model)) { continue; } getType(sdkContext, model); } for (const sdkEnum of sdkPackage.enums) { // filter out api version enum since python do not generate it if (sdkEnum.usage === UsageFlags.ApiVersionEnum) { continue; } if (onlyUsedByPolling(sdkEnum.usage)) { continue; } // filter out core enums if (isAzureCoreModel(sdkEnum)) { continue; } getType(sdkContext, sdkEnum); } // clear usage when a model is only used by paging for (const type of sdkContext.__typesMap.values()) { if ( type["type"] === "model" && type["referredByOperationType"] === ReferredByOperationTypes.PagingOnly && (type["usage"] & UsageFlags.Input) === 0 ) { type["usage"] = UsageFlags.None; } } codeModel["types"] = [ ...sdkContext.__typesMap.values(), ...Object.values(KnownTypes), ...sdkContext.__simpleTypesMap.values(), ]; codeModel["crossLanguagePackageId"] = ignoreDiagnostics(getCrossLanguagePackageId(sdkContext)); codeModel["crossLanguageVersion"] = sdkContext.sdkPackage.crossLanguageVersion; if ((sdkContext.emitContext.options as any).flavor === "azure") { const metadata = { ...sdkPackage.metadata } as any; if (metadata.apiVersions) { metadata.apiVersions = Object.fromEntries(metadata.apiVersions); } codeModel["metadata"] = metadata; } return codeModel; }