Skip to content

Clients

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

By default, each namespace with the @service decorator is generated as a separate 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 sub clients with a hierarchical structure. The sequence of sub clients 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 a sub client 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, sub clients can only be initialized by the root client or their parent sub client.

Different languages organize clients and sub clients 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;
# generated _client.py
class PetStoreClient(_PetStoreClientOperationsMixin):
def __init__(self, endpoint: str, **kwargs: Any) -> None: ...
# generated _operations/_operations.py
class _PetStoreClientOperationsMixin:
@distributed_trace
def feed(self, **kwargs: Any) -> None:
@distributed_trace
def pet(self, **kwargs: Any) -> None:
#usage sample
from pet_store import PetStoreClient
client = PetStoreClient()
client.feed()
client.pet()

The PetStore includes two sub clients: 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;
}
# generated _client.py
class PetStoreClient:
def __init__(self, endpoint: str, **kwargs: Any) -> None: ...
# generated operations/_operations.py
class DogsOperations:
@distributed_trace
def feed(self, **kwargs: Any) -> None:
@distributed_trace
def pet(self, **kwargs: Any) -> None:
# generated cats/operations/_operations.py
class CatsOperations:
@distributed_trace
def feed(self, **kwargs: Any) -> None:
@distributed_trace
def pet(self, **kwargs: Any) -> None:
#usage sample
from pet_store import PetStoreClient
client = PetStoreClient()
client.dogs.feed()
client.dogs.pet()
client.cats.feed()
client.cats.pet()

PetStore has three sub clients: Billings (from an interface), Pets (from a sub-namespace), and Actions (from an interface). Pets also contains a nested sub client, 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;
}
# generated _client.py
class PetStoreClient(_PetStoreClientOperationsMixin):
def __init__(self, endpoint: str, **kwargs: Any) -> None: ...
# generated operations/_operations.py
class _PetStoreClientOperationsMixin:
@distributed_trace
def info(self, **kwargs: Any) -> None:
class BillingsOperations:
@distributed_trace
def history(self, **kwargs: Any) -> None:
class ActionsOperations:
@distributed_trace
def open(self, **kwargs: Any) -> None:
@distributed_trace
def close(self, **kwargs: Any) -> None:
# generated pets/operations/_operations.py
class PetsOperations:
@distributed_trace
def info(self, **kwargs: Any) -> None:
class PetsActionsOperations:
@distributed_trace
def feed(self, **kwargs: Any) -> None:
@distributed_trace
def pet(self, **kwargs: Any) -> None:
#usage sample
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 to restructure the client hierarchy. Be careful when you use both of them since @clientLocation could not move the client that is no longer exist with @client customization.

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 decorator allows 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. You can override this using @clientNamespace if needed.

The sequence of clients and sub clients is determined by the order of the @client 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;
using PetStore;
@@clientLocation(Feeds.feed, PetStore);
@@clientLocation(Pets.pet, PetStore);
# generated _client.py
class PetStoreClient(_PetStoreClientOperationsMixin):
def __init__(self, endpoint: str, **kwargs: Any) -> None: ...
# generated _operations/_operations.py
class _PetStoreClientOperationsMixin:
@distributed_trace
def feed(self, **kwargs: Any) -> None:
@distributed_trace
def pet(self, **kwargs: Any) -> None:
#usage sample
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");
# generated _client.py
class PetStoreGreatClient(_PetStoreGreatClientOperationsMixin):
def __init__(self, endpoint: str, **kwargs: Any) -> None: ...
# generated _operations/_operations.py
class _PetStoreGreatClientOperationsMixin:
@distributed_trace
def feed(self, **kwargs: Any) -> None:
@distributed_trace
def pet(self, **kwargs: Any) -> None:
#usage sample
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");
# generated _client.py
class PetStoreClient(_PetStoreClientOperationsMixin):
def __init__(self, endpoint: str, **kwargs: Any) -> None: ...
# generated _operations/_operations.py
class _PetStoreClientOperationsMixin:
@distributed_trace
def feed(self, **kwargs: Any) -> None:
@distributed_trace
def pet(self, **kwargs: Any) -> None:
#usage sample
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.

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 sub clients 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;
}
# generated _client.py
class FoodClient(_FoodClientOperationsMixin):
def __init__(self, endpoint: str, **kwargs: Any) -> None: ...
class PetActionClient(_PetActionClientOperationsMixin):
def __init__(self, endpoint: str, **kwargs: Any) -> None: ...
# generated _operations/_operations.py
class _FoodClientOperationsMixin:
@distributed_trace
def feed(self, **kwargs: Any) -> None:
class _PetActionClientOperationsMixin
@distributed_trace
def pet(self, **kwargs: Any) -> None:
#usage sample
from pet_store_renamed import FoodClient, PetActionClient
client1 = FoodClient()
client2 = PetActionClient()
client1.feed()
client2.pet()

Two sub clients that separate the operations can be declared using the @client decorator (or the @operationGroup decorator, which is an alias) 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 sub clients defined in this customization file
@operationGroup
interface OpGrp1 {
feed is PetStore.feed;
}
@operationGroup
interface OpGrp2 {
pet is PetStore.pet;
}
# generated _client.py
class PetStoreClient:
def __init__(self, endpoint: str, **kwargs: Any) -> None: ...
# generated operations/_operations.py
class OpGrp1Operations:
@distributed_trace
def feed(self, **kwargs: Any) -> None:
class OpGrp2Operations:
@distributed_trace
def pet(self, **kwargs: Any) -> None:
#usage sample
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;
}
# generated food/_client.py
class FoodClient(_FoodClientOperationsMixin):
def __init__(self, endpoint: str, **kwargs: Any) -> None: ...
# generated food/_operations/_operations.py
class _FoodClientOperationsMixin:
@distributed_trace
def feed(self, **kwargs: Any) -> None:
# generated petaction/_client.py
class PetActionClient(_PetActionClientOperationsMixin):
def __init__(self, endpoint: str, **kwargs: Any) -> None: ...
# generated petaction/_operations/_operations.py
class _PetActionClientOperationsMixin:
@distributed_trace
def pet(self, **kwargs: Any) -> None:
#usage sample
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 sub clients 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;
}
# generated _client.py
class FoodClient(_FoodClientOperationsMixin):
def __init__(self, endpoint: str, **kwargs: Any) -> None: ...
# generated _operations/_operations.py
class _FoodClientOperationsMixin:
@distributed_trace
def feed(self, **kwargs: Any) -> None:
# generated subnamespace/_client.py
class PetActionClient(_PetActionClientOperationsMixin):
def __init__(self, endpoint: str, **kwargs: Any) -> None: ...
# generated subnamespace/_operations/_operations.py
class _PetActionClientOperationsMixin:
@distributed_trace
def pet(self, **kwargs: Any) -> None:
#usage sample
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,
}
);
# generated _client.py
class StorageClient(_StorageClientOperationsMixin):
def __init__(self, endpoint: str, blob_name: str, **kwargs: Any) -> None: ...
# generated _operations/_operations.py
class _StorageClientOperationsMixin:
@distributed_trace
def upload(self, **kwargs: Any) -> None:
@distributed_trace
def download(self, **kwargs: Any) -> None:
#usage sample
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,
}
);
# generated _client.py
class StorageClient(_StorageClientOperationsMixin):
def __init__(self, endpoint: str, blob: str, **kwargs: Any) -> None: ...
# generated _operations/_operations.py
class _StorageClientOperationsMixin:
@distributed_trace
def upload(self, **kwargs: Any) -> None:
@distributed_trace
def download(self, **kwargs: Any) -> None:
#usage sample
from storage import StorageClient
client = StorageClient(endpoint="<my-endpoint>", blob="myBlobName", ...)
client.upload()
client.download()

By default, all the nested sub clients could only be initialized by parent client or sub client. There are cases where spec authors would like their sub clients 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,
}
);
NOT_SUPPORTED

You could define a single client that combines operations from multiple services. This is useful when you want to provide a unified client experience for related services that share a common endpoint, but require different versioning.

  1. Service Array: Set all the services that you want to merge in to the @client decorator’s service property (e.g., service: [ServiceA, ServiceB]).

  2. Auto-Merge with autoMergeService: true: Set autoMergeService: true on the @client decorator to automatically merge all operations and sub clients from every listed service into the single client. autoMergeService defaults to false. When false or omitted, the client will have no auto-populated operations — you would need to define child @client decorators or use is to map operations explicitly.

  3. Versioning: Each service’s API versions are resolved independently from its own @versioned definition. TCGC always uses the latest version for each service. No @useDependency is needed.

  4. Sub client Structure: When autoMergeService: true is set, operations and sub clients will be auto merged into the single client (based on operations, interfaces, or namespaces from the original services).

  5. Sub client Merging: If multiple services have nested namespaces or interfaces with the same name, TCGC will automatically merge them into a single sub client. The merged sub client will have empty apiVersions and a string type for the API version parameter, and will contain operations from all the services.

  6. @clientLocation with Multiple Services: You can use @clientLocation to move operations from different services to the same sub client. The resulting sub client will have empty apiVersions and a string type for the API version parameter, allowing it to handle operations with different API versions.

client.tsp
import "./main.tsp";
import "@azure-tools/typespec-client-generator-core";
using Azure.ClientGenerator.Core;
@client({
name: "CombineClient",
service: [ServiceA, ServiceB],
autoMergeService: true,
})
namespace CombineClient;
# generated _client.py
class CombineClient:
def __init__(self, endpoint: str, **kwargs: Any) -> None:
self.ai = AIOperations(endpoint=endpoint, **kwargs)
self.bi = BIOperations(endpoint=endpoint, **kwargs)
# generated operations/_operations.py
class AIOperations:
def __init__(self, client, api_version: str = "av2") -> None: ...
@distributed_trace
def a_test(self, **kwargs: Any) -> None: ...
class BIOperations:
def __init__(self, client, api_version: str = "bv2") -> None: ...
@distributed_trace
def b_test(self, **kwargs: Any) -> None: ...
# usage sample
from combine_client import CombineClient
client = CombineClient(endpoint="<my-endpoint>")
client.ai.a_test() # uses api-version av2
client.bi.b_test() # uses api-version bv2

Mixing Multi-Service and Single-Service Clients

Section titled “Mixing Multi-Service and Single-Service Clients”

You can combine multiple services into one client while keeping another service as a separate client. This is useful when some services are closely related and should be accessed through a unified client, while others are independent enough to warrant their own client.

Set autoMergeService: true on any client that should automatically pull in all operations from its service(s). A multi-service client uses service: [ServiceA, ServiceB] with autoMergeService: true to merge everything, while a single-service client uses service: ServiceC with autoMergeService: true to get default behavior.

client.tsp
import "./main.tsp";
import "@azure-tools/typespec-client-generator-core";
using Azure.ClientGenerator.Core;
// Multi-service client combining ServiceA and ServiceB
@client({
name: "CombinedABClient",
service: [ServiceA, ServiceB],
autoMergeService: true,
})
namespace CombinedABClient;
// Single-service client for ServiceC
@client({
name: "ServiceCClient",
service: ServiceC,
autoMergeService: true,
})
namespace ServiceCClient;
# generated _client.py
class CombinedABClient:
def __init__(self, endpoint: str, **kwargs: Any) -> None:
self.operations = OperationsOperations(endpoint=endpoint, **kwargs)
class ServiceCClient:
def __init__(self, endpoint: str, **kwargs: Any) -> None:
self.operations = OperationsOperations(endpoint=endpoint, **kwargs)
# usage sample
from combined_ab_client import CombinedABClient
from service_c_client import ServiceCClient
# Combined client for ServiceA and ServiceB
combined_client = CombinedABClient(endpoint="<my-endpoint>")
combined_client.operations.op_a() # From ServiceA
combined_client.operations.op_b() # From ServiceB
# Separate client for ServiceC
service_c_client = ServiceCClient(endpoint="<my-endpoint>")
service_c_client.operations.op_c()

Services as Direct Children (No Deep Auto-Merge)

Section titled “Services as Direct Children (No Deep Auto-Merge)”

When combining multiple services, instead of auto-merging all sub clients at the root level, you can keep each service as a direct child of the root client.

The key is to omit autoMergeService (or set it to false) on the root client so it does NOT merge everything at the top level. Then, set autoMergeService: true on each child @client so that each child auto-populates from its own service. This creates a hierarchy where services remain separated under the root client.

client.tsp
import "./main.tsp";
import "@azure-tools/typespec-client-generator-core";
using Azure.ClientGenerator.Core;
@client({
name: "CombineClient",
service: [ServiceA, ServiceB],
})
namespace CombineClient {
@client({
name: "ComputeClient",
service: ServiceA,
autoMergeService: true,
})
namespace Compute;
@client({
name: "DiskClient",
service: ServiceB,
autoMergeService: true,
})
namespace Disk;
}
# generated _client.py
class CombineClient:
def __init__(self, endpoint: str, **kwargs: Any) -> None:
self.compute = ComputeClient(endpoint=endpoint, **kwargs)
self.disk = DiskClient(endpoint=endpoint, **kwargs)
# usage sample
from combine_client import CombineClient
client = CombineClient(endpoint="<my-endpoint>")
# Access ServiceA operations via ComputeClient
client.compute.operations.op_a()
client.compute.sub_namespace.sub_op_a()
# Access ServiceB operations via DiskClient
client.disk.operations.op_b()
client.disk.sub_namespace.sub_op_b()

Fully Customized Client Hierarchy from Multiple Services

Section titled “Fully Customized Client Hierarchy from Multiple Services”

For maximum flexibility, you can fully customize how operations from different services are organized into a client hierarchy. This uses nested @client decorators with explicit operation mapping via the is keyword.

In this approach, autoMergeService is not set (defaults to false) on any client — neither the root nor the children. Instead of auto-populating from services, each child @client explicitly declares its operations using is references (e.g., opA is ServiceA.Operations.opA). This gives you full control over which operations go where, including the ability to mix operations from different services into a single sub client.

client.tsp
import "./main.tsp";
import "@azure-tools/typespec-client-generator-core";
using Azure.ClientGenerator.Core;
@client({
name: "CustomClient",
service: [ServiceA, ServiceB],
})
namespace CustomClient {
// Custom sub client combining operations from both services
@client({
name: "SharedOperations",
service: [ServiceA, ServiceB],
})
interface SharedOperations {
opA is ServiceA.Operations.opA;
opB is ServiceB.Operations.opB;
}
// Custom sub client with operations from ServiceA only
@client({
name: "ServiceAOnly",
service: ServiceA,
})
interface ServiceAOnly {
subOpA is ServiceA.SubNamespace.subOpA;
}
// Custom sub client with operations from ServiceB only
@client({
name: "ServiceBOnly",
service: ServiceB,
})
interface ServiceBOnly {
subOpB is ServiceB.SubNamespace.subOpB;
}
}
# generated _client.py
class CustomClient:
def __init__(self, endpoint: str, **kwargs: Any) -> None:
self.shared_operations = SharedOperationsOperations(endpoint=endpoint, **kwargs)
self.service_a_only = ServiceAOnlyOperations(endpoint=endpoint, **kwargs)
self.service_b_only = ServiceBOnlyOperations(endpoint=endpoint, **kwargs)
# usage sample
from custom_client import CustomClient
client = CustomClient(endpoint="<my-endpoint>")
# Access shared operations from both services
client.shared_operations.op_a() # Uses ServiceA's API version
client.shared_operations.op_b() # Uses ServiceB's API version
# Access ServiceA-only operations
client.service_a_only.sub_op_a()
# Access ServiceB-only operations
client.service_b_only.sub_op_b()