diff --git a/sdks/python/CHANGELOG.md b/sdks/python/CHANGELOG.md index 00f232a091..95338dccdc 100644 --- a/sdks/python/CHANGELOG.md +++ b/sdks/python/CHANGELOG.md @@ -13,10 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.23.4] - 2026-02-13 -### Changed - -- Fixes cases where raising exception classes or exceptions with no message would cause the whole error including stack trace to be converted to an empty string. -- When an error is raised because a workflow has no tasks it now includes the workflows name. +#### Changed +- Python SDK `ClientConfig` token validation now raises `HatchetConfigurationError` instead of `ValueError` for invalid or missing configuration. +- Minor field validators updated to improve clarity and robustness. +- Updated package patch version to include these non-breaking improvements. ## [1.23.3] - 2026-02-12 diff --git a/sdks/python/hatchet_sdk/config.py b/sdks/python/hatchet_sdk/config.py index e49d2c9eac..f3363fe7f5 100644 --- a/sdks/python/hatchet_sdk/config.py +++ b/sdks/python/hatchet_sdk/config.py @@ -6,6 +6,7 @@ from pydantic import Field, field_validator, model_validator from pydantic_settings import BaseSettings, SettingsConfigDict +from hatchet_sdk.exceptions import HatchetConfigurationError from hatchet_sdk.token import get_addresses_from_jwt, get_tenant_id_from_jwt from hatchet_sdk.utils.opentelemetry import OTelAttribute @@ -48,7 +49,6 @@ class HealthcheckConfig(BaseSettings): def validate_event_loop_block_threshold_seconds( cls, value: timedelta | int | float | str ) -> timedelta: - # Settings env vars are strings; interpret as seconds. if isinstance(value, timedelta): return value @@ -56,7 +56,6 @@ def validate_event_loop_block_threshold_seconds( return timedelta(seconds=float(value)) v = value.strip() - # Allow a small convenience suffix, but keep "seconds" as the contract. if v.endswith("s"): v = v[:-1].strip() @@ -138,11 +137,15 @@ class ClientConfig(BaseSettings): @model_validator(mode="after") def validate_token_and_tenant(self) -> "ClientConfig": if not self.token: - raise ValueError("Token must be set") + raise HatchetConfigurationError( + "HATCHET_CLIENT_TOKEN must be set. " + "Provide it via environment variable or pass token=... to ClientConfig." + ) if not self.token.startswith("ey"): - raise ValueError( - f"Token must be a valid JWT. Hint: These are the first few characters of the token provided: {self.token[:5]}" + raise HatchetConfigurationError( + "HATCHET_CLIENT_TOKEN must be a valid JWT. " + f"Received token starting with: '{self.token[:5]}...'" ) if not self.tenant_id: @@ -152,8 +155,6 @@ def validate_token_and_tenant(self) -> "ClientConfig": @model_validator(mode="after") def validate_addresses(self) -> "ClientConfig": - ## If nothing is set, read from the token - ## If either is set, override what's in the JWT server_url_from_jwt, grpc_broadcast_address_from_jwt = get_addresses_from_jwt( self.token ) diff --git a/sdks/python/hatchet_sdk/exceptions.py b/sdks/python/hatchet_sdk/exceptions.py index 3ecc0c3e66..31d48cc762 100644 --- a/sdks/python/hatchet_sdk/exceptions.py +++ b/sdks/python/hatchet_sdk/exceptions.py @@ -3,6 +3,10 @@ from typing import cast +class HatchetConfigurationError(ValueError): + """Raised when required configuration is missing or invalid.""" + + class InvalidDependencyError(Exception): pass