Skip to content

Clients

This page documents the client behavior as well as how to customize clients.

NOTE: The JS RLC doesn’t support client hierarchy customization and will ignore client.tsp. Therefore, the following scenarios won’t affect the JS RLC user experience. In this context, the TypeScript part refers to the JS Modular Emitter.

By default, the first 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.

@versioned(Versions)
@service(#{ title: "Pet Store" })
namespace PetStore;
enum Versions {
v1,
}
@route("/feed") op feed(): void;
@route("/pet") op pet(): void;
from pet_store import PetStoreClient
client = PetStoreClient()
client.feed()
client.pet()

Client with One-Layer Child Operation Groups

Section titled “Client with One-Layer Child Operation Groups”

The PetStore includes two operation groups: Dogs, which belongs to a subnamespace, and Cats, which is part of an interface.

@versioned(Versions)
@service(#{ title: "Pet Store", version: "v1" })
namespace PetStore;
enum Versions {
v1,
}
@route("/dogs")
interface Dogs {
@route("feed") feed(): void;
@route("pet") pet(): void;
}
@route("/cats")
namespace Cats {
@route("feed") op feed(): void;
@route("pet") 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

Section titled “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.

@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 {
@route("feed") feed(): void;
@route("pet") pet(): void;
}
}
@route("/actions")
interface Actions {
@route("open") open(): void;
@route("close") 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 SHOULD always be made in a file named client.tsp alongside main.tsp.

There are two ways to customize the client hierarchy: using @clientLocation to change the location of the operations, or use @client and @operationGroup to restructure the client hierarchy. These two ways should not be used together. If so, @client and @operationGroup will take precedence over @clientLocation.

The @clientLocaton decorator allows you to change the location of the operations in the client hierarchy. It can be used to move operations to an existed sub client defined by Interface or Namespace, or move the the root client of namespace with @service decorator. This decorator is useful when you want to keep the default client hierarchy but need to change the location of some operations.

The @client and @operationGroup decorators allow you to totally 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.

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

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

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()

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()

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()

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

Section titled “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

Section titled “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()

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()

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