Skip to content

Configuration

Both VClient (async) and SyncVClient (sync) accept the same constructor options for timeouts, retries, idempotency, and more. You can also set required values through environment variables. See the Sync Client page for sync-specific usage patterns.

Configuration Options

Pass these options when creating a VClient instance:

from vclient import VClient

client = VClient(
    base_url="https://api.valentina-noir.com",
    api_key="your-api-key",
    timeout=30.0,
    max_retries=3,
    retry_delay=1.0,
    auto_retry_rate_limit=True,
    auto_idempotency_keys=False,
    retry_statuses={429, 500, 502, 503, 504},
    default_company_id=None,
    headers=None,
)
Option Type Default Description
base_url str or None None Base URL for the API. Falls back to VALENTINA_CLIENT_BASE_URL env var.
api_key str or None None API key for authentication. Falls back to VALENTINA_CLIENT_API_KEY env var.
timeout float 30.0 Request timeout in seconds.
max_retries int 3 Maximum retry attempts for failed requests.
retry_delay float 1.0 Base delay between retries in seconds.
auto_retry_rate_limit bool True Automatically retry rate-limited requests.
auto_idempotency_keys bool False Auto-generate idempotency keys for POST/PUT/PATCH.
retry_statuses set[int] or None {429, 500, 502, 503, 504} HTTP status codes that trigger automatic retries.
default_company_id str or None None Default company ID for service factory methods. Falls back to VALENTINA_CLIENT_DEFAULT_COMPANY_ID env var.
headers dict[str, str] or None None Additional headers to include with all requests.

Note

base_url and api_key are required. If not passed as arguments, they must be set via the corresponding environment variables. A ValueError is raised if neither source provides a value.

Environment Variables

The client reads configuration from environment variables when constructor arguments aren't provided. Explicit arguments always take precedence.

Environment Variable Maps To Required
VALENTINA_CLIENT_BASE_URL base_url Yes
VALENTINA_CLIENT_API_KEY api_key Yes
VALENTINA_CLIENT_DEFAULT_COMPANY_ID default_company_id No
from vclient import VClient

# All config from environment variables
client = VClient()

# Mix: explicit base_url, api_key from env var
client = VClient(base_url="https://staging.valentina-noir.com")

Idempotency Keys

Enable automatic idempotency key generation for all mutating requests (POST, PUT, PATCH). The client includes a unique UUID v4 header with each request, ensuring safe retries.

from vclient import VClient

client = VClient(
    base_url="https://api.valentina-noir.com",
    api_key="your-api-key",
    auto_idempotency_keys=True,
)

Safe Retries

When enabled, the client automatically generates and includes an Idempotency-Key header for every POST, PUT, and PATCH request. The server detects duplicate requests and returns the same response, making retries safe even for non-idempotent operations.

Retry Behavior

When auto_retry_rate_limit is enabled (the default), the client automatically retries requests that encounter transient failures. Retries use exponential backoff with jitter.

The following table summarizes which conditions trigger retries:

Condition Retried? Notes
Rate limit (429) Always Respects Retry-After / RateLimit headers
Server error (5xx) Idempotent methods only GET, PUT, DELETE always retry; POST/PATCH only with idempotency key
Network error Idempotent methods only ConnectError, TimeoutException from httpx
Client error (4xx) Never 400, 401, 403, 404, 409 are not transient

Non-idempotent methods (POST, PATCH) are only retried on 5xx and network errors when an idempotency key is present — either explicitly provided or auto-generated via auto_idempotency_keys=True. This prevents duplicate side effects from retrying unsafe requests.

Safe Retries for All Methods

Enable auto_idempotency_keys=True to make all mutating requests (POST, PUT, PATCH) safe to retry on transient errors. The client auto-generates a unique Idempotency-Key header for each request.

To customize which status codes trigger retries:

# Only retry on 429 and 503
client = VClient(
    base_url="https://api.valentina-noir.com",
    api_key="your-api-key",
    retry_statuses={429, 503},
)

Logging

The client uses Loguru for structured logging, disabled by default. Enable it to see HTTP request/response details, retry attempts, and error information.

Enable Logging

from loguru import logger

# Add a sink that includes structured fields or make sure {extra} is available in your format string
logger.add(
    "vclient.log",
    format="{time} | {level} | {message} | {extra}",
    filter="vclient",
)

# Enable vclient logs (they flow to your existing loguru handlers)
logger.enable("vclient")

Using with Standard Library Logging

The client includes a bridge to Python's stdlib logging module. After enabling, logs are also available through the standard logging system. Structured fields are accessible on each LogRecord via its extra attribute.

To include structured fields in your log output, create a custom handler that reads the extra dict from each record:

import logging
from loguru import logger


class StructuredHandler(logging.Handler):
    """Format vclient log records with structured context."""

    def emit(self, record):
        extra = getattr(record, "extra", {})
        method = extra.get("method", "")
        url = extra.get("url", "")
        status = extra.get("status", "")
        print(f"{record.levelname} | {record.message} | {method} {url} {status}")


# Enable vclient logs
logger.enable("vclient")

# Attach the handler to the vclient stdlib logger
logging.getLogger("vclient").addHandler(StructuredHandler())
logging.getLogger("vclient").setLevel(logging.DEBUG)

# Output: DEBUG | Send request | GET /companies
# Output: DEBUG | Receive response | GET /companies 200

Log Levels

Level Events
DEBUG Request start, response received, 404 not found
INFO Client initialization, client close
WARNING Retries (rate limit, server error, network), validation errors (400), conflicts (409)
ERROR Authentication failures (401), authorization failures (403), retries exhausted

Default Company ID

Most services require a company_id. Set a default to avoid passing it to every service method.

from vclient import VClient, users_service

# Configure default company ID
client = VClient(
    base_url="https://api.valentina-noir.com",
    api_key="your-api-key",
    default_company_id="507f1f77bcf86cd799439011",
)

# Uses default company_id
users = client.users()
all_users = await users.list_all()

# Override for a specific call
other_users = client.users(company_id="other-company-id")

# Also works with factory functions
svc = users_service()  # Uses default
svc2 = users_service(company_id="explicit-id")  # Override

Warning

If no company_id is provided and no default is configured, a ValueError is raised.

Context Manager

For applications that need explicit resource management, use a context manager to ensure the HTTP client is closed automatically:

from vclient import VClient

# Async
async with VClient(
    base_url="https://api.valentina-noir.com",
    api_key="your-api-key",
    set_as_default=False,  # Don't register as default
) as client:
    companies = client.companies
    all_companies = await companies.list_all()
    # HTTP client is automatically closed when exiting the context
from vclient import SyncVClient

# Sync
with SyncVClient(
    base_url="https://api.valentina-noir.com",
    api_key="your-api-key",
    set_as_default=False,  # Don't register as default
) as client:
    companies = client.companies
    all_companies = companies.list_all()
    # HTTP client is automatically closed when exiting the context