Skip to main content
Version: Next 🚧

TypeSpec Type Representation in TCGC

This page documents what type definitions in TypeSpec would look like when returned by TCGC

Main TypeSpec Code Example​

import "@typespec/http";

using TypeSpec.Http;

@useAuth(ApiKeyAuth<ApiKeyLocation.header, "api-key">)
@service({
title: "Contoso Widget Manager",
})
@server(
"{endpoint}/widget",
"Contoso Widget APIs",
{
endpoint: string,
}
)
namespace Contoso.WidgetManager;

model Widget {
@visibility("read", "update")
@path
id: string;

weight: int32;
color: "red" | "blue";
}

@error
model Error {
code: int32;
message: string;
}

@route("/widgets")
@tag("Widgets")
interface Widgets {
@get list(): Widget[] | Error;
@get read(@path id: string): Widget | Error;
@post create(...Widget): Widget | Error;
@patch update(...Widget): Widget | Error;
@delete delete(@path id: string): void | Error;
@route("{id}/analyze") @post analyze(@path id: string): string | Error;
}

Overall Hierarchy of Types​

This is a rough sketch of the hierarchy. We'll go into more detail for each section, this is just to get an idea of the overall structure.

const sdkContext = {
sdkPackage: {
clients: [
{
kind: "client",
name: "WidgetClient",
initialization: {
properties: [
{
kind: "endpoint",
type: {
kind: "union",
values: [
{
kind: "endpoint",
serverUrl: "{endpoint}",
templateArguments: [
{
name: "endpoint",
kind: "path",
...
}
]
},
{
kind: "endpoint",
serverUrl: "{endpoint}/widget",
templateArguments: [
{
name: "endpoint",
kind: "path",
...
}
]
}
]
}
},
{
kind: "credential",
...
}
]
},
methods: [
{
kind: "clientaccessor",
response: {
name: "WidgetPartsClient",
kind: "client",
...
}
},
{
kind: "basic",
parameters: [
{
name: "id",
kind: "method",
type: {
kind: "string",
},
...
}
],
operation: {
kind: "http",
verb: "get",
parameters: [
{
name: "id",
kind: "path",
type: {
kind: "string",
},
correspondingMethodParams: [
{
name: "id",
kind: "method",
type: {
kind: "string",
},
...
}
]
}
],
bodyParam: undefined,
responses: {
200: {
kind: "http",
...
}
},
examples: [
{
kind: "http",
name: "Example 1",
description: "Example 1",
filePath: "example1.json",
rawExample: {...},
parameters: [...],
responses: {...}
}
]
},
response: {
kind: "method",
logicalPath: undefined,
...
},
exception: {
kind: "method",
...
},
...
}
],
...
}
],
models: [
{
kind: "model",
name: "Widget",
properties: [
{
kind: "path",
name: "id",
type: {
kind: "string",
},
...
},
{
kind: "property",
name: "color",
type: {
kind: "enum",
isFixed: true,
...
},
...
}
]
}
],
enums: [
{
name: "WidgetColor",
generatedName: true,
values: [
{
value: "red",
...
},
{
value: "blue",
...
}
]
}
],
...
},
...
}

SdkContext​

SdkContext is the context of your SDK emitter.

Interface​

export interface SdkContext<
TOptions extends object = Record<string, any>,
TServiceOperation extends SdkServiceOperation = SdkHttpOperation,
> {
// emit context from tsp that is used to create our TCGC Sdk Context
emitContext: EmitContext<TOptions>;
// representation of the entire package the language emitter should generate
sdkPackage: SdkPackage<TServiceOperation>;
program: Program;
emitterName: string;
}

Usage​

It is used to persist information across functions. You should never define global constants or variables in your emitter code, because this leads to issues with multiple emitter runs. Instead, all global information should be stored in your context.

You should define your own language-specific SdkContext that extends from the one defined in @azure-tools/typespec-client-generator-core. This is where you keep your global variables. You should utilize the createSdkContext from tcgc. You also shouldn't instantiate your own interface, instead you should call createSdkContext to create.

import { EmitContext } from "@typespec/compiler";
import { createSdkContext, SdkServiceOperation } from "@azure-tools/typespec-client-generator-core";
import { PythonEmitterOptions } from "./lib.js";

interface PythonSdkContext<TServiceOperation extends SdkServiceOperation>
extends SdkContext<PythonEmitterOptions, TServiceOperation> {
ClientMap: Map<SdkClientType, PythonSdkClientType>;
}

export async function $onEmit(context: EmitContext<PythonEmitterOptions>) {
const sdkContext: PythonSdkContext = {
...createSdkContext(context, "@azure-tools/typespec-python"),
ClientMap: new Map<sdkClientType, PythonSdkClientType>(),
};
}

SdkPackage​

SdkPackage represents the entire package that your language emitter should generate. It synthesizes all of the information language emitters will need to generate the entire package

Interface​

export interface SdkPackage<TServiceOperation extends SdkServiceOperation> {
name: string;
rootNamespace: string;
clients: SdkClientType<TServiceOperation>[];
models: SdkModelType[];
enums: SdkEnumType[];
diagnostics: readonly Diagnostic[];
}

Example​

const sdkPackage = {
name: "ContosoWidgetManager",
rootNamespace: "Contoso.WidgetManager",
clients: [
{
name: "ContosoWidgetManagerClient",
kind: "client",
...
}
],
models: [
{
name: "Widget",
kind: "model",
...
},
{
name: "Error",
kind: "model",
...
}
],
enums: [],
diagnostics: [],
}

Usage​

const sdkPackage = sdkContext.sdkPackage;

SdkClientType​

An SdkClientType represents a single client in your package.

All clients will have an initialization property. Whether that property's access is internal or public will determine whether your client can be publicly instantiated or not.

Interface​

export interface SdkClientType<TServiceOperation extends SdkServiceOperation> {
name: string;
kind: "client";
description?: string;
details?: string;
initialization: SdkInitializationType;
apiVersions: string[];
// fully qualified. Compare to sdkPackage.rootNamespace to figure out where to generate
nameSpace: string;
methods: SdkMethod<TServiceOperation>[];
}

Example​

const sdkClient = {
name: "ContosoWidgetManagerClient";
kind: "client";
initialization: {
name: "WidgetManagerOptions",
kind: "model",
access: "public",
...
};
apiVersions: [],
nameSpace: "Contoso.WidgetManager",
methods: [
{
name: "getWidgets",
kind: "clientaccessor",
response: {
name: "Widgets",
kind: "client",
initialization: {
kind: "model",
access: "internal",
},
apiVersions: [],
nameSpace: "Contoso.WidgetManager",
methods: [
{
name: "list",
kind: "method",
...
},
{
name: "read",
kind: "method",
...
}
]
}
}
],
}

Usage​

import { SdkClientType } from "@azure-tools/typespec-client-generator-core";
import { PythonSdkServiceOperation } from "./interfaces.js";
import { get }

const serializedClients: PythonSdkClientType[] = [];

function serializeClient<
TServiceOperation extends PythonSdkServiceOperation
>(
sdkContext: PythonSdkContext<TServiceOperation>,
client: SdkClientType<TServiceOperation>,
): PythonSdkClientType {
// Map the information from the SdkClientType to your language's interface for a client.
// Would recommend that eventually your language's client type interface extends from
// SdkClientType, and just adds your language-specific information on top.
const pythonClient = {
...client,
parameters: (client.initialization.properties.map(
(x) => getSdkModelPropertyType(
context, x
)
) : undefined
),
methods: client.methods.filter(
(x) => x.kind === "method"
).map(
(x) => serializeServiceMethod(sdkContext, x)
),
subclients: [],
}
sdkContext.ClientMap.set(client, pythonClient)
return pythonClient
}

for (const client of sdkPackage.clients) {
serializedClients.push(serializeClient(sdkContext, client));
}

SdkInitializationType​

Initialization model for a client. Whether it's access is public or internal tells you whether the client is publicly instantiable.

Interface​

export interface SdkInitializationType extends SdkModelType {
// properties takes care of all of the initialization info that you will need.
properties: (SdkEndpointParameter | SdkCredentialParameter | SdkMethodParameter)[];
}

Example​

const sdkInitializationType = {
name: "WidgetManagerOptions",
kind: "model",
properties: [
{
kind: "endpoint",
serverUrl: "{endpoint}/widget",
templateArguments: [
{
kind: "path",
name: "endpoint",
type: {
kind: "string",
},
},
],
},
{
kind: "credential",
type: {
kind: "credential",
scheme: {
kind: "apiKey",
in: "header",
name: "api-key",
},
},
onClient: true,
},
],
crossLanguageDefinitionId: "Contoso.WidgetManager.WidgetManagerOptions",
apiVersions: [],
usage: UsageFlags.Input,
access: "public",
isFormDataType: false,
isError: false,
};

Usage​

The usage of this property is more language-dependent.

Some emitters will create a model and have the client accept the model as options to initialize your client. Others will flatten out the parameters and accept them individually as parameter input.

SdkMethod​

An SdkMethod is any of the types of methods that can be a method on a client.

There are two main types of an SdkMethod:

  1. SdkClientAccessor returns a subclient of the client it's on
  2. SdkServiceMethod wraps a service call
export type SdkMethod<TServiceOperation extends PythonSdkServiceOperation> =
| SdkServiceMethod<TServiceOperation>
| SdkClientAccessor<TServiceOperation>;

They each extend from the shared SdkMethodBase

interface SdkMethodBase<TServiceOperation extends SdkServiceOperation> {
__raw?: Operation;
name: string;
access: AccessFlags;
parameters: SdkParameter[];
apiVersions: string[];
description?: string;
details?: string;
}

Below we go into each method type

SdkClientAccessor​

A clientaccessor method is simply a method on a client that returns another client. The returned client can be instantiable or un-instantiable. If the returned client is instantiable, most likely your clientaccessor will be part of the public api surface, so users can instantiate the subclient using your client method. If it is not instantiable, it is up to you how to expose the subclient on your current client for users to access.

Interface​

interface SdkClientAccessor<TServiceOperation extends SdkServiceOperation>
extends SdkMethodBase<TServiceOperation> {
kind: "clientaccessor";
response: SdkClientType<TServiceOperation>;
}

Example​

const sdkClientAccessor = {
name: "getWidgets",
kind: "clientaccessor",
response: {
name: "Widgets",
kind: "client",
initialization: undefined,
...
}
}

Usage​

Our strong recommendation is you build up a list of clients by iterating through sdkPackage.clients first, and to not recurse into creating the subclients. Only once you've processed each client, then you go through and link clients to their subclients. Not doing recursion prevents confusion over where you are in the call chain.

import { SdkServiceOperation, SdkClientType } from "@azure-tools/typespec-client-generator-core";
import { PythonSdkContext } from "./lib.js";
import { PythonSdkClientType } from "./interfaces.js";

function linkSubClients<TServiceOperation extends PythonSdkServiceOperation>(
sdkContext: PythonSdkContext<TServiceOperation>,
): void {
for (const client of sdkContext.clients) {
client.subclients = client.methods
.filter((x) => x.kind === "clientaccessor")
.map((x) => x.returnType)
.map((x) => sdkContext.ClientMap.get(x));
}
}

SdkServiceMethod​

An SdkServiceMethod is any service method on a client that calls the service.

The actual service call is a separate property on the method, .operation. This way, our service methods are able to abstract away the protocol used to call the service (i.e. http or gRPC)

All SdkServiceMethods share the following base:

interface SdkServiceMethodBase<TServiceOperation extends SdkServiceOperation>
extends SdkMethodBase<TServiceOperation> {
// This represents the operation request we need to send to the service.
// This helps abstract away the transport
operation: TServiceOperation;
// The parameters you generate on the method signature that users will interact with.
// These aren't tied to any protocol, so there are no 'header' / 'path' parameters etc.
parameters: SdkMethodParameter[];
response: SdkMethodResponse;
exception?: SdkMethodResponse;
}
function serializeServiceMethod<TServiceOperation extends PythonSdkServiceOperation>(
context: PythonSdkContext<TServiceOperation>,
method: SdkServiceMethod<TServiceOperation>,
): PythonSdkServiceMethod {
switch (method.kind) {
case "basic":
return serializeBasicServiceMethod(context, method);
case "paging":
return serializePagingServiceMethod(context, method);
case "lro":
return serializeLroServiceMethod(context, method);
case "lropaging":
return serializeLroPagingServiceMethod(context, method);
}
}

SdkBasicServiceMethod​

This models a basic service call that is synchronous server side.

Interface​
export interface SdkBasicServiceMethod<TServiceOperation extends SdkServiceOperation>
extends SdkServiceMethodBase<TServiceOperation> {
kind: "basic";
}
Example​
const sdkBasicServiceMethod = {
kind: "basic",
__raw?: {
// Original Tsp Operation type
kind: "Operation",
},
name: "read",
access: "public",
apiVersions: [],
parameters: [
{
name: "id",
kind: "method",
...
},
],
operation: {
kind: "http",
...
},
response: {
kind: "method",
type: {
kind: "model",
name: "Widget",
}
},
}
Usage​
function serializeBasicServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkBasicServiceMethod<TServiceOperation>,
): PythonSdkBasicServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method, method.operation)
},
}

SdkPagingServiceMethod​

This represents a paging method we will generate on the client. It includes an initial service operation call, and potentially a next link operation as well.

We currently only have paging method support for azure-generated sdks.

Interface​
export interface SdkPagingServiceMethod<TServiceOperation extends SdkServiceOperation>
extends SdkServiceMethodBase<TServiceOperation> {
kind: "paging";
// raw paging information returned from getPagedResult
__raw_paged_metadata: PagedResultMetadata;
// string to get to next link. If undefined, we are doing fake paging
nextLinkPath?: string;
// service operation if separate requests need to be made for subsequent paging
nextLinkOperation?: TServiceOperation;
}
Example​
import "@azure-tools/typespec-azure-core";

using Azure.Core;
using Azure.Core.Traits;

// Skipping past service definitions

alias Operations = Azure.Core.ResourceOperations<SupportsRepeatableRequests>;

listWidgets is Operations.ResourceList<Widget>;
const sdkPagingServiceMethod = {
kind: "paging",
__raw?: {
// Original Tsp Operation type
kind: "Operation",
},
__raw_paged_metadata: {
// Raw output from getPagedMetadata
}
name: "listWidgets",
access: "public",
apiVersions: [],
parameters: [],
operation: {
kind: "http",
...
},
response: {
kind: "method",
type: {
kind: "model",
name: "Widget",
},
// we want to return the `.value` part of the service response to users
pathFromService: "value",
},
nextLinkPath: "nextLink",
nextLinkOperation: undefined,
}
Usage​
function serializePagingServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkPagingServiceMethod<TServiceOperation>,
): PythonSdkBasicServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method, method.operation),
nextLinkOperation: method.nextLinkOperation ? serializeServiceOperation(context, method, method.nextLinkOperation) : undefined,
},
}

SdkLroServiceMethod​

Represents an LRO method we want to generate on the client.

Only returned for azure-generated sdks

Interfaces​
export interface SdkLroServiceMethod<TServiceOperation extends SdkServiceOperation>
extends SdkServiceMethodBase<TServiceOperation> {
kind: "lro";
// raw LroMetadata returned from azure-core helper function getLroMetadata
__raw_lro_metadata: LroMetadata;
// initial call to begin LRO polling
// thinking of instead making it the regular operation property so we can continue using that property
operation: TServiceOperation;
}

Example​

import "@azure-tools/typespec-azure-core";

using Azure.Core;
using Azure.Core.Traits;

// Skipping past service definitions

alias Operations = Azure.Core.ResourceOperations<SupportsRepeatableRequests>;

// Operation Status
/** Gets status of a Widget operation. */
getWidgetOperationStatus is Operations.GetResourceOperationStatus<Widget>;

// Widget Operations
/** Creates or updates a Widget asynchronously */
@pollingOperation(getWidgetOperationStatus)
createOrUpdateWidget is Operations.LongRunningResourceCreateOrUpdate<Widget>;
const sdkLroServiceMethod = {
kind: "lro",
__raw?: {
// Original Tsp Operation type
kind: "Operation",
},
__raw_lro_metadata: {
// Raw output from getLroMetadata
}
name: "createOrUpdateWidget",
access: "public",
apiVersions: [],
parameters: [],
operation: {
kind: "http",
...
},
response: {
kind: "method",
type: {
kind: "model",
name: "Widget",
}
},
}
Usage​
function serializeLroServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkLroServiceMethod<TServiceOperation>,
): PythonSdkLroServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method),
},
}

SdkLroPagingServiceMethod​

This is a combination of Lro and paging. We start off with an LRO call to the service, and then the response is returned to us in pages. Also only available for azure-sdks.

Interface​
export interface SdkLroPagingServiceMethod<TServiceOperation extends SdkServiceOperation>
extends SdkServiceMethodBase<TServiceOperation> {
kind: "lropaging";
__raw_lro_metadata: LroMetadata;
// initial call to start LRO
operation: SdkServiceOperation;
__raw_paged_metadata: PagedResultMetadata;
nextLinkPath?: string; // off means fake paging
nextLinkOperation?: SdkServiceOperation;
}
Usage​
function serializeLroPagingServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkLroPagingServiceMethod<TServiceOperation>,
): PythonSdkLroPagingServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method, method.operation),
nextLinkOperation: method.nextLinkOperation ? serializeServiceOperation(context, method, method.nextLinkOperation) : undefined,
},
}

SdkServiceOperation​

One main part of this design is we've decoupled the actual service operation call from the method we generate on our client. This is because we want to abstract away the protocol used to call our service. Additionally, while there is a high level of correlation between the method parameters we intake from SDK users, and the parameters we end up passing the service, it's not one-to-one. We need to serialize the method parameters and additionally, when parameters are spread or grouped together, it requires us to do mapping between which method parameters correspond to which service parameters.

We currently only support HTTP calls to the service.

Interface​

export interface SdkHttpOperation {
kind: "http";
// raw HTTP operation output from typespec/http
__raw: HttpOperation;
// route path for the target operation. TypeSpec suggest to use `uriTemplate` instead as `path` will not work for complex cases like not-escaping reserved chars.
path: string;
// the fully resolved URI template for the target operation as defined by [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570).
uriTemplate: string;
verb: HttpVerb;
parameters: (SdkPathParameter | SdkQueryParameter | SdkHeaderParameter)[];
bodyParam: SdkBodyParameter;
// mapping of status codes to SdkHttpResponse for valid responses
// HttpStatusCodeRange can represent either a single status code or a range.
responses: SdkHttpResponse[];
exceptions: SdkHttpResponse[];
examples?: SdkHttpOperationExample[];
}

Example​

const sdkHttpOperation = {
kind: "http",
__raw: {
// raw HttpOperation from @typespec/http;
},
path: "/widgets",
uriTemplate: "/widgets",
verb: "get",
parameters: [
{
kind: "path",
name: "id",
...
}
],
bodyParam: undefined,
responses: {
200: {
kind: "http",
...
},
},
exceptions: {
"*": {
kind: "http",
...
}
},
examples: [
{
kind: "http",
name: "Example 1",
description: "Example 1",
filePath: "example1.json",
rawExample: {...},
parameters: [...],
responses: {...}
}
]
}

Usage​

function serializeServiceOperation<TServiceOperation extends PythonSdkServiceOperation>(
context: PythonSdkContext<TServiceOperation>,
method: SdkServiceMethod<TServiceOperation>,
operation: TServiceOperation,
): TServiceOperation {
switch (operation.kind) {
case "http":
return serializeHttpServiceOperation(context, method, operation);
}
}

Parameters and Properties​

Like the TypeSpec concept of a ModelProperty, all properties and parameters are part of the SdkModelPropertyType. They all extend from SdkModelPropertyTypeBase.

export type SdkModelPropertyType =
| SdkMethodParameter
| SdkEndpointParameter
| SdkCredentialParameter
| SdkBodyModelPropertyType
| SdkQueryParameter
| SdkPathParameter
| SdkHeaderParameter
| SdkBodyParameter;
interface SdkModelPropertyTypeBase {
__raw?: ModelProperty;
type: SdkType;
name: string;
description?: string;
details?: string;
apiVersions: string[];
onClient: boolean;
// clientDefaultValue only exists for api versions
clientDefaultValue?: any;
isApiVersionParam: boolean;
optional: boolean;
nullable: boolean;
}

Method parameters​

These are parameters to client initialization and method son the client. These will be the parameters that SDK users directly use. They will eventually be mapped to

SdkEndpointParameter​

An SdkEndpointParameter represents a parameter to a client's endpoint.

TCGC will always give it to you as overridable:

If the server URL is a constant, we will return a templated endpoint with a default value of the constant server URL. In the case where the endpoint has extra template arguments, the type is a union of a completely-overridable endpoint, and an endpoint that accepts template arguments. If there are multiple servers, we will return the union of all of the possibilities.

export interface SdkEndpointParameter extends SdkModelPropertyTypeBase {
kind: "endpoint";
urlEncode: boolean;
onClient: true;
serializedName?: string;
type: SdkEndpointType | SdkUnionType<SdkEndpointType>;
}

SdkCredentialParameter​

Parameter for credential input to clients.

export interface SdkCredentialParameter extends SdkModelPropertyTypeBase {
kind: "credential";
// can be either an SdkCredentialType or a union
// of different credential types
type: SdkCredentialType | SdkUnionType;
onClient: true;
}

SdkMethodParameter​

Represents a parameter to a client method. Does not have any transport (i.e. HTTP) related information on it. This is solely meant to represent information that we expect sdk users to pass in. We then take care to map the method input and create our request to the service using the information users have inputted.

export interface SdkMethodParameter extends SdkModelPropertyTypeBase {
kind: "method";
}

Service Parameters​

Currently we only support HTTP service parameters.

SdkQueryParameter​

This represents an HTTP query parameter.

// different collection format to use to separate each value in array
// - multi: no separator, treat each value in array as a separate query parameter
// - csv: , as separator
// - ssv: space as separator
// - tsv: tab as separator
// - pipes: | as separator
// - simple: , as separator
// - form: no separator, treat each value in array as a separate query parameter
export type CollectionFormat = "multi" | "csv" | "ssv" | "tsv" | "pipes" | "simple" | "form";

export interface SdkQueryParameter extends SdkModelPropertyTypeBase {
kind: "query";
// the collection format to use to separate each value in array of a query parameter
collectionFormat?: CollectionFormat;
serializedName: string;
correspondingMethodParams: SdkModelPropertyType[];
// if true, send each value in array or object as a separate query parameter, default is `false`
explode: boolean;
}

SdkHeaderParameter​

This is an HTTP header parameter.

// different collection format to use to separate each value in array
// - multi: no separator, treat each value in array as a separate header parameter
// - csv: , as separator
// - ssv: space as separator
// - tsv: tab as separator
// - pipes: | as separator
// - simple: , as separator
// - form: no separator, treat each value in array as a separate header parameter
export type CollectionFormat = "multi" | "csv" | "ssv" | "tsv" | "pipes" | "simple" | "form";

export interface SdkHeaderParameter extends SdkModelPropertyTypeBase {
kind: "header";
collectionFormat?: CollectionFormat;
serializedName: string;
correspondingMethodParams: SdkModelPropertyType[];
}

SdkPathParameter​

export interface SdkPathParameter extends SdkModelPropertyTypeBase {
kind: "path";
// when interpolating this parameter in the case of array or object, expand each value using the given style, default is `false`
explode: boolean;
// different interpolating styles for the path parameter, default is `"simple"`
// - simple: no separator
// - label: . as separator
// - matrix: ; as separator
// - fragment: # as separator
// - path: / as separator
style: "simple" | "label" | "matrix" | "fragment" | "path";
// when interpolating this parameter, do not encode reserved characters, default is `false`
allowReserved: boolean;
serializedName: string;
optional: false;
correspondingMethodParams: SdkModelPropertyType[];
}

SdkBodyParameter​

export interface SdkBodyParameter extends SdkModelPropertyTypeBase {
kind: "body";
optional: boolean;
contentTypes: string[];
defaultContentType: string;
correspondingMethodParams: SdkModelPropertyType[];
}

Properties​

SdkBodyModelPropertyType​

This represents a property on a body model

export interface SdkBodyModelPropertyType extends SdkModelPropertyTypeBase {
kind: "property";
discriminator: boolean;
serializedName: string;
isMultipartFileInput: boolean;
visibility: Visibility[];
flatten: boolean;
}

Usage​

We recommend that for usage, you use one single function with switch statements for each kind of property. This allows for the circular nature of an SdkModelPropertyType.

import { SdkServiceOperation, SdkModelPropertyType } from "@azure-tools/typespec-client-generator-core";
import { PythonSdkContext } from "./lib.js";
import { getPythonSdkType } from "./types.js";

export function getSdkModelPropertyType<
TServiceOperation extends SdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
prop: SdkModelPropertyType
): PythonSdkModelType {
const type = getPythonSdkType(context, prop.type)
switch (prop.kind) {
case "query":
case "header"
case "path":
return {
...prop,
type,
correspondingMethodParams: prop.correspondingMethodParams.map(
(x) => getSdkModelPropertyType(context, x),
)
}
default:
return {
...prop,
type,
}
}
}

SdkMethodResponse​

This represents the response that our method will ultimately return. It is not tied to a transport, like http, it solely represents what we want to return to users

Interface​

export interface SdkMethodResponse {
kind: "method";
// the path from the service response to what the method returns.
logicalPath?: string;
type?: SdkType;
description?: string;
details?: string;
}

Example​

const sdkMethodResponse = {
kind: "method",
logicalPath: undefined,
type: {
kind: string,
},
}

const sdkPagingMethodResponse = {
kind: "method",
logicalPath: "value",
type: {
kind: "array",
valueType: {
kind: "model",
name: "Widget",
nullableValues: false,
...
}
}
}

Usage​

import { SdkMethodResponse } from "@azure-tools/typespec-client-generator-core";
import { PythonSdkContext, PythonSdkMethodResponse } from "./lib.js";
import { getPythonSdkType } from "./types.js";

function serializeMethodResponse<TServiceOperation extends SdkServiceOperation>(
context: PythonSdkContext<TServiceOperation>,
response: SdkMethodResponse,
): PythonSdkMethodResponse {
return {
...response,
type: getPythonSdkType(context, response.type),
};
}

SdkHttpResponse​

This is the response returned from an HTTP service. The emitters should take care to map it to what the method ultimately returns to end users.

Each response is mapped to an HttpStatusCodeRange on the SdkHttpOperation interface.

Interface​

interface HttpStatusCodeRange {
start: number; // inclusive start. If there's only start, then it's just a single value.
end?: number; // exclusive end. If it's a range, end is present, otherwise it's undefined.
}

export interface SdkHttpResponse {
kind: "http";
statusCodes: number | HttpStatusCodeRange | "*";
headers: SdkServiceResponseHeader[];
apiVersions: string[];
type?: SdkType;
contentTypes: string[];
defaultContentType?: string;
}

SdkType​

These are the TCGC versions of each TypeSpec Type. They include information that all emitters want, i.e. their description directly on them.

They all share this base:

interface SdkTypeBase {
// the raw TypeSpec type. Some of our types are currently
// created by tcgc. Those won't have an original type
__raw?: Type;
kind: string;
deprecation?: string;
description?: string;
details?: string;
}

SdkBuiltInType​

A SdkBuiltInType represents a built-in scalar TypeSpec type or scalar type that derives from a built-in scalar TypeSpec type, but datetime and duration are not included. We add encode onto these types if @encode decorator exists, telling us how to encode when sending to the service.

There is a one-to-one mapping between the TypeSpec scalar kinds and the SdkBuiltInKinds.

Interface​

export interface SdkBuiltInType extends SdkTypeBase {
kind: SdkBuiltInKinds;
name: string;
encode?: string;
baseType?: SdkBuiltInType;
crossLanguageDefinitionId: string;
}

The crossLanguageDefinitionId represents the fully qualified name of this type in TypeSpec language for the emitter to distinguish from the built-in TypeSpec types.

For a full list of types defined in @azure-tools/typespec-azure-core library, please refer to its reference doc.

SdkDateTimeType​

interface SdkDatetimeTypeBase extends SdkTypeBase {
name: string;
baseType?: SdkDateTimeType;
encode: DateTimeKnownEncoding;
// what we send over the wire. Often it's string
wireType: SdkBuiltInType;
crossLanguageDefinitionId: string;
}

interface SdkUtcDatetimeType extends SdkDatetimeTypeBase {
kind: "utcDateTime";
}

interface SdkOffsetDatetimeType extends SdkDatetimeTypeBase {
kind: "offsetDateTime";
}

SdkDurationType​

interface SdkDurationType extends SdkTypeBase {
kind: "duration";
name: string;
baseType?: SdkDurationType;
encode: DurationKnownEncoding;
// What we send over the wire. It's usually either a string or a float
wireType: SdkBuiltInType;
crossLanguageDefinitionId: string;
}

SdkArrayType​

interface SdkArrayType extends SdkTypeBase {
kind: "array";
name: string;
valueType: SdkType;
crossLanguageDefinitionId: string;
}

SdkDictionaryType​

interface SdkDictionaryType extends SdkTypeBase {
kind: "dict";
keyType: SdkType; // currently can only be string
valueType: SdkType;
}

SdkEnumType​

export interface SdkEnumType extends SdkTypeBase {
kind: "enum";
name: string;
// Determines whether the name was generated or not
isGeneratedName: boolean;
valueType: SdkBuiltInType;
values: SdkEnumValueType[];
isFixed: boolean;
isFlags: boolean;
usage: UsageFlags;
access: AccessFlags;
crossLanguageDefinitionId: string;
apiVersions: string[];
isUnionAsEnum: boolean;
}

SdkEnumValueType​

export interface SdkEnumValueType extends SdkTypeBase {
kind: "enumvalue";
name: string;
value: string | number;
enumType: SdkEnumType;
valueType: SdkBuiltInType;
}

SdkConstantType​

export interface SdkConstantType extends SdkTypeBase {
kind: "constant";
value: string | number | boolean | null;
valueType: SdkBuiltInType;
name: string;
isGeneratedName: boolean;
}

SdkUnionType​

export interface SdkUnionType extends SdkTypeBase {
name: string;
// determines if the union name was generated or not
isGeneratedName: boolean;
kind: "union";
variantTypes: SdkType[];
crossLanguageDefinitionId: string;
}

SdkTupleType​

export interface SdkTupleType extends SdkTypeBase {
kind: "tuple";
valueTypes: SdkType[];
}

SdkModelType​

export interface SdkModelType extends SdkTypeBase {
kind: "model";
// purposely can also be header / query params for fidelity with TypeSpec
properties: SdkModelPropertyType[];
// we will always have a name. generatedName determines if it's generated or not.
name: string;
isGeneratedName: boolean;
access: AccessFlags;
usage: UsageFlags;
additionalProperties?: SdkType;
discriminatorValue?: string;
discriminatedSubtypes?: Record<string, SdkModelType>;
discriminatorProperty?: SdkModelPropertyType;
baseModel?: SdkModelType;
crossLanguageDefinitionId: string;
apiVersions: string[];
}

UsageFlags​

export enum UsageFlags {
None = 0,
Input = 1 << 1,
Output = 1 << 2,
ApiVersionEnum = 1 << 3,
JsonMergePatch = 1 << 4,
MultipartFormData = 1 << 5,
}

AccessFlags​

We default the value of .access property on model, enum, and method types to be "public". So if the @access decorator isn't explicitly applied to one of these definitions, its value will be "public".

If you want to know if a tsp author explicitly set the value with an @access decorator, you can call getAccessOverride

export type AccessFlags = "internal" | "public";

SdkEndpointType​

export interface SdkEndpointType {
kind: "endpoint";
serverUrl: string;
templateArguments: SdkPathParameter[];
}

Example Types​

The example types help to model the examples that TypeSpec author defined to help user understand how to use the API.

We currently only have examples based on the payload, so the examples model will bind to the SdkServiceOperation.

These types are used to represent an example of a service operation.

We currently only support HTTP calls to the service.

So, we have SdkHttpoperationExample bind to SdkHttpOperation, SdkHttpParameterExampleValue bind to SdkHttpParameter, SdkHttpResponseExampleValue bind to SdkHttpResponse, and SdkHttpResponseHeaderExampleValue bind to SdkHttpResponseHeader.

Each type will have the example value type and its cooresponding definition type.

interface SdkExampleBase {
kind: string;
name: string;
description: string;
filePath: string;
rawExample: any;
}

export interface SdkHttpOperationExample extends SdkExampleBase {
kind: "http";
parameters: SdkHttpParameterExampleValue[];
responses: SdkHttpResponseExampleValue[];
}

export interface SdkHttpParameterExampleValue {
parameter: SdkHttpParameter;
value: SdkExampleValue;
}

export interface SdkHttpResponseExampleValue {
response: SdkHttpResponse;
statusCode: number;
headers: SdkHttpResponseHeaderExampleValue[];
bodyValue?: SdkExampleValue;
}

export interface SdkHttpResponseHeaderExampleValue {
header: SdkServiceResponseHeader;
value: SdkExampleValue;
}

SdkExampleValue​

These types are used to represent the example value of a type. One definition types will have different example value types. For SdkUnionExampleValue, since it is hard to determine whether the example value should belong to which union variant, we will keep the raw value and leave the work for the emitter. For SdkModelExampleValue, we will help to map the example type to the right subtype for the discriminated type, and we will separate the additional properties value from the property value. But for the model with inheritance, we will not break down the type graph, just put all the example value in the child model.

export type SdkExampleValue =
| SdkStringExampleValue
| SdkNumberExampleValue
| SdkBooleanExampleValue
| SdkNullExampleValue
| SdkUnknownExampleValue
| SdkArrayExampleValue
| SdkDictionaryExampleValue
| SdkUnionExampleValue
| SdkModelExampleValue;

interface SdkExampleValueBase {
kind: string;
type: SdkType;
value: unknown;
}

export interface SdkStringExampleValue extends SdkExampleTypeBase {
kind: "string";
type:
| SdkBuiltInType
| SdkDateTimeType
| SdkDurationType
| SdkEnumType
| SdkEnumValueType
| SdkConstantType;
value: string;
}

export interface SdkNumberExampleValue extends SdkExampleTypeBase {
kind: "number";
type:
| SdkBuiltInType
| SdkDateTimeType
| SdkDurationType
| SdkEnumType
| SdkEnumValueType
| SdkConstantType;
value: number;
}

export interface SdkBooleanExampleValue extends SdkExampleTypeBase {
kind: "boolean";
type: SdkBuiltInType | SdkConstantType;
value: boolean;
}

export interface SdkNullExampleValue extends SdkExampleTypeBase {
kind: "null";
type: SdkNullableType;
value: null;
}

export interface SdkUnknownExampleValue extends SdkExampleTypeBase {
kind: "unknown";
type: SdkBuiltInType;
value: unknown;
}

export interface SdkArrayExampleValue extends SdkExampleTypeBase {
kind: "array";
type: SdkArrayType;
value: SdkExampleValue[];
}

export interface SdkDictionaryExampleValue extends SdkExampleTypeBase {
kind: "dict";
type: SdkDictionaryType;
value: Record<string, SdkExampleValue>;
}

export interface SdkUnionExampleValue extends SdkExampleTypeBase {
kind: "union";
type: SdkUnionType;
value: unknown;
}

export interface SdkModelExampleValue extends SdkExampleTypeBase {
kind: "model";
type: SdkModelType;
value: Record<string, SdkExampleValue>;
additionalPropertiesValue?: Record<string, SdkExampleValue>;
}

Example​

const sdkHttpOperationExample = {
kind: "http",
name: "Example 1",
description: "Example 1",
filePath: "example1.json",
rawExample: {
operationId: "Widgets_Read",
title: "Example 1",
parameters: {
id: "test",
},
responses: {
200: {
body: {
id: "test",
color: "red",
weight: 100,
},
},
},
},
parameters: [
{
parameter: {}, // ref to the SdkHttpParameter
value: {
kind: "string",
type: {}, // ref to the SdkType
value: "test",
},
},
],
responses: {
200: {
response: {}, // ref to the SdkHttpResponse
bodyValue: {
kind: "model",
type: {}, // ref to the SdkType
value: {
id: {
kind: "string",
type: {}, // ref to the SdkType
value: "test",
},
color: {
kind: "string",
type: {}, // ref to the SdkType
value: "red",
},
weight: {
kind: "number",
type: {}, // ref to the SdkType
value: 100,
},
},
},
},
},
};

Usage​

function serializeServiceOperationExample(
context: PythonSdkContext<SdkHttpOperation>,
operation: SdkHttpOperation,
): PythonSdkHttpOperationExample {
for (const example of operation.examples) {
return {
name: example.name,
filePath: example.filePath,
parameters: example.parameters.map((x) => ({
parameter: getSdkModelPropertyType(x.parameter),
value: serializeTypeExample(context, x.value),
})),
responses: new Map(
[...example.responses].map(([key, value]) => [
key,
{
response: serializeHttpResponse(value.response),
headers: value.headers.map((x) => ({
header: getSdkModelPropertyType(x.header),
value: serializeTypeExample(context, x.value),
})),
bodyValue: serializeTypeExample(context, value.bodyValue),
},
]),
),
};
}
}

function serializeTypeExample(
context: PythonSdkContext<SdkHttpOperation>,
example: SdkExampleValue,
): PythonSdkTypeExample {
switch (example.kind) {
case "string":
return {
...example,
type: getPythonSdkType(context, example.type),
};
case "number":
return {
...example,
type: getPythonSdkType(context, example.type),
};
case "boolean":
return {
...example,
type: getPythonSdkType(context, example.type),
};
case "null":
return {
...example,
type: getPythonSdkType(context, example.type),
};
case "unknown":
return {
...example,
type: getPythonSdkType(context, example.type),
};
case "array":
return {
...example,
type: getPythonSdkType(context, example.type),
value: example.value.map((x) => serializeTypeExample(context, x)),
};
case "dict":
return {
...example,
type: getPythonSdkType(context, example.type),
value: Object.fromEntries(
Object.entries(example.value).map(([key, value]) => [
key,
serializeTypeExample(context, value),
]),
),
};
case "union":
return {
...example,
type: getPythonSdkType(context, example.type),
};
case "model":
return {
...example,
type: getPythonSdkType(context, example.type),
value: Object.fromEntries(
Object.entries(example.value).map(([key, value]) => [
key,
serializeTypeExample(context, value),
]),
),
additionalPropertiesValue: example.additionalPropertiesValue
? Object.fromEntries(
Object.entries(example.additionalPropertiesValue).map(([key, value]) => [
key,
serializeTypeExample(context, value),
]),
)
: undefined,
};
}
}

Complete Usage Code​

lib.ts​

import { SdkContext } from "@azure-tools/typespec-client-generator-core";
import { PythonSdkType, PythonSdkServiceOperation } from "./interfaces.js";

export interface PythonEmitterOptions {
"flavor"?: "azure";
...
}

export interface PythonSdkContext<
TServiceOperation extends PythonSdkServiceOperation
> extends SdkContext<PythonEmitterOptions, TServiceOperation> {
__endpointPathParameters: Record<str, Any>;
pythonTypesMap: Record<str, PythonSdkType>;
}

emitter.ts​

import { EmitContext } from "@typespec/compiler";
import {
SdkContext,
createSdkContext,
SdkClientType,
} from "@azure-tools/typespec-client-generator-core";
import { PythonSdkContext } from "./lib.js";
import { getPythonSdkType, getSdkModelPropertyType } from "./types.js";
import {
PythonSdkType,
PythonSdkPackage,
PythonSdkClientType,
PythonSdkServiceMethod,
PythonSdkServiceOperation,
} from "./interfaces.js";
import { serializeServiceMethod } from "./methods.js";

export async function $onEmit(context: EmitContext<PythonEmitterOptions>) {
const sdkContext: PythonSdkContext = {
...createSdkContext(context, "@azure-tools/typespec-python"),
__endpointPathParameters: {},
};

const pythonSdkPackage: PythonSdkPackage = {
clients: sdkContext.clients.map((x) => serializeClient(sdkContext, x)),
rootNamespace: sdkContext.sdkPackage.rootNamespace,
models: sdkContext.sdkPackage.models.map((x) => getPythonSdkType(sdkContext, x)),
enums: sdkContext.sdkPackage.enums.map((x) => getPythonSdkType(sdkContext, x)),
};

linkSubClients(sdkContext);

// Serialize the pythonSdkPackage and pass it to your generator
}

function serializeClient<TServiceOperation extends PythonSdkServiceOperation>(
sdkContext: PythonSdkContext<TServiceOperation>,
client: SdkClientType<TServiceOperation>,
): PythonSdkClientType {
const pythonClient = {
...client,
parameters: client.initialization?.properties.map((x) =>
getSdkModelPropertyType(sdkContext, x),
),
subClients: client.methods
.filter((x) => x.kind === "clientaccessor")
.map((x) => serializeClient(sdkContext, x.response)),
methods: client.methods
.filter((x) => x.kind === "method")
.map((x) => serializeServiceMethod(sdkContext, x)),
subclients: [],
};
sdkContext, ClientMap.set(client, pythonClient);
return pythonClient;
}

function linkSubClients<TServiceOperation extends PythonSdkServiceOperation>(
sdkContext: PythonSdkContext<TServiceOperation>,
): void {
for (const client of sdkContext.clients) {
client.subclients = client.methods
.filter((x) => x.kind === "clientaccessor")
.map((x) => x.returnType)
.map((x) => sdkContext.ClientMap.get(x));
}
}

methods.ts​

import {
SdkClientType,
SdkServiceMethod,
SdkBasicServiceMethod,
SdkPagingServiceMethod,
SdkLroServiceMethod,
SdkLroPagingServiceMethod,
} from "@azure-tools/typespec-client-generator-core";
import {
PythonSdkBasicServiceMethod,
PythonSdkPagingServiceMethod,
PythonSdkLroServiceMethod,
PythonSdkLroPagingServiceMethod,
PythonSdkServiceOperation,
} from "./interfaces.js";
import { PythonSdkContext } from "./lib.js";
import { serializeHttpServiceOperation } from "./http.js";

export function serializeServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
sdkContext: PythonSdkContext<TServiceOperation>,
method: SdkServiceMethod<TServiceOperation>
): PythonSdkServiceMethod<TServiceOperation> {
switch (method.kind) {
case "basic":
return serializeBasicServiceMethod(context, method);
case "paging":
return serializePagingServiceMethod(context, method);
case "lro":
return serializeLroServiceMethod(context, method);
case "lropaging":
return serializeLroPagingServiceMethod(context, method);
}
}

function serializeBasicServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkBasicServiceMethod<TServiceOperation>,
): PythonSdkBasicServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method, method.operation)
},
}

function serializePagingServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkPagingServiceMethod<TServiceOperation>,
): PythonSdkBasicServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method, method.operation),
nextLinkOperation: method.nextLinkOperation ? serializeServiceOperation(context, method, method.nextLinkOperation) : undefined,
},
}

function serializeLroServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkLroServiceMethod<TServiceOperation>,
): PythonSdkLroServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method, method.operation),
},
}

function serializeLroPagingServiceMethod<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkLroPagingServiceMethod<TServiceOperation>,
): PythonSdkLroPagingServiceMethod<TServiceOperation> {
return {
...method,
name: camelToSnakeCase(method.name),
operation: serializeServiceOperation(context, method, method.operation),
nextLinkOperation: method.nextLinkOperation ? serializeServiceOperation(context, method, method.nextLinkOperation) : undefined,
},
}

function serializeServiceOperation<
TServiceOperation extends PythonSdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
method: SdkServiceMethod<TServiceOperation>,
operation: TServiceOperation
): TServiceOperation {
switch (operation.kind) {
case "http": {
return serializeHttpServiceOperation(
context, operation
)
}
}
}

function serializeHttpServiceOperation(
context: PythonSdkContext<SdkHttpOperation>,
operation: SdkHttpOperation,
): PythonSdkHttpOperation {
return {
...operation,
parameters: operation.parameters.map(
(x) => getSdkModelPropertyType(context, x)
),
bodyParam: (
operation.bodyParam ?
getSdkModelPropertyType(context, operation.bodyParam) : undefined
),
responses: Object.entries(operation.responses).map(
([statusCode, responses]) => serializeHttpResponse(context, method, statusCode, responses)
),
exceptions: Object.entries(operation.exceptions).map(
([statusCode, exceptions]) => serializeHttpResponse(context, method, statusCode, exceptions)
),
}
}

http.js​

import { SdkClientType, SdkHttpOperation } from "@azure-tools/typespec-client-generator-core";
import { PythonSdkContext } from "./lib.js";
import { PythonSdkHttpOperation, PythonSdkHttpResponse } from "./interfaces.js";
import { getPythonSdkType, getSdkModelPropertyType } from "./types.js";

function serializeHttpServiceOperation(
context: PythonSdkContext<SdkHttpOperation>,
method: SdkServiceMethod<SdkHttpOperation>,
operation: SdkHttpOperation,
): PythonSdkHttpOperation {
return {
...operation,
parameters: operation.parameters.map((x) => getSdkModelPropertyType(context, x)),
bodyParam: operation.bodyParam
? getSdkModelPropertyType(context, operation.bodyParam)
: undefined,
responses: Object.entries(operation.responses).map(([statusCode, responses]) =>
serializeHttpResponse(context, method, statusCode, responses),
),
exceptions: Object.entries(operation.exceptions).map(([statusCode, exceptions]) =>
serializeHttpResponse(context, method, statusCode, exceptions),
),
};

function serializeHttpResponse(
context: PythonSdkContext<SdkHttpOperation>,
response: SdkHttpResponse,
): PythonSdkHttpResponse {
return {
...response,
type: response.type ? getPythonSdkType(context, response.type) : undefined,
};
}
}

types.ts​

import { SdkServiceOperation, SdkType, SdkModelType, SdkModelPropertyType } from "@azure-tools/typespec-client-generator-core";
import { PythonSdkContext } from "./lib.js";
import { PythonSdkType, PythonSdkModelType, PythonSdkEnumType } from "./interfaces.js";

export function getSdkModelPropertyType<
TServiceOperation extends SdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
prop: SdkModelPropertyType
): PythonSdkModelType {
const type = getPythonSdkType(context, prop.type)
switch (prop.kind) {
case "query":
case "header"
case "path":
return {
...prop,
correspondingMethodParams: prop.correspondingMethodParams.map(
(x) => getSdkModelPropertyType(context, x),
)
}
default:
return {
...prop,
type,
}
}
}

export function getPythonSdkType<
TServiceOperation extends SdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
type: SdkType,
): PythonSdkType {
// We don't have to convert all types, just types that we want
// to add more information onto
switch (type.kind) {
case "model":
return getPythonModelType(context, type);
case "enum":
return getPythonEnumType(context, type);
default:
return type;
}
}

function getPythonModelType<
TServiceOperation extends SdkServiceOperation
>(
context: PythonSdkContext<TServiceOperation>,
type: SdkModelType,
): PythonSdkModelType {
return {
...type,
snakeCaseName: camelToSnakeCase(type.name),
properties: type.properties.map(
(x) => getPythonBodyModelPropertyType(
context, x,
)
),
baseModel: type.baseModel ? getPythonModelType(context, type.baseModel) : undefined,
...
}
}