Skip to content

Clients

This page explains client behavior and how to customize clients. For an overview of the setup, please refer to the previous page.

NOTE: JS RLC does not support customization. It will ignore client.tsp, and the following scenarios will not affect the JS RLC user experience. In this context, “TypeScript part” refers to the JS Modular Emitter.

Default Behavior

Basic Rules

By default, the namespace with the @service decorator is generated as a root client. The client name is the namespace name with Client appended as a suffix.

Sub-namespaces and interfaces under each root client are generated as operation groups with a hierarchical structure.

The sequence of operation groups is determined by the order of namespace declarations, followed by interface declarations.

The root client’s SDK namespace corresponds to the namespace decorated with @service. If an operation group originates from a sub-namespace, its SDK namespace corresponds to that sub-namespace. If it originates from an interface, its SDK namespace corresponds to the namespace containing the interface.

By default, operation groups can only be initialized by the root client or their parent operation group.

Different languages organize clients and operation groups differently. Refer to the examples below for details.

Single Client

main.tsp
@service(#{ title: "Pet Store" })
namespace PetStore;
@route("/feed")
op feed(): void;
@route("/op2")
op pet(): void;
from pet_store import PetStoreClient
client = PetStoreClient()
client.feed()
client.pet()

Client with One-Layer Child Operation Groups

PetStore has two operation groups. The Dogs operation group comes from a sub-namespace, while Cats comes from an interface.

@service(#{ title: "Pet Store", version: "v1" })
namespace PetStore;
@route("/dogs")
interface Dogs {
feed(): void;
pet(): void;
}
@route("/cats")
namespace Cats {
op feed(): void;
op pet(): void;
}
from pet_store import PetStoreClient
client = PetStoreClient()
client.dogs.feed()
client.dogs.pet()
client.cats.feed()
client.cats.pet()

Client with Multi-Layer Child Operation Groups

PetStore has three operation groups: Billings (from an interface), Pets (from a sub-namespace), and Actions (from an interface). Pets also contains a nested operation group, Actions, from an interface.

main.tsp
@service(#{ title: "Pet Store", version: "v1" })
namespace PetStore;
@route("/info")
op info(): void;
@route("/billings")
interface Billings {
@route("history")
history(): void;
}
@route("/pets")
namespace Pets {
@route("info")
op info(): void;
@route("/actions")
interface Actions {
feed(): void;
pet(): void;
}
}
@route("/actions")
interface Actions {
open(): void;
close(): void;
}
from pet_store import PetStoreClient
client = PetStoreClient()
client.info()
client.billings.history()
client.pets.info()
client.pets.actions.feed()
client.pets.actions.pet()
client.actions.open()
client.actions.close()

Customizations

Customizations SHOULD always be made in a file named client.tsp alongside main.tsp.

You can use @client and @operationGroup to restructure the client hierarchy. However, if any customizations are made, the client hierarchy will only reflect those customizations. The default behavior logic will no longer apply.

If customizations are made, the client’s SDK namespace will follow the namespace decorated with @client or the namespace containing the interface decorated with @client. Similarly, the operation group’s SDK namespace follows the same logic for @operationGroup. You can override this using @clientNamespace if needed.

The sequence of clients and operation groups is determined by the order of the @client and @operationGroup decorators.

For this section, we assume you have a service called PetStore in the PetStore namespace, defining two operations: feed and pet.

Renaming the Client Name

This can be achieved with the augment decorator: @clientName from typespec-client-generator-core.

client.tsp
import "./main.tsp";
import "@azure-tools/typespec-client-generator-core";
using Azure.ClientGenerator.Core;
@@clientName(PetStore, "PetStoreGreatClient");
from pet_store import PetStoreGreatClient
client = PetStoreGreatClient()
client.feed()
client.pet()

Renaming the Client Namespace

This can be achieved with the augment decorator: @clientNamespace from typespec-client-generator-core.

client.tsp
import "./main.tsp";
import "@azure-tools/typespec-client-generator-core";
using Azure.ClientGenerator.Core;
@@clientNamespace(PetStore, "PetStoreRenamed");
from pet_store_renamed import PetStoreClient
client = PetStoreClient()
client.feed()
client.pet()

Splitting the Operations into Two Clients

Two clients that separate the operations can be declared using the @client decorator from typespec-client-generator-core.

import "./main.tsp";
import "@azure-tools/typespec-client-generator-core";
using Azure.ClientGenerator.Core;
namespace PetStoreRenamed; // this namespace will be the namespace of the clients and operation groups defined in this customization file
@client({
name: "FoodClient",
service: PetStore,
})
interface Client1 {
feed is PetStore.feed;
}
@client({
name: "PetActionClient",
service: PetStore,
})
interface Client2 {
pet is PetStore.pet;
}
from pet_store_renamed import FoodClient, PetActionClient
client1 = FoodClient()
client2 = PetActionClient()
client1.feed()
client2.pet()

One Client and Two Operation Groups

Two clients that separate the operations can be declared using the @client decorator and the @operationGroup decorator from typespec-client-generator-core:

client.tsp
import "./main.tsp";
import "@azure-tools/typespec-client-generator-core";
using Azure.ClientGenerator.Core;
@client({
name: "PetStoreClient",
service: PetStore,
})
namespace PetStoreRenamed; // this namespace will be the namespace of the clients and operation groups defined in this customization file
@operationGroup
interface OpGrp1 {
feed is PetStore.feed;
}
@operationGroup
interface OpGrp2 {
pet is PetStore.pet;
}
from pet_store_renamed import PetStoreClient
client = PetStoreClient()
client.op_grp_1.feed()
client.op_grp_2.pet()

Splitting the Operations into Sub-Namespaces

client.tsp
import "./main.tsp";
import "@azure-tools/typespec-client-generator-core";
using Azure.ClientGenerator.Core;
namespace NewPetStore;
@client({
name: "FoodClient",
service: PetStore,
})
namespace Food {
op feed is PetStore.feed;
}
@client({
name: "PetActionClient",
service: PetStore,
})
namespace PetAction {
op pet is PetStore.pet;
}
from new_pet_store.food import FoodClient
from new_pet_store.pet_action import PetActionClient
client1 = FoodClient()
client2 = PetActionClient()
client1.feed()
client2.pet()

Splitting the Operations into Two Clients and Having Clients in Different Namespaces

Two clients that separate the operations can be declared using the client decorator of typespec-client-generator-core:

client.tsp
import "./main.tsp";
import "@azure-tools/typespec-client-generator-core";
using Azure.ClientGenerator.Core;
namespace PetStoreRenamed; // this namespace will be the namespace of the clients and operation groups defined in this customization file
@client({
name: "FoodClient",
service: PetStore,
})
interface Client1 {
feed is PetStore.feed;
}
@client({
name: "PetActionClient",
service: PetStore,
})
@clientNamespace("PetStoreRenamed.SubNamespace") // use @clientNamespace to specify the namespace of the client
interface Client2 {
pet is PetStore.pet;
}
from pet_store_renamed import FoodClient
from pet_store_renamed.sub_namespace import PetActionClient
client1 = FoodClient()
client2 = PetActionClient()
client1.feed()
client2.pet()

Adding Client Initialization Parameters

By default, we only generate our clients with initialization parameters for endpoint, credential, and apiVersion, whenever any of these are applicable. There are cases where spec authors would like their clients to have additional input parameters.

With @clientInitialization, you can pass in additional parameters you would like your client to have, by passing in parameters option of a model. All properties of the model will be appended to the current default list of client initialization parameters. Additionally, these client parameters will no longer appear on service methods that previously had them as part of the method signature. The generated code will automatically pass in the inputted value from the client init to the service.

client.tsp
import "./main.tsp";
import "@azure-tools/typespec-client-generator-core";
using Azure.ClientGenerator.Core;
namespace Customizations;
model StorageClientOptions {
blobName: string;
}
@@clientInitialization(Storage,
{
parameters: StorageClientOptions,
}
);
from storage import StorageClient
client = StorageClient(endpoint="<my-endpoint>", blob_name="myBlobName", ...)
client.upload()
client.download()

If you want to rename the parameter name that you elevate to the client level, you can use the @paramAlias decorator.

client.tsp
import "./main.tsp";
import "@azure-tools/typespec-client-generator-core";
using Azure.ClientGenerator.Core;
namespace Customizations;
model StorageClientOptions {
@paramAlias("blobName")
blob: string;
}
@@clientInitialization(Storage,
{
parameters: StorageClientOptions,
}
);
from storage import StorageClient
client = StorageClient(endpoint="<my-endpoint>", blob="myBlobName", ...)
client.upload()
client.download()

Change Operation Group Initialization Way

By default, all the nested operation group could only be initialized by parent client or operation group. There are cases where spec authors would like their operation groups could both be initialized by parent as well as individually.

With @clientInitialization, you can change the initialization way, by passing in initializedBy option of InitializedBy.individually | InitializedBy.parent value.

client.tsp
import "./main.tsp";
import "@azure-tools/typespec-client-generator-core";
using Azure.ClientGenerator.Core;
namespace Customizations;
model StorageClientOptions {
blobName: string;
}
@@clientInitialization(Storage,
{
initializedBy: InitializedBy.individually | InitializedBy.parent,
}
);
# TODO