Compare commits

..
Author SHA1 Message Date
Parker J. RuleandGitHub 172238b2d5 fix(sdk-py): allow reset of config/context in assistants update (#6862)
This distinguishes an empty `config`/`context` (`{}`) from a null
`config`/`context`, and therefore allows resetting these fields.

(Easiest to test this E2E on the server side; will create a separate PR
for that.)
2026-02-18 18:48:07 +00:00
William FHandGitHub 095da17833 chore: state_updated_at sort by (#6857) 2026-02-18 10:41:23 -08:00
16 changed files with 66 additions and 39 deletions
@@ -62,3 +62,4 @@ target-version = "py310"
name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = true
-3
View File
@@ -24,9 +24,6 @@
"@langchain/core": "^1.1.24",
"@langchain/langgraph": "^1.1.4"
},
"resolutions": {
"js-yaml": "^4.1.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^10.0.1",
+26 -1
View File
@@ -1351,6 +1351,13 @@ anymatch@^3.1.3:
normalize-path "^3.0.0"
picomatch "^2.0.4"
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
dependencies:
sprintf-js "~1.0.2"
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
@@ -2298,6 +2305,11 @@ espree@^11.1.0:
acorn-jsx "^5.3.2"
eslint-visitor-keys "^5.0.0"
esprima@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
esquery@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d"
@@ -3520,7 +3532,15 @@ js-tokens@^4.0.0:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
js-yaml@^3.13.1, js-yaml@^4.1.1:
js-yaml@^3.13.1:
version "3.14.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
js-yaml@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b"
integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==
@@ -4314,6 +4334,11 @@ source-map@^0.6.0, source-map@^0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
stack-utils@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f"
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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.6"
__version__ = "0.3.7"
__all__ = ["Auth", "Encryption", "EncryptionContext", "get_client", "get_sync_client"]
@@ -290,7 +290,7 @@ class AssistantsClient:
"""
get_params = {"recurse": recurse}
if params:
get_params = {**get_params, **params}
get_params = {**get_params, **dict(params)}
if namespace is not None:
return await self.http.get(
f"/assistants/{assistant_id}/subgraphs/{namespace}",
@@ -425,9 +425,9 @@ class AssistantsClient:
payload: dict[str, Any] = {}
if graph_id:
payload["graph_id"] = graph_id
if config:
if config is not None:
payload["config"] = config
if context:
if context is not None:
payload["context"] = context
if metadata:
payload["metadata"] = metadata
+2 -2
View File
@@ -110,7 +110,7 @@ def get_client(
if url is None:
url = "http://api"
if os.environ.get("__LANGGRAPH_DEFER_LOOPBACK_TRANSPORT") == "true":
transport = get_asgi_transport()(app=None, root_path="/noauth")
transport = get_asgi_transport()(app=None, root_path="/noauth") # type: ignore[invalid-argument-type]
_registered_transports.append(transport)
else:
try:
@@ -122,7 +122,7 @@ def get_client(
"Failed to connect to in-process LangGraph server. Deferring configuration.",
exc_info=True,
)
transport = get_asgi_transport()(app=None, root_path="/noauth")
transport = get_asgi_transport()(app=None, root_path="/noauth") # type: ignore[invalid-argument-type]
_registered_transports.append(transport)
if transport is None:
+8 -7
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import builtins
import warnings
from collections.abc import AsyncIterator, Callable, Mapping, Sequence
from typing import Any, overload
@@ -507,11 +508,11 @@ class RunsClient:
async def create_batch(
self,
payloads: list[RunCreate],
payloads: builtins.list[RunCreate],
*,
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> list[Run]:
) -> builtins.list[Run]:
"""Create a batch of stateless background runs."""
def filter_payload(payload: RunCreate):
@@ -547,7 +548,7 @@ class RunsClient:
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
) -> list[dict] | dict[str, Any]: ...
) -> builtins.list[dict] | dict[str, Any]: ...
@overload
async def wait(
@@ -572,7 +573,7 @@ class RunsClient:
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
) -> list[dict] | dict[str, Any]: ...
) -> builtins.list[dict] | dict[str, Any]: ...
async def wait(
self,
@@ -600,7 +601,7 @@ class RunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
) -> list[dict] | dict[str, Any]:
) -> builtins.list[dict] | dict[str, Any]:
"""Create a run, wait until it finishes and return the final state.
Args:
@@ -751,10 +752,10 @@ class RunsClient:
limit: int = 10,
offset: int = 0,
status: RunStatus | None = None,
select: list[RunSelectField] | None = None,
select: builtins.list[RunSelectField] | None = None,
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> list[Run]:
) -> builtins.list[Run]:
"""List runs.
Args:
+1 -1
View File
@@ -138,7 +138,7 @@ class StoreClient:
if refresh_ttl is not None:
get_params["refresh_ttl"] = refresh_ttl
if params:
get_params = {**get_params, **params}
get_params = {**get_params, **dict(params)}
return await self.http.get("/store/items", params=get_params, headers=headers)
async def delete_item(
+2 -2
View File
@@ -543,7 +543,7 @@ class ThreadsClient:
elif checkpoint_id:
get_params = {"subgraphs": subgraphs}
if params:
get_params = {**get_params, **params}
get_params = {**get_params, **dict(params)}
return await self.http.get(
f"/threads/{thread_id}/state/{checkpoint_id}",
params=get_params,
@@ -552,7 +552,7 @@ class ThreadsClient:
else:
get_params = {"subgraphs": subgraphs}
if params:
get_params = {**get_params, **params}
get_params = {**get_params, **dict(params)}
return await self.http.get(
f"/threads/{thread_id}/state",
params=get_params,
@@ -294,7 +294,7 @@ class SyncAssistantsClient:
"""
get_params = {"recurse": recurse}
if params:
get_params = {**get_params, **params}
get_params = {**get_params, **dict(params)}
if namespace is not None:
return self.http.get(
f"/assistants/{assistant_id}/subgraphs/{namespace}",
@@ -427,9 +427,9 @@ class SyncAssistantsClient:
payload: dict[str, Any] = {}
if graph_id:
payload["graph_id"] = graph_id
if config:
if config is not None:
payload["config"] = config
if context:
if context is not None:
payload["context"] = context
if metadata:
payload["metadata"] = metadata
+8 -7
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import builtins
import warnings
from collections.abc import Callable, Iterator, Mapping, Sequence
from typing import Any, overload
@@ -503,11 +504,11 @@ class SyncRunsClient:
def create_batch(
self,
payloads: list[RunCreate],
payloads: builtins.list[RunCreate],
*,
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> list[Run]:
) -> builtins.list[Run]:
"""Create a batch of stateless background runs."""
def filter_payload(payload: RunCreate):
@@ -543,7 +544,7 @@ class SyncRunsClient:
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
) -> list[dict] | dict[str, Any]: ...
) -> builtins.list[dict] | dict[str, Any]: ...
@overload
def wait(
@@ -568,7 +569,7 @@ class SyncRunsClient:
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
) -> list[dict] | dict[str, Any]: ...
) -> builtins.list[dict] | dict[str, Any]: ...
def wait(
self,
@@ -596,7 +597,7 @@ class SyncRunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
) -> list[dict] | dict[str, Any]:
) -> builtins.list[dict] | dict[str, Any]:
"""Create a run, wait until it finishes and return the final state.
Args:
@@ -740,10 +741,10 @@ class SyncRunsClient:
limit: int = 10,
offset: int = 0,
status: RunStatus | None = None,
select: list[RunSelectField] | None = None,
select: builtins.list[RunSelectField] | None = None,
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> list[Run]:
) -> builtins.list[Run]:
"""List runs.
Args:
+2 -2
View File
@@ -530,7 +530,7 @@ class SyncThreadsClient:
elif checkpoint_id:
get_params = {"subgraphs": subgraphs}
if params:
get_params = {**get_params, **params}
get_params = {**get_params, **dict(params)}
return self.http.get(
f"/threads/{thread_id}/state/{checkpoint_id}",
params=get_params,
@@ -539,7 +539,7 @@ class SyncThreadsClient:
else:
get_params = {"subgraphs": subgraphs}
if params:
get_params = {**get_params, **params}
get_params = {**get_params, **dict(params)}
return self.http.get(
f"/threads/{thread_id}/state",
params=get_params,
+3 -1
View File
@@ -146,7 +146,9 @@ AssistantSortBy = Literal[
The field to sort by.
"""
ThreadSortBy = Literal["thread_id", "status", "created_at", "updated_at"]
ThreadSortBy = Literal[
"thread_id", "status", "created_at", "updated_at", "state_updated_at"
]
"""
The field to sort by.
"""
+2 -2
View File
@@ -19,7 +19,7 @@ class AsyncListByteStream(httpx.AsyncByteStream):
self._chunks = list(chunks)
self._exc = exc
async def __aiter__(self): # type: ignore[override]
async def __aiter__(self):
for chunk in self._chunks:
yield chunk
if self._exc is not None:
@@ -34,7 +34,7 @@ class ListByteStream(httpx.ByteStream):
self._chunks = list(chunks)
self._exc = exc
def __iter__(self): # type: ignore[override]
def __iter__(self):
yield from self._chunks
if self._exc is not None:
raise self._exc
+2 -2
View File
@@ -65,7 +65,7 @@ def test_raise_for_status_typed_maps_exceptions_and_sets_status_code(
with pytest.raises(exc_type) as ei:
_raise_for_status_typed(r)
err = cast("APIStatusError", ei.value)
err = ei.value
assert err.status_code == status
# response attribute should be present and match
assert err.response.status_code == status
@@ -113,7 +113,7 @@ def test_error_message_in_str_and_args() -> None:
r = make_response(422, json_body={"message": "Validation failed"})
with pytest.raises(UnprocessableEntityError) as ei:
_raise_for_status_typed(r)
err = cast("UnprocessableEntityError", ei.value)
err = ei.value
assert str(err) == "Validation failed"
assert err.args == ("Validation failed",)
assert err.message == "Validation failed"