Configure Provider Clients

Su Suriya Ravichandran Updated 10 Nov 2026
5 min read ·Lesson 167 of 200

One Place That Creates the Client

Once a platform is chosen (Lesson 165), the application still has to create the right client, with the right settings, wherever it needs one. In a small script, that is one line. In a larger application, the same client is created in several modules, and each copy of the setup is another place where a setting can drift: one module uses a timeout of sixty seconds, another uses the default, and a third forgot the region entirely.

The remedy is a single factory. Configuration is read once, validated once, and a client is produced from it wherever the application needs one. This lesson builds that factory, explains why the configuration is kept separate from the client, and shows how the same code can serve the direct API and each cloud. The factory is also where the settings from earlier lessons, such as timeouts and retry counts (Lessons 143 and 144), are applied consistently.


Configuration Separate From Creation

The factory has two steps, and keeping them apart makes both easier to test. The first step turns a configuration into a set of constructor arguments, which is pure data and can be checked without any network or SDK. The second step passes those arguments to the client class, which is the only part that touches the SDK. Tests exercise the first step thoroughly, and the second only needs a few checks.

configuration ──► constructor arguments ──► client
  (data, checked)     (data, checked)        (the SDK)

Every platform-specific rule lives in the first step. The second step is a single line per provider, which means the rules cannot be bypassed by creating a client directly somewhere else.


The Settings Every Client Shares

Some settings apply to every provider: the timeout, which bounds each request (Lesson 144), and the retry count, which decides how many automatic retries the SDK makes (Lesson 143). Setting them in one place keeps the whole application consistent. A team that changes its timeout changes one value, and every client follows.

The provider-specific settings differ: a region for Bedrock, a project and region for Vertex, and a resource name for Foundry. The configuration holds those values too, and the constructor step requires the ones each provider needs.


The Factory

The factory below reads a configuration, builds the constructor arguments, and creates the client. The SDK classes are imported inside the function that needs them, so the configuration logic can be tested on a machine that has only some of the cloud extras installed:

"""provider_clients.py: one factory for every integration, with validation before creation."""

from dataclasses import dataclass


@dataclass(frozen=True)
class ProviderConfig:
    provider: str
    region: str | None = None
    project_id: str | None = None
    resource: str | None = None
    timeout: float = 60.0
    max_retries: int = 2


def constructor_kwargs(config: ProviderConfig) -> dict:
    kwargs = {"timeout": config.timeout, "max_retries": config.max_retries}
    if config.provider == "bedrock":
        if not config.region:
            raise ValueError("bedrock needs a region")
        kwargs["aws_region"] = config.region
    elif config.provider == "vertex":
        if not config.project_id or not config.region:
            raise ValueError("vertex needs project_id and region")
        kwargs.update(project_id=config.project_id, region=config.region)
    elif config.provider == "foundry":
        if not config.resource:
            raise ValueError("foundry needs a resource name")
        kwargs["resource"] = config.resource
    elif config.provider != "direct":
        raise ValueError(f"unknown provider {config.provider}")
    return kwargs


def make_client(config: ProviderConfig):
    kwargs = constructor_kwargs(config)
    if config.provider == "direct":
        from anthropic import Anthropic
        return Anthropic(**kwargs)
    if config.provider == "bedrock":
        from anthropic import AnthropicBedrock
        return AnthropicBedrock(**kwargs)
    if config.provider == "vertex":
        from anthropic import AnthropicVertex
        return AnthropicVertex(**kwargs)
    from anthropic import AnthropicFoundry
    return AnthropicFoundry(**kwargs)


if __name__ == "__main__":
    configs = [
        ProviderConfig("direct"),
        ProviderConfig("bedrock", region="us-east-1"),
        ProviderConfig("vertex", project_id="my-project-id", region="global"),
        ProviderConfig("foundry", resource="acme-claude"),
        ProviderConfig("bedrock"),
    ]
    for config in configs:
        try:
            print(f"{config.provider}: {constructor_kwargs(config)}")
        except ValueError as error:
            print(f"{config.provider}: error: {error}")

Output:

direct: {'timeout': 60.0, 'max_retries': 2}
bedrock: {'timeout': 60.0, 'max_retries': 2, 'aws_region': 'us-east-1'}
vertex: {'timeout': 60.0, 'max_retries': 2, 'project_id': 'my-project-id', 'region': 'global'}
foundry: {'timeout': 60.0, 'max_retries': 2, 'resource': 'acme-claude'}
bedrock: error: bedrock needs a region

How It Works

  1. The configuration is immutable. The frozen dataclass cannot be changed after it is created, so a setting cannot drift while the application runs.
  2. constructor_kwargs() holds the platform rules. Each provider's required values are checked, and a missing value raises an error that names the problem. The fifth configuration, a Bedrock client with no region, is rejected here, before any client exists.
  3. The shared settings come first in the arguments, so every provider receives the same timeout and retry count unless the configuration says otherwise.
  4. make_client() imports the SDK class only when it is needed. A machine without the Vertex extra can still run the configuration logic and its tests, because the Vertex import happens only on the Vertex path.
  5. Errors are raised as ValueError, which the caller can catch at startup and report, so a misconfiguration stops the program before it serves a request.

Creating Clients Once

A client holds connections and configuration, and creating one per request wastes effort. The factory's output should normally be created once when the application starts, and shared by the code that needs it. Lesson 79 covered closing clients, and the same care applies here: a shared client is closed when the application shuts down, not after each use.


Testing the Factory

The configuration step is pure, so it can be tested directly: each provider with valid settings returns the expected arguments, and each missing value raises the expected error. The client creation step needs only a smoke test per provider, which confirms that the class is constructed with the arguments the factory produced. Separating the two keeps most of the testing fast and free of credentials.


Summary

A single factory keeps client creation consistent across an application. Configuration is validated and turned into constructor arguments, which is data that tests can check without any SDK. The shared timeout and retry settings apply to every provider, and each provider's required values are enforced in one place. Clients are created once and shared. The next lesson covers the errors that cloud integrations return, which need their own handling on top of the shared classes.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Configure Provider Clients and get answers drawn from it.

Signed-in readers only.