mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-25 17:12:26 +02:00
Bumps the minor-and-patch group with 1 update in the /libs/sdk-py directory: [ty](https://github.com/astral-sh/ty). Updates `ty` from 0.0.23 to 0.0.33 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/astral-sh/ty/releases">ty's releases</a>.</em></p> <blockquote> <h2>0.0.33</h2> <h2>Release Notes</h2> <p>Released on 2026-04-28.</p> <h3>Notable changes</h3> <ul> <li> <p>ty now prefers the declared type of an annotated assignment in more situations (<a href="https://redirect.github.com/astral-sh/ruff/pull/24802">#24802</a>). Consider this example:</p> <pre lang="py"><code>from some_library import untyped_function <p>threshold: int | None = 0 result: str = untyped_function() </code></pre></p> <p>ty previously favored the <em>inferred</em> type of the right hand side expression when <code>threshold</code> and <code>result</code> were used. This is useful for <code>threshold</code>, as it allows something like <code>threshold += 1</code> to work without an error: we know that <code>threshold</code> could later become <code>None</code>, but <em>right now</em>, we see that it is an <code>int</code>. However, for <code>result</code>, the inferred type is <code>Unknown</code>. This is <em>not</em> a useful type and it can lead to false negatives. Starting with this release, ty will therefore prefer the declared type <em>if the inferred and declared types are mutually assignable</em>. In the above example, <code>threshold</code> will still be inferred as <code>int</code> (or rather <code>Literal[1]</code>), but <code>result</code> will now be inferred as <code>str</code>. If you previously added <code>cast</code>s to work around this behavior, you should be able to remove them after upgrading.</p> </li> </ul> <h3>Bug fixes</h3> <ul> <li>Fix reporting of annotation-only locals as unused (<a href="https://redirect.github.com/astral-sh/ruff/pull/24811">#24811</a>)</li> <li>Fix project and workspace selection (<a href="https://redirect.github.com/astral-sh/ruff/pull/24824">#24824</a>)</li> <li>Fix go-to definition for generic classes (<a href="https://redirect.github.com/astral-sh/ruff/pull/24714">#24714</a>)</li> <li>Fix receiver coloring for aliased decorators (<a href="https://redirect.github.com/astral-sh/ruff/pull/24884">#24884</a>)</li> </ul> <h3>LSP server</h3> <ul> <li>Add support for go-to definition in literal enum member inlay hints (<a href="https://redirect.github.com/astral-sh/ruff/pull/24792">#24792</a>)</li> <li>Add support for "baking" keyword argument inlay hints into the source code (<a href="https://redirect.github.com/astral-sh/ruff/pull/24667">#24667</a>)</li> <li>Don't allow inlay hint edits when introducing a non global scope symbol (<a href="https://redirect.github.com/astral-sh/ruff/pull/24797">#24797</a>)</li> <li>Omit semantic highlighting for unresolved symbols (<a href="https://redirect.github.com/astral-sh/ruff/pull/24718">#24718</a>)</li> </ul> <h3>Core type checking</h3> <ul> <li>Support narrowing with aliased conditional expressions (<a href="https://redirect.github.com/astral-sh/ruff/pull/24302">#24302</a>)</li> <li>Model short-circuiting control flow in Boolean expressions (<a href="https://redirect.github.com/astral-sh/ruff/pull/24458">#24458</a>)</li> <li>Handle <code>finally</code> blocks where all <code>try</code>/<code>except</code> blocks are terminal (<a href="https://redirect.github.com/astral-sh/ruff/pull/24882">#24882</a>)</li> <li>Detect invalid <code>ClassVar</code> vs instance-attribute overrides (<a href="https://redirect.github.com/astral-sh/ruff/pull/24767">#24767</a>)</li> <li>Emit diagnostic for invalid uses of <code>Unpack[...]</code> (<a href="https://redirect.github.com/astral-sh/ruff/pull/24868">#24868</a>)</li> <li>Infer lambda parameter types with <code>Callable</code> type context (<a href="https://redirect.github.com/astral-sh/ruff/pull/24317">#24317</a>)</li> <li>Support <code>**</code> unpacking of <code>TypedDict</code> in dict-literal assignments (<a href="https://redirect.github.com/astral-sh/ruff/pull/24703">#24703</a>)</li> <li>Support <code>Unpack[TypedDict]</code> in <code>**kwargs</code> signatures (<a href="https://redirect.github.com/astral-sh/ruff/pull/24653">#24653</a>)</li> <li>Treat <code>[*xs]</code> as an irrefutable pattern when matching on <code>Sequence</code> (<a href="https://redirect.github.com/astral-sh/ruff/pull/24787">#24787</a>)</li> <li>Improve generics solving for unions in invariant positions (<a href="https://redirect.github.com/astral-sh/ruff/pull/24698">#24698</a>)</li> <li>Improve generics solving for unions when matching against protocols (<a href="https://redirect.github.com/astral-sh/ruff/pull/24837">#24837</a>)</li> </ul> <h3>Diagnostics</h3> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/astral-sh/ty/blob/main/CHANGELOG.md">ty's changelog</a>.</em></p> <blockquote> <h2>0.0.33</h2> <p>Released on 2026-04-28.</p> <h3>Notable changes</h3> <ul> <li> <p>ty now prefers the declared type of an annotated assignment in more situations (<a href="https://redirect.github.com/astral-sh/ruff/pull/24802">#24802</a>). Consider this example:</p> <pre lang="py"><code>from some_library import untyped_function <p>threshold: int | None = 0 result: str = untyped_function() </code></pre></p> <p>ty previously favored the <em>inferred</em> type of the right hand side expression when <code>threshold</code> and <code>result</code> were used. This is useful for <code>threshold</code>, as it allows something like <code>threshold += 1</code> to work without an error: we know that <code>threshold</code> could later become <code>None</code>, but <em>right now</em>, we see that it is an <code>int</code>. However, for <code>result</code>, the inferred type is <code>Unknown</code>. This is <em>not</em> a useful type and it can lead to false negatives. Starting with this release, ty will therefore prefer the declared type <em>if the inferred and declared types are mutually assignable</em>. In the above example, <code>threshold</code> will still be inferred as <code>int</code> (or rather <code>Literal[1]</code>), but <code>result</code> will now be inferred as <code>str</code>. If you previously added <code>cast</code>s to work around this behavior, you should be able to remove them after upgrading.</p> </li> </ul> <h3>Bug fixes</h3> <ul> <li>Fix reporting of annotation-only locals as unused (<a href="https://redirect.github.com/astral-sh/ruff/pull/24811">#24811</a>)</li> <li>Fix project and workspace selection (<a href="https://redirect.github.com/astral-sh/ruff/pull/24824">#24824</a>)</li> <li>Fix go-to definition for generic classes (<a href="https://redirect.github.com/astral-sh/ruff/pull/24714">#24714</a>)</li> <li>Fix receiver coloring for aliased decorators (<a href="https://redirect.github.com/astral-sh/ruff/pull/24884">#24884</a>)</li> </ul> <h3>LSP server</h3> <ul> <li>Add support for go-to definition in literal enum member inlay hints (<a href="https://redirect.github.com/astral-sh/ruff/pull/24792">#24792</a>)</li> <li>Add support for "baking" keyword argument inlay hints into the source code (<a href="https://redirect.github.com/astral-sh/ruff/pull/24667">#24667</a>)</li> <li>Don't allow inlay hint edits when introducing a non global scope symbol (<a href="https://redirect.github.com/astral-sh/ruff/pull/24797">#24797</a>)</li> <li>Omit semantic highlighting for unresolved symbols (<a href="https://redirect.github.com/astral-sh/ruff/pull/24718">#24718</a>)</li> </ul> <h3>Core type checking</h3> <ul> <li>Support narrowing with aliased conditional expressions (<a href="https://redirect.github.com/astral-sh/ruff/pull/24302">#24302</a>)</li> <li>Model short-circuiting control flow in Boolean expressions (<a href="https://redirect.github.com/astral-sh/ruff/pull/24458">#24458</a>)</li> <li>Handle <code>finally</code> blocks where all <code>try</code>/<code>except</code> blocks are terminal (<a href="https://redirect.github.com/astral-sh/ruff/pull/24882">#24882</a>)</li> <li>Detect invalid <code>ClassVar</code> vs instance-attribute overrides (<a href="https://redirect.github.com/astral-sh/ruff/pull/24767">#24767</a>)</li> <li>Emit diagnostic for invalid uses of <code>Unpack[...]</code> (<a href="https://redirect.github.com/astral-sh/ruff/pull/24868">#24868</a>)</li> <li>Infer lambda parameter types with <code>Callable</code> type context (<a href="https://redirect.github.com/astral-sh/ruff/pull/24317">#24317</a>)</li> <li>Support <code>**</code> unpacking of <code>TypedDict</code> in dict-literal assignments (<a href="https://redirect.github.com/astral-sh/ruff/pull/24703">#24703</a>)</li> <li>Support <code>Unpack[TypedDict]</code> in <code>**kwargs</code> signatures (<a href="https://redirect.github.com/astral-sh/ruff/pull/24653">#24653</a>)</li> <li>Treat <code>[*xs]</code> as an irrefutable pattern when matching on <code>Sequence</code> (<a href="https://redirect.github.com/astral-sh/ruff/pull/24787">#24787</a>)</li> <li>Improve generics solving for unions in invariant positions (<a href="https://redirect.github.com/astral-sh/ruff/pull/24698">#24698</a>)</li> <li>Improve generics solving for unions when matching against protocols (<a href="https://redirect.github.com/astral-sh/ruff/pull/24837">#24837</a>)</li> </ul> <h3>Diagnostics</h3> <ul> <li>Add error context to <code>invalid-return-type</code> diagnostics, <code>invalid-yield</code> diagnostics, attribute assignment diagnostics (<a href="https://redirect.github.com/astral-sh/ruff/pull/24770">#24770</a>, <a href="https://redirect.github.com/astral-sh/ruff/pull/24771">#24771</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/astral-sh/ty/commit/c512d8425418a2170e92aa7fbbd70952d4e04118"><code>c512d84</code></a> Bump version to 0.0.33 (<a href="https://redirect.github.com/astral-sh/ty/issues/3368">#3368</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/4cd7b334b90eba09042700e7b654044c2d6bcd15"><code>4cd7b33</code></a> Upgrade Depot runners from macOS 14 to 15 (<a href="https://redirect.github.com/astral-sh/ty/issues/3363">#3363</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/c78b8324515bf15a662f61e1d74fd85484d67e8c"><code>c78b832</code></a> Update rui314/setup-mold digest to 9c9c13b (<a href="https://redirect.github.com/astral-sh/ty/issues/3342">#3342</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/dea338134aef96d741a48886e414f622dbf10426"><code>dea3381</code></a> Update actions/cache action to v5.0.5 (<a href="https://redirect.github.com/astral-sh/ty/issues/3343">#3343</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/d451af477bd5517e132609c3b016799d697f4182"><code>d451af4</code></a> update typing-features and faqs (<a href="https://redirect.github.com/astral-sh/ty/issues/3335">#3335</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/052d70bc1a7457f50c5acbafe8de7a846f7d5e77"><code>052d70b</code></a> Update prek dependencies (<a href="https://redirect.github.com/astral-sh/ty/issues/3344">#3344</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/66b5e878163ce4ab4810a45a2679a98011932b8b"><code>66b5e87</code></a> Update astral-sh/setup-uv action to v8.1.0 (<a href="https://redirect.github.com/astral-sh/ty/issues/3345">#3345</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/7ec6712a6f02d0255de4c7f0066d5bb23987a9bb"><code>7ec6712</code></a> Add a 'Diagnostics improvements' section to the changelogs (<a href="https://redirect.github.com/astral-sh/ty/issues/3309">#3309</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/978dfdb38dfb568943f73779d769a965c1d5a397"><code>978dfdb</code></a> Add version metadata publishing to the release process (<a href="https://redirect.github.com/astral-sh/ty/issues/3292">#3292</a>)</li> <li><a href="https://github.com/astral-sh/ty/commit/4d1e1fc57ca8bfdcbcee513ba92135d2932eb279"><code>4d1e1fc</code></a> Bump version to 0.0.32 (<a href="https://redirect.github.com/astral-sh/ty/issues/3302">#3302</a>)</li> <li>Additional commits viewable in <a href="https://github.com/astral-sh/ty/compare/0.0.23...0.0.33">compare view</a></li> </ul> </details> <br /> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: John Kennedy <65985482+jkennedyvz@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
219 lines
7.1 KiB
Python
219 lines
7.1 KiB
Python
"""Shared utility functions for async and sync clients."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import functools
|
|
import os
|
|
import re
|
|
from collections.abc import Mapping
|
|
from datetime import tzinfo
|
|
from typing import TYPE_CHECKING, Any, cast
|
|
from urllib.parse import urlparse
|
|
|
|
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())
|
|
|
|
|
|
def _get_api_key(api_key: str | None = NOT_PROVIDED) -> str | None:
|
|
"""Get the API key from the environment.
|
|
Precedence:
|
|
1. explicit string argument
|
|
2. LANGGRAPH_API_KEY (if api_key not provided)
|
|
3. LANGSMITH_API_KEY (if api_key not provided)
|
|
4. LANGCHAIN_API_KEY (if api_key not provided)
|
|
|
|
Args:
|
|
api_key: The API key to use. Can be:
|
|
- A string: use this exact API key
|
|
- None: explicitly skip loading from environment
|
|
- NOT_PROVIDED (default): auto-load from environment variables
|
|
"""
|
|
if isinstance(api_key, str):
|
|
return api_key
|
|
if api_key is NOT_PROVIDED:
|
|
# api_key is not explicitly provided, try to load from environment
|
|
for prefix in ["LANGGRAPH", "LANGSMITH", "LANGCHAIN"]:
|
|
if env := os.getenv(f"{prefix}_API_KEY"):
|
|
return env.strip().strip('"').strip("'")
|
|
# api_key is explicitly None, don't load from environment
|
|
return None
|
|
|
|
|
|
def _get_headers(
|
|
api_key: str | None,
|
|
custom_headers: Mapping[str, str] | None,
|
|
) -> dict[str, str]:
|
|
"""Combine api_key and custom user-provided headers."""
|
|
custom_headers = custom_headers or {}
|
|
for header in RESERVED_HEADERS:
|
|
if header in custom_headers:
|
|
raise ValueError(f"Cannot set reserved header '{header}'")
|
|
|
|
headers = {
|
|
"User-Agent": f"langgraph-sdk-py/{langgraph_sdk.__version__}",
|
|
**custom_headers,
|
|
}
|
|
resolved_api_key = _get_api_key(api_key)
|
|
if resolved_api_key:
|
|
headers["x-api-key"] = resolved_api_key
|
|
|
|
return headers
|
|
|
|
|
|
def _orjson_default(obj: Any) -> Any:
|
|
is_class = isinstance(obj, type)
|
|
if hasattr(obj, "model_dump") and callable(obj.model_dump):
|
|
if is_class:
|
|
raise TypeError(
|
|
f"Cannot JSON-serialize type object: {obj!r}. Did you mean to pass an instance of the object instead?"
|
|
f"\nReceived type: {obj!r}"
|
|
)
|
|
return obj.model_dump()
|
|
elif hasattr(obj, "dict") and callable(obj.dict):
|
|
if is_class:
|
|
raise TypeError(
|
|
f"Cannot JSON-serialize type object: {obj!r}. Did you mean to pass an instance of the object instead?"
|
|
f"\nReceived type: {obj!r}"
|
|
)
|
|
return obj.dict()
|
|
elif isinstance(obj, (set, frozenset)):
|
|
return list(obj)
|
|
else:
|
|
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
|
|
|
|
|
|
# Compiled regex pattern for extracting run metadata from Content-Location header
|
|
_RUN_METADATA_PATTERN = re.compile(
|
|
r"(\/threads\/(?P<thread_id>.+))?\/runs\/(?P<run_id>.+)"
|
|
)
|
|
|
|
|
|
def _get_run_metadata_from_response(
|
|
response: httpx.Response,
|
|
) -> RunCreateMetadata | None:
|
|
"""Extract run metadata from the response headers."""
|
|
if (content_location := response.headers.get("Content-Location")) and (
|
|
match := _RUN_METADATA_PATTERN.search(content_location)
|
|
):
|
|
return RunCreateMetadata(
|
|
run_id=match.group("run_id"),
|
|
thread_id=match.group("thread_id") or None,
|
|
)
|
|
|
|
return None
|
|
|
|
|
|
def _sse_to_v2_dict(event: str, data: Any) -> dict[str, Any] | None:
|
|
"""Convert an SSE event+data pair into a v2 stream part dict.
|
|
|
|
Returns None for ``end`` events (signals end of stream).
|
|
"""
|
|
if event == "end":
|
|
return None
|
|
parts = event.split("|")
|
|
event_type = parts[0]
|
|
ns = parts[1:] if len(parts) > 1 else []
|
|
result: dict[str, Any] = {"type": event_type, "ns": ns, "data": data}
|
|
if event_type == "values" and isinstance(data, dict):
|
|
result["interrupts"] = data.pop("__interrupt__", [])
|
|
else:
|
|
result["interrupts"] = []
|
|
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
|
|
key = getattr(tz, "key", None)
|
|
if isinstance(key, str):
|
|
return key
|
|
# 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 _default_port(scheme: str) -> int:
|
|
return 443 if scheme == "https" else 80
|
|
|
|
|
|
def _validate_reconnect_location(base_url: httpx.URL, location: str) -> str:
|
|
"""Validate that a reconnect Location URL is same-origin as the base URL.
|
|
|
|
Raises ValueError if the Location header points to a different origin
|
|
(scheme + host + port), which would leak credentials to an external server.
|
|
"""
|
|
parsed = urlparse(location)
|
|
# Relative URLs are safe — they resolve against the base
|
|
if not parsed.scheme and not parsed.netloc:
|
|
return location
|
|
# Compare origin components (normalize default ports to avoid mismatches)
|
|
base_scheme = str(base_url.scheme)
|
|
base_origin = (
|
|
base_scheme,
|
|
str(base_url.host),
|
|
base_url.port or _default_port(base_scheme),
|
|
)
|
|
loc_origin = (
|
|
parsed.scheme,
|
|
parsed.hostname or "",
|
|
parsed.port or _default_port(parsed.scheme),
|
|
)
|
|
if base_origin != loc_origin:
|
|
raise ValueError(
|
|
f"Refusing to follow cross-origin reconnect Location: {location!r} "
|
|
f"(origin {loc_origin}) does not match base URL origin {base_origin}"
|
|
)
|
|
return location
|
|
|
|
|
|
def _provided_vals(d: Mapping[str, Any]) -> dict[str, Any]:
|
|
return {k: v for k, v in d.items() if v is not None}
|
|
|
|
|
|
_registered_transports: list[httpx.ASGITransport] = []
|
|
|
|
|
|
# Do not move; this is used in the server.
|
|
def configure_loopback_transports(app: Any) -> None:
|
|
for transport in _registered_transports:
|
|
transport.app = app
|
|
|
|
|
|
@functools.lru_cache(maxsize=1)
|
|
def get_asgi_transport() -> type[httpx.ASGITransport]:
|
|
try:
|
|
from langgraph_api import asgi_transport # ty: ignore[unresolved-import]
|
|
|
|
return asgi_transport.ASGITransport
|
|
except ImportError:
|
|
# Older versions of the server
|
|
return httpx.ASGITransport
|