From acae5e23b00ff19d61bd9d3fa352765c76c8ae2c Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Tue, 10 Mar 2026 17:38:02 -0700 Subject: [PATCH] chore(sdk-py): cron tz support (#7108) --- libs/sdk-py/langgraph_sdk/__init__.py | 2 +- libs/sdk-py/langgraph_sdk/_async/cron.py | 18 +++++++--- .../sdk-py/langgraph_sdk/_shared/utilities.py | 35 ++++++++++++++++++- libs/sdk-py/langgraph_sdk/_sync/cron.py | 18 +++++++--- libs/sdk-py/langgraph_sdk/schema.py | 5 +++ 5 files changed, 68 insertions(+), 10 deletions(-) diff --git a/libs/sdk-py/langgraph_sdk/__init__.py b/libs/sdk-py/langgraph_sdk/__init__.py index 8c3b7e6ab..022b85e16 100644 --- a/libs/sdk-py/langgraph_sdk/__init__.py +++ b/libs/sdk-py/langgraph_sdk/__init__.py @@ -3,6 +3,6 @@ from langgraph_sdk.client import get_client, get_sync_client from langgraph_sdk.encryption import Encryption from langgraph_sdk.encryption.types import EncryptionContext -__version__ = "0.3.10" +__version__ = "0.3.11" __all__ = ["Auth", "Encryption", "EncryptionContext", "get_client", "get_sync_client"] diff --git a/libs/sdk-py/langgraph_sdk/_async/cron.py b/libs/sdk-py/langgraph_sdk/_async/cron.py index dd3f89047..8ffd73a32 100644 --- a/libs/sdk-py/langgraph_sdk/_async/cron.py +++ b/libs/sdk-py/langgraph_sdk/_async/cron.py @@ -4,10 +4,11 @@ from __future__ import annotations import warnings from collections.abc import Mapping, Sequence -from datetime import datetime +from datetime import datetime, tzinfo from typing import Any from langgraph_sdk._async.http import HttpClient +from langgraph_sdk._shared.utilities import _resolve_timezone from langgraph_sdk.schema import ( All, Config, @@ -70,6 +71,7 @@ class CronClient: multitask_strategy: str | None = None, end_time: datetime | None = None, enabled: bool | None = None, + timezone: str | tzinfo | None = None, stream_mode: StreamMode | Sequence[StreamMode] | None = None, stream_subgraphs: bool | None = None, stream_resumable: bool | None = None, @@ -84,7 +86,7 @@ class CronClient: assistant_id: The assistant ID or graph name to use for the cron job. If using graph name, will default to first assistant created from that graph. schedule: The cron schedule to execute this job on. - Schedules are interpreted in UTC. + Schedules are interpreted in UTC unless a timezone is specified. input: The input to the graph. metadata: Metadata to assign to the cron job runs. config: The configuration for the assistant. @@ -100,6 +102,7 @@ class CronClient: Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'. end_time: The time to stop running the cron job. If not provided, the cron job will run indefinitely. enabled: Whether the cron job is enabled or not. + timezone: IANA timezone for the cron schedule. Accepts a string (e.g. 'America/New_York') or a ``datetime.tzinfo`` instance (e.g. ``ZoneInfo("America/New_York")``). stream_mode: The stream mode(s) to use. stream_subgraphs: Whether to stream output from subgraphs. stream_resumable: Whether to persist the stream chunks in order to resume the stream later. @@ -152,6 +155,7 @@ class CronClient: "webhook": webhook, "end_time": end_time.isoformat() if end_time else None, "enabled": enabled, + "timezone": _resolve_timezone(timezone), "stream_mode": stream_mode, "stream_subgraphs": stream_subgraphs, "stream_resumable": stream_resumable, @@ -184,6 +188,7 @@ class CronClient: multitask_strategy: str | None = None, end_time: datetime | None = None, enabled: bool | None = None, + timezone: str | tzinfo | None = None, stream_mode: StreamMode | Sequence[StreamMode] | None = None, stream_subgraphs: bool | None = None, stream_resumable: bool | None = None, @@ -197,7 +202,7 @@ class CronClient: assistant_id: The assistant ID or graph name to use for the cron job. If using graph name, will default to first assistant created from that graph. schedule: The cron schedule to execute this job on. - Schedules are interpreted in UTC. + Schedules are interpreted in UTC unless a timezone is specified. input: The input to the graph. metadata: Metadata to assign to the cron job runs. config: The configuration for the assistant. @@ -215,6 +220,7 @@ class CronClient: Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'. end_time: The time to stop running the cron job. If not provided, the cron job will run indefinitely. enabled: Whether the cron job is enabled or not. + timezone: IANA timezone for the cron schedule. Accepts a string (e.g. 'America/New_York') or a ``datetime.tzinfo`` instance (e.g. ``ZoneInfo("America/New_York")``). stream_mode: The stream mode(s) to use. stream_subgraphs: Whether to stream output from subgraphs. stream_resumable: Whether to persist the stream chunks in order to resume the stream later. @@ -268,6 +274,7 @@ class CronClient: "on_run_completed": on_run_completed, "end_time": end_time.isoformat() if end_time else None, "enabled": enabled, + "timezone": _resolve_timezone(timezone), "stream_mode": stream_mode, "stream_subgraphs": stream_subgraphs, "stream_resumable": stream_resumable, @@ -324,6 +331,7 @@ class CronClient: interrupt_after: All | list[str] | None = None, on_run_completed: OnCompletionBehavior | None = None, enabled: bool | None = None, + timezone: str | tzinfo | None = None, stream_mode: StreamMode | Sequence[StreamMode] | None = None, stream_subgraphs: bool | None = None, stream_resumable: bool | None = None, @@ -336,7 +344,7 @@ class CronClient: Args: cron_id: The cron ID to update. schedule: The cron schedule to execute this job on. - Schedules are interpreted in UTC. + Schedules are interpreted in UTC unless a timezone is specified. end_time: The end date to stop running the cron. input: The input to the graph. metadata: Metadata to assign to the cron job runs. @@ -350,6 +358,7 @@ class CronClient: after execution. 'keep' creates a new thread for each execution but does not clean them up. enabled: Enable or disable the cron job. + timezone: IANA timezone for the cron schedule. Accepts a string (e.g. 'America/New_York') or a ``datetime.tzinfo`` instance (e.g. ``ZoneInfo("America/New_York")``). stream_mode: The stream mode(s) to use. stream_subgraphs: Whether to stream output from subgraphs. stream_resumable: Whether to persist the stream chunks in order to resume the stream later. @@ -384,6 +393,7 @@ class CronClient: "interrupt_after": interrupt_after, "on_run_completed": on_run_completed, "enabled": enabled, + "timezone": _resolve_timezone(timezone), "stream_mode": stream_mode, "stream_subgraphs": stream_subgraphs, "stream_resumable": stream_resumable, diff --git a/libs/sdk-py/langgraph_sdk/_shared/utilities.py b/libs/sdk-py/langgraph_sdk/_shared/utilities.py index 54d55580c..4441bd255 100644 --- a/libs/sdk-py/langgraph_sdk/_shared/utilities.py +++ b/libs/sdk-py/langgraph_sdk/_shared/utilities.py @@ -6,13 +6,17 @@ import functools import os import re from collections.abc import Mapping -from typing import Any, cast +from datetime import tzinfo +from typing import TYPE_CHECKING, Any, cast import httpx import langgraph_sdk from langgraph_sdk.schema import RunCreateMetadata +if TYPE_CHECKING: + from zoneinfo import ZoneInfo + RESERVED_HEADERS = ("x-api-key",) NOT_PROVIDED = cast(None, object()) @@ -125,6 +129,35 @@ def _sse_to_v2_dict(event: str, data: Any) -> dict[str, Any] | None: return result +def _resolve_timezone(tz: str | tzinfo | ZoneInfo | None) -> str | None: + """Convert a timezone argument to an IANA timezone string. + + Accepts: + - A string (returned as-is, assumed to be an IANA timezone name) + - A ``datetime.tzinfo`` instance (e.g. ``zoneinfo.ZoneInfo("America/New_York")``, + ``datetime.timezone.utc``). The ``key`` attribute is used if available, + otherwise ``tzname(None)`` is used. + - ``None`` (returned as ``None``) + """ + if tz is None or isinstance(tz, str): + return tz + if isinstance(tz, tzinfo): + # ZoneInfo objects have a .key attribute with the IANA name + if hasattr(tz, "key"): + return tz.key # type: ignore[union-attr] + # Fall back to tzname for fixed-offset timezones like datetime.timezone.utc + name = tz.tzname(None) + if name is not None: + return name + raise ValueError( + f"Cannot determine timezone name from {tz!r}. " + "Use a zoneinfo.ZoneInfo instance or pass a string like 'America/New_York'." + ) + raise TypeError( + f"Expected str, datetime.tzinfo, or None for timezone, got {type(tz).__name__}" + ) + + def _provided_vals(d: Mapping[str, Any]) -> dict[str, Any]: return {k: v for k, v in d.items() if v is not None} diff --git a/libs/sdk-py/langgraph_sdk/_sync/cron.py b/libs/sdk-py/langgraph_sdk/_sync/cron.py index 2860a95cf..f4a66d435 100644 --- a/libs/sdk-py/langgraph_sdk/_sync/cron.py +++ b/libs/sdk-py/langgraph_sdk/_sync/cron.py @@ -4,9 +4,10 @@ from __future__ import annotations import warnings from collections.abc import Mapping, Sequence -from datetime import datetime +from datetime import datetime, tzinfo from typing import Any +from langgraph_sdk._shared.utilities import _resolve_timezone from langgraph_sdk._sync.http import SyncHttpClient from langgraph_sdk.schema import ( All, @@ -64,6 +65,7 @@ class SyncCronClient: multitask_strategy: str | None = None, end_time: datetime | None = None, enabled: bool | None = None, + timezone: str | tzinfo | None = None, stream_mode: StreamMode | Sequence[StreamMode] | None = None, stream_subgraphs: bool | None = None, stream_resumable: bool | None = None, @@ -78,7 +80,7 @@ class SyncCronClient: assistant_id: The assistant ID or graph name to use for the cron job. If using graph name, will default to first assistant created from that graph. schedule: The cron schedule to execute this job on. - Schedules are interpreted in UTC. + Schedules are interpreted in UTC unless a timezone is specified. input: The input to the graph. metadata: Metadata to assign to the cron job runs. config: The configuration for the assistant. @@ -92,6 +94,7 @@ class SyncCronClient: Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'. end_time: The time to stop running the cron job. If not provided, the cron job will run indefinitely. enabled: Whether the cron job is enabled. By default, it is considered enabled. + timezone: IANA timezone for the cron schedule. Accepts a string (e.g. 'America/New_York') or a ``datetime.tzinfo`` instance (e.g. ``ZoneInfo("America/New_York")``). stream_mode: The stream mode(s) to use. stream_subgraphs: Whether to stream output from subgraphs. stream_resumable: Whether to persist the stream chunks in order to resume the stream later. @@ -144,6 +147,7 @@ class SyncCronClient: "multitask_strategy": multitask_strategy, "end_time": end_time.isoformat() if end_time else None, "enabled": enabled, + "timezone": _resolve_timezone(timezone), "stream_mode": stream_mode, "stream_subgraphs": stream_subgraphs, "stream_resumable": stream_resumable, @@ -174,6 +178,7 @@ class SyncCronClient: multitask_strategy: str | None = None, end_time: datetime | None = None, enabled: bool | None = None, + timezone: str | tzinfo | None = None, stream_mode: StreamMode | Sequence[StreamMode] | None = None, stream_subgraphs: bool | None = None, stream_resumable: bool | None = None, @@ -187,7 +192,7 @@ class SyncCronClient: assistant_id: The assistant ID or graph name to use for the cron job. If using graph name, will default to first assistant created from that graph. schedule: The cron schedule to execute this job on. - Schedules are interpreted in UTC. + Schedules are interpreted in UTC unless a timezone is specified. input: The input to the graph. metadata: Metadata to assign to the cron job runs. config: The configuration for the assistant. @@ -205,6 +210,7 @@ class SyncCronClient: Must be one of 'reject', 'interrupt', 'rollback', or 'enqueue'. end_time: The time to stop running the cron job. If not provided, the cron job will run indefinitely. enabled: Whether the cron job is enabled. By default, it is considered enabled. + timezone: IANA timezone for the cron schedule. Accepts a string (e.g. 'America/New_York') or a ``datetime.tzinfo`` instance (e.g. ``ZoneInfo("America/New_York")``). stream_mode: The stream mode(s) to use. stream_subgraphs: Whether to stream output from subgraphs. stream_resumable: Whether to persist the stream chunks in order to resume the stream later. @@ -259,6 +265,7 @@ class SyncCronClient: "multitask_strategy": multitask_strategy, "end_time": end_time.isoformat() if end_time else None, "enabled": enabled, + "timezone": _resolve_timezone(timezone), "stream_mode": stream_mode, "stream_subgraphs": stream_subgraphs, "stream_resumable": stream_resumable, @@ -313,6 +320,7 @@ class SyncCronClient: interrupt_after: All | list[str] | None = None, on_run_completed: OnCompletionBehavior | None = None, enabled: bool | None = None, + timezone: str | tzinfo | None = None, stream_mode: StreamMode | Sequence[StreamMode] | None = None, stream_subgraphs: bool | None = None, stream_resumable: bool | None = None, @@ -325,7 +333,7 @@ class SyncCronClient: Args: cron_id: The cron ID to update. schedule: The cron schedule to execute this job on. - Schedules are interpreted in UTC. + Schedules are interpreted in UTC unless a timezone is specified. end_time: The end date to stop running the cron. input: The input to the graph. metadata: Metadata to assign to the cron job runs. @@ -339,6 +347,7 @@ class SyncCronClient: after execution. 'keep' creates a new thread for each execution but does not clean them up. enabled: Enable or disable the cron job. + timezone: IANA timezone for the cron schedule. Accepts a string (e.g. 'America/New_York') or a ``datetime.tzinfo`` instance (e.g. ``ZoneInfo("America/New_York")``). stream_mode: The stream mode(s) to use. stream_subgraphs: Whether to stream output from subgraphs. stream_resumable: Whether to persist the stream chunks in order to resume the stream later. @@ -373,6 +382,7 @@ class SyncCronClient: "interrupt_after": interrupt_after, "on_run_completed": on_run_completed, "enabled": enabled, + "timezone": _resolve_timezone(timezone), "stream_mode": stream_mode, "stream_subgraphs": stream_subgraphs, "stream_resumable": stream_resumable, diff --git a/libs/sdk-py/langgraph_sdk/schema.py b/libs/sdk-py/langgraph_sdk/schema.py index 8f9a53149..6fffcec34 100644 --- a/libs/sdk-py/langgraph_sdk/schema.py +++ b/libs/sdk-py/langgraph_sdk/schema.py @@ -385,6 +385,8 @@ class Cron(TypedDict): """The end date to stop running the cron.""" schedule: str """The schedule to run, cron format.""" + timezone: str | None + """IANA timezone for the cron schedule (e.g. 'America/New_York'). Defaults to null, which is treated as UTC.""" created_at: datetime """The time the cron was created.""" updated_at: datetime @@ -406,6 +408,8 @@ class CronUpdate(TypedDict, total=False): schedule: str """The cron schedule to execute this job on.""" + timezone: str + """IANA timezone for the cron schedule (e.g. 'America/New_York').""" end_time: datetime """The end date to stop running the cron.""" input: Input @@ -482,6 +486,7 @@ CronSelectField = Literal[ "thread_id", "end_time", "schedule", + "timezone", "created_at", "updated_at", "user_id",