mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-01 12:48:50 +02:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b24b6c274 | ||
|
|
11ee185999 | ||
|
|
d5f4b2aa96 | ||
|
|
5a77be5e8b | ||
|
|
bdb8a9c7a4 | ||
|
|
38031739e5 |
@@ -21,16 +21,6 @@ updates:
|
||||
directory: "/libs/checkpoint"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
versioning-strategy: increase
|
||||
ignore:
|
||||
- dependency-name: "langgraph"
|
||||
- dependency-name: "langgraph-cli"
|
||||
- dependency-name: "langgraph-sdk"
|
||||
- dependency-name: "langgraph-prebuilt"
|
||||
- dependency-name: "langgraph-checkpoint"
|
||||
- dependency-name: "langgraph-checkpoint-postgres"
|
||||
- dependency-name: "langgraph-checkpoint-sqlite"
|
||||
- dependency-name: "langgraph-checkpoint-conformance"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns:
|
||||
@@ -48,16 +38,6 @@ updates:
|
||||
directory: "/libs/checkpoint-conformance"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
versioning-strategy: increase
|
||||
ignore:
|
||||
- dependency-name: "langgraph"
|
||||
- dependency-name: "langgraph-cli"
|
||||
- dependency-name: "langgraph-sdk"
|
||||
- dependency-name: "langgraph-prebuilt"
|
||||
- dependency-name: "langgraph-checkpoint"
|
||||
- dependency-name: "langgraph-checkpoint-postgres"
|
||||
- dependency-name: "langgraph-checkpoint-sqlite"
|
||||
- dependency-name: "langgraph-checkpoint-conformance"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns:
|
||||
@@ -75,16 +55,6 @@ updates:
|
||||
directory: "/libs/checkpoint-postgres"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
versioning-strategy: increase
|
||||
ignore:
|
||||
- dependency-name: "langgraph"
|
||||
- dependency-name: "langgraph-cli"
|
||||
- dependency-name: "langgraph-sdk"
|
||||
- dependency-name: "langgraph-prebuilt"
|
||||
- dependency-name: "langgraph-checkpoint"
|
||||
- dependency-name: "langgraph-checkpoint-postgres"
|
||||
- dependency-name: "langgraph-checkpoint-sqlite"
|
||||
- dependency-name: "langgraph-checkpoint-conformance"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns:
|
||||
@@ -102,16 +72,6 @@ updates:
|
||||
directory: "/libs/checkpoint-sqlite"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
versioning-strategy: increase
|
||||
ignore:
|
||||
- dependency-name: "langgraph"
|
||||
- dependency-name: "langgraph-cli"
|
||||
- dependency-name: "langgraph-sdk"
|
||||
- dependency-name: "langgraph-prebuilt"
|
||||
- dependency-name: "langgraph-checkpoint"
|
||||
- dependency-name: "langgraph-checkpoint-postgres"
|
||||
- dependency-name: "langgraph-checkpoint-sqlite"
|
||||
- dependency-name: "langgraph-checkpoint-conformance"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns:
|
||||
@@ -129,16 +89,6 @@ updates:
|
||||
directory: "/libs/cli"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
versioning-strategy: increase
|
||||
ignore:
|
||||
- dependency-name: "langgraph"
|
||||
- dependency-name: "langgraph-cli"
|
||||
- dependency-name: "langgraph-sdk"
|
||||
- dependency-name: "langgraph-prebuilt"
|
||||
- dependency-name: "langgraph-checkpoint"
|
||||
- dependency-name: "langgraph-checkpoint-postgres"
|
||||
- dependency-name: "langgraph-checkpoint-sqlite"
|
||||
- dependency-name: "langgraph-checkpoint-conformance"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns:
|
||||
@@ -156,16 +106,6 @@ updates:
|
||||
directory: "/libs/langgraph"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
versioning-strategy: increase
|
||||
ignore:
|
||||
- dependency-name: "langgraph"
|
||||
- dependency-name: "langgraph-cli"
|
||||
- dependency-name: "langgraph-sdk"
|
||||
- dependency-name: "langgraph-prebuilt"
|
||||
- dependency-name: "langgraph-checkpoint"
|
||||
- dependency-name: "langgraph-checkpoint-postgres"
|
||||
- dependency-name: "langgraph-checkpoint-sqlite"
|
||||
- dependency-name: "langgraph-checkpoint-conformance"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns:
|
||||
@@ -183,16 +123,6 @@ updates:
|
||||
directory: "/libs/prebuilt"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
versioning-strategy: increase
|
||||
ignore:
|
||||
- dependency-name: "langgraph"
|
||||
- dependency-name: "langgraph-cli"
|
||||
- dependency-name: "langgraph-sdk"
|
||||
- dependency-name: "langgraph-prebuilt"
|
||||
- dependency-name: "langgraph-checkpoint"
|
||||
- dependency-name: "langgraph-checkpoint-postgres"
|
||||
- dependency-name: "langgraph-checkpoint-sqlite"
|
||||
- dependency-name: "langgraph-checkpoint-conformance"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns:
|
||||
@@ -210,16 +140,6 @@ updates:
|
||||
directory: "/libs/sdk-py"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
versioning-strategy: increase
|
||||
ignore:
|
||||
- dependency-name: "langgraph"
|
||||
- dependency-name: "langgraph-cli"
|
||||
- dependency-name: "langgraph-sdk"
|
||||
- dependency-name: "langgraph-prebuilt"
|
||||
- dependency-name: "langgraph-checkpoint"
|
||||
- dependency-name: "langgraph-checkpoint-postgres"
|
||||
- dependency-name: "langgraph-checkpoint-sqlite"
|
||||
- dependency-name: "langgraph-checkpoint-conformance"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns:
|
||||
@@ -266,38 +186,3 @@ updates:
|
||||
- "*"
|
||||
update-types:
|
||||
- "major"
|
||||
|
||||
# CI builds this maintained integration image and Compose stack.
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/libs/sdk-py/integration"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
major:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- "major"
|
||||
|
||||
- package-ecosystem: "docker-compose"
|
||||
directory: "/libs/sdk-py/integration"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
groups:
|
||||
minor-and-patch:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
major:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- "major"
|
||||
|
||||
@@ -76,6 +76,7 @@ __pypackages__/
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.env.*
|
||||
.envrc
|
||||
*.crt
|
||||
*.key
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
This repository is a monorepo. Each library lives in a subdirectory under `libs/`.
|
||||
|
||||
<corridor>
|
||||
|
||||
## Corridor security analysis
|
||||
|
||||
When Corridor's `analyzePlan` tool is available, create a plan and use the tool to analyze it before generating or modifying code. Apply the resulting security guidance before writing code.
|
||||
|
||||
</corridor>
|
||||
|
||||
When you modify code in any library, run the following commands in that library's directory before creating a pull request:
|
||||
|
||||
- `make format` – run code formatters
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
This repository is a monorepo. Each library lives in a subdirectory under `libs/`.
|
||||
|
||||
<corridor>
|
||||
|
||||
## Corridor security analysis
|
||||
|
||||
Before generating or modifying code, create a plan and use Corridor's `analyzePlan` tool to analyze it. Apply the resulting security guidance before writing code.
|
||||
|
||||
</corridor>
|
||||
|
||||
When you modify code in any library, run the following commands in that library's directory before creating a pull request:
|
||||
|
||||
- `make format` – run code formatters
|
||||
|
||||
@@ -7,7 +7,7 @@ import logging
|
||||
import re
|
||||
import threading
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable, Iterable, Iterator, Sequence
|
||||
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
from typing import (
|
||||
@@ -354,7 +354,7 @@ class BasePostgresStore(Generic[C]):
|
||||
(
|
||||
_namespace_to_text(op.namespace),
|
||||
op.key,
|
||||
Jsonb(cast(dict, op.value)),
|
||||
Jsonb(dict(cast(Mapping[str, Any], op.value))),
|
||||
)
|
||||
)
|
||||
if op.ttl is not None:
|
||||
|
||||
@@ -7,7 +7,7 @@ import re
|
||||
import sqlite3
|
||||
import threading
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable, Iterable, Iterator, Sequence
|
||||
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
|
||||
from contextlib import contextmanager
|
||||
from typing import Any, Literal, NamedTuple, cast
|
||||
|
||||
@@ -387,7 +387,7 @@ class BaseSqliteStore:
|
||||
[
|
||||
_namespace_to_text(op.namespace),
|
||||
op.key,
|
||||
orjson.dumps(cast(dict, op.value)),
|
||||
orjson.dumps(dict(cast(Mapping[str, Any], op.value))),
|
||||
expires_at,
|
||||
op.ttl,
|
||||
]
|
||||
|
||||
@@ -12,7 +12,7 @@ Core types:
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import Iterable, Mapping
|
||||
from datetime import datetime
|
||||
from typing import (
|
||||
Any,
|
||||
@@ -473,10 +473,10 @@ class PutOp(NamedTuple):
|
||||
the full path would effectively be `"documents/user123/report1"`
|
||||
"""
|
||||
|
||||
value: dict[str, Any] | None
|
||||
value: Mapping[str, Any] | None
|
||||
"""The data to store, or `None` to mark the item for deletion.
|
||||
|
||||
The value must be a dictionary with string keys and JSON-serializable values.
|
||||
The value must be a mapping with string keys and JSON-serializable values.
|
||||
Setting this to `None` signals that the item should be deleted.
|
||||
|
||||
Example:
|
||||
@@ -857,7 +857,7 @@ class BaseStore(ABC):
|
||||
self,
|
||||
namespace: tuple[str, ...],
|
||||
key: str,
|
||||
value: dict[str, Any],
|
||||
value: Mapping[str, Any],
|
||||
index: Literal[False] | list[str] | None = None,
|
||||
*,
|
||||
ttl: float | None | NotProvided = NOT_PROVIDED,
|
||||
@@ -869,7 +869,7 @@ class BaseStore(ABC):
|
||||
Example: `("documents", "user123")`
|
||||
key: Unique identifier within the namespace. Together with namespace forms
|
||||
the complete path to the item.
|
||||
value: Dictionary containing the item's data. Must contain string keys
|
||||
value: Mapping containing the item's data. Must contain string keys
|
||||
and JSON-serializable values.
|
||||
index: Controls how the item's fields are indexed for search:
|
||||
|
||||
@@ -1110,7 +1110,7 @@ class BaseStore(ABC):
|
||||
self,
|
||||
namespace: tuple[str, ...],
|
||||
key: str,
|
||||
value: dict[str, Any],
|
||||
value: Mapping[str, Any],
|
||||
index: Literal[False] | list[str] | None = None,
|
||||
*,
|
||||
ttl: float | None | NotProvided = NOT_PROVIDED,
|
||||
@@ -1122,7 +1122,7 @@ class BaseStore(ABC):
|
||||
Example: `("documents", "user123")`
|
||||
key: Unique identifier within the namespace. Together with namespace forms
|
||||
the complete path to the item.
|
||||
value: Dictionary containing the item's data. Must contain string keys
|
||||
value: Mapping containing the item's data. Must contain string keys
|
||||
and JSON-serializable values.
|
||||
index: Controls how the item's fields are indexed for search:
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import functools
|
||||
import weakref
|
||||
from collections.abc import Callable, Iterable
|
||||
from collections.abc import Callable, Iterable, Mapping
|
||||
from typing import Any, Literal, TypeVar
|
||||
|
||||
from langgraph.store.base import (
|
||||
@@ -132,7 +132,7 @@ class AsyncBatchedBaseStore(BaseStore):
|
||||
self,
|
||||
namespace: tuple[str, ...],
|
||||
key: str,
|
||||
value: dict[str, Any],
|
||||
value: Mapping[str, Any],
|
||||
index: Literal[False] | list[str] | None = None,
|
||||
*,
|
||||
ttl: float | None | NotProvided = NOT_PROVIDED,
|
||||
@@ -231,7 +231,7 @@ class AsyncBatchedBaseStore(BaseStore):
|
||||
self,
|
||||
namespace: tuple[str, ...],
|
||||
key: str,
|
||||
value: dict[str, Any],
|
||||
value: Mapping[str, Any],
|
||||
index: Literal[False] | list[str] | None = None,
|
||||
*,
|
||||
ttl: float | None | NotProvided = NOT_PROVIDED,
|
||||
|
||||
@@ -11,7 +11,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import functools
|
||||
import json
|
||||
from collections.abc import Awaitable, Callable, Sequence
|
||||
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
||||
from typing import Any
|
||||
|
||||
from langchain_core.embeddings import Embeddings
|
||||
@@ -244,6 +244,9 @@ def get_text_at_path(obj: Any, path: str | list[str]) -> list[str]:
|
||||
- Multi-field selection: "{field1,field2}"
|
||||
- Nested paths in multi-field: "{field1,nested.field2}"
|
||||
"""
|
||||
if isinstance(obj, Mapping) and not isinstance(obj, dict):
|
||||
obj = dict(obj)
|
||||
|
||||
if not path or path == "$":
|
||||
return [json.dumps(obj, sort_keys=True, ensure_ascii=False)]
|
||||
|
||||
|
||||
@@ -408,7 +408,7 @@ class InMemoryStore(BaseStore):
|
||||
self._vectors[namespace].pop(key, None)
|
||||
else:
|
||||
self._data[namespace][key] = Item(
|
||||
value=op.value,
|
||||
value=dict(op.value),
|
||||
key=key,
|
||||
namespace=namespace,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import asyncio
|
||||
import json
|
||||
from collections.abc import Iterable
|
||||
from collections import UserDict
|
||||
from collections.abc import Iterable, Mapping
|
||||
from datetime import datetime
|
||||
from types import MappingProxyType
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
@@ -137,6 +139,18 @@ def test_get_text_at_path() -> None:
|
||||
assert get_text_at_path(nested_data, "nested[{invalid}]") == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mapping",
|
||||
[
|
||||
UserDict({"text": "searchable"}),
|
||||
MappingProxyType({"text": "searchable"}),
|
||||
],
|
||||
)
|
||||
def test_get_text_at_path_with_non_dict_mapping(mapping: Mapping[str, str]) -> None:
|
||||
assert get_text_at_path(mapping, "$") == ['{"text": "searchable"}']
|
||||
assert get_text_at_path(mapping, "text") == ["searchable"]
|
||||
|
||||
|
||||
async def test_async_batch_store(mocker: MockerFixture) -> None:
|
||||
abatch = mocker.stub()
|
||||
|
||||
|
||||
Generated
+50
-53
@@ -363,14 +363,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.4.2"
|
||||
version = "8.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -680,7 +677,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "hatch"
|
||||
version = "1.17.1"
|
||||
version = "1.18.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "backports-zstd", marker = "python_full_version < '3.14'" },
|
||||
@@ -703,9 +700,9 @@ dependencies = [
|
||||
{ name = "uv" },
|
||||
{ name = "virtualenv" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/5a/7cacce8d33a93e81cda9b4f8623ebf67b73859950af0062e1f2c527f4b34/hatch-1.17.1.tar.gz", hash = "sha256:e5f2f389ececd7e86cd86863e3f921a9024cd04ce6ef6b36d07cd9a6dc37c9cd", size = 5252686, upload-time = "2026-07-08T01:54:37.327Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/10/fd/634c575b5becad2dfa7135f3ad66d57102cd77d834cf473756d868a808ff/hatch-1.18.0.tar.gz", hash = "sha256:463d214cb068391454be198e58d3cc0846f71e39d68c5568ba650af7781e8ae3", size = 5275084, upload-time = "2026-08-11T05:06:41.513Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/0a/2b4e653186fc85061f0dfde43d602e7e93c08c0d75b23fa3577f9b3f83fd/hatch-1.17.1-py3-none-any.whl", hash = "sha256:cc6c06d302cfa785c35586ab4285c8c28a24b1744addb66344192302f6958083", size = 162405, upload-time = "2026-07-08T01:54:35.452Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/b1/d14265cb26c57af1485c01fd95037a35f13748b718617405cdefc6150429/hatch-1.18.0-py3-none-any.whl", hash = "sha256:75874c0905fe76b44fd39585c49bc9bd2b9fee638abb11f1f3d087042e7f9122", size = 171310, upload-time = "2026-08-11T05:06:39.807Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1199,7 +1196,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-sdk"
|
||||
version = "0.4.2"
|
||||
version = "0.4.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
@@ -1208,9 +1205,9 @@ dependencies = [
|
||||
{ name = "orjson" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b4/2b/bd8ac26d4e97f6df88ef05ce5b6a38945a3903e1025d926f4752aa88aa97/langgraph_sdk-0.4.2.tar.gz", hash = "sha256:b88f0f5f6328ac0680d6790614a905b2bcfa257f2276dba4e38f0e86db0aa738", size = 348327, upload-time = "2026-06-01T17:51:19.856Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3f/ae/91446c1fffa04a2dc1f81afbfb5bfff3590452891a72bd9098a656ab2657/langgraph_sdk-0.4.4.tar.gz", hash = "sha256:4e651ffa09de695681579396375377bdde23bedbc8e35b070e615cbd5af7da8b", size = 345789, upload-time = "2026-08-27T21:25:22.593Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/05/aac507337cceae773c2cc9ab91eb6301963af7aeeb55b4217a00e15aff17/langgraph_sdk-0.4.2-py3-none-any.whl", hash = "sha256:75fa5096c1177ce39c847096a8fe3745ffd480ddb412995f836e9f5f884c43dd", size = 160521, upload-time = "2026-06-01T17:51:18.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/d5/2ad7f3a835c6fcebf58b6669552536ecd52d2dfe4cf49c6c36648fd83572/langgraph_sdk-0.4.4-py3-none-any.whl", hash = "sha256:39afe416c91742925e6f8a93715f566d499b36e1b636b804a4ffe3190e4f4e64", size = 162270, upload-time = "2026-08-27T21:25:21.072Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1868,11 +1865,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.2"
|
||||
version = "1.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945, upload-time = "2026-08-16T16:54:54.067Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1990,27 +1987,27 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.16.0"
|
||||
version = "0.16.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4d/94/1e5e4967626faf12fa56999cd6222dff6992ceb086ad7945756baf70c7a7/ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982", size = 4790557, upload-time = "2026-07-23T19:11:30.981Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/85/c8e12473c93018f92d19dd988a294202e1c27426c47ec4de53ffb847b8d8/ruff-0.16.5.tar.gz", hash = "sha256:1b88500f9ffbcab3dedb0082c9f9492e91ec3d618aac1236a3e0189938f7040b", size = 4912003, upload-time = "2026-08-27T16:34:18.258Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/81/1c8818fee7ce1a04cd7d1b3172e0a8f8e4f1dc4feb7fc390e16daa8af323/ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e", size = 10754633, upload-time = "2026-07-23T19:10:46.415Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/df/beaf59c09d68db84304d555f188b276a77132a5d5b0b67a5c762aa143628/ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522", size = 10969164, upload-time = "2026-07-23T19:10:50.271Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/ce/741cd197496a1abbf51352710fd15ed995d2a2be87189c1da26a450d6e83/ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81", size = 10488846, upload-time = "2026-07-23T19:10:52.639Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/2a/a2db8e88cade358f5cdcb05674a917751074109315d014eb6352d9a893f7/ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9", size = 10889729, upload-time = "2026-07-23T19:10:54.89Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/65/62a771694ebd63029dc953e27dbad40e1588bd4860ff9fe881018fddaa49/ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a", size = 10568275, upload-time = "2026-07-23T19:10:56.993Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/e2/ced249fe8af5f086c5c58cc21cc3356d50f32f7401c5df87050c999620a7/ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717", size = 11385112, upload-time = "2026-07-23T19:10:59.615Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/0b/05154977a8fd69eeb6c103271f55403bfd8711f5c0f8ed07489d95a504e7/ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17", size = 12207008, upload-time = "2026-07-23T19:11:02.154Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/29/98225831a3a1eab0e02f4acc6ca6559a98611dcc68b6965ff4b7234627c1/ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4", size = 11650842, upload-time = "2026-07-23T19:11:04.557Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/66/6bd3cf90500653d55dc0ffc8507aa8300bd49d0214b2e8cb4d3fef2943ba/ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b", size = 11400718, upload-time = "2026-07-23T19:11:09.233Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/a2/a54eb4eae05d66364050a5d3b8a9c5ef88196531b3cbe7109d873f87f819/ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0", size = 11426177, upload-time = "2026-07-23T19:11:11.994Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/be/16e3eea4b2a478a496919f5e36f17c4559e54620bd3bbac5d6affa068006/ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213", size = 10856126, upload-time = "2026-07-23T19:11:14.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/84/252eb8b868a16eec7257c14f504f77537e734b2d69c762e639e588e304a3/ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af", size = 10571208, upload-time = "2026-07-23T19:11:16.378Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/09/817a482f542f7570cbb4554b26e896610c7114f539b1d9e2d2145bf6bef6/ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09", size = 11063329, upload-time = "2026-07-23T19:11:19.173Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/23/9403c180ca1cb9b1f7335f5c3e5305c09d49ea5b345196682a36028bde4a/ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed", size = 11489751, upload-time = "2026-07-23T19:11:21.74Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/1d/1b2ef7bcde851c78d7f17f1cca13fd6dc695fc4b3d6197941e72cae5b132/ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb", size = 10785885, upload-time = "2026-07-23T19:11:23.947Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/a3/d5e4ef7a56be3f928ffb90b94c25ba7d3cb9c7fe0736aeaaedf361770712/ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472", size = 11923141, upload-time = "2026-07-23T19:11:26.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/9a/8415f2657cbe200f41a4531ccededf135505a92d4a012229121f885b26f9/ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d", size = 11273407, upload-time = "2026-07-23T19:11:28.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/b6/77c90a970fe2dae17a723acbd011043ea97c98d7deacccefdc4ba74ec512/ruff-0.16.5-py3-none-linux_armv6l.whl", hash = "sha256:12e5f673e774c35fbb62f288809c7653b73445f8ecec6b6063fd6ea3521aa14b", size = 10011941, upload-time = "2026-08-27T16:33:41.287Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/46/6cf67cf6411885a1d6f7f6d801682f155536a85176d10b605e2ceffed8bd/ruff-0.16.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:eda58a5802de40e7ed5b32b64e0b32539338cc6fcd2c78f61e3ad6a0d79f51c3", size = 10204049, upload-time = "2026-08-27T16:33:44.056Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/fd/c8720ca7a090abf0c2fef4abe8a5ef6e5127ed15196d8886ff75a2b370e2/ruff-0.16.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5ae9a7b9a8875131f40f8fe967cc86abf899779efd663cb7ce3d572d01da7eb", size = 9809037, upload-time = "2026-08-27T16:33:46.257Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/45/a684caacdedaca180f52bacccc40bf0789d2c5a7c75f25324853e9eaedb5/ruff-0.16.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b719b0a1f4d59710d283ab2965f621684a108a9e41da622e3b23f0326cd0025", size = 9964129, upload-time = "2026-08-27T16:33:48.352Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/f2/5d2bcdaca6b5b93d1b4dfc166cd2aebf7680143a1b38a28759df13a94d31/ruff-0.16.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2298f2780ed1be0c5cb1361e32ab7b1467f3cce7dabe101d2210a314f2fe42e9", size = 9821518, upload-time = "2026-08-27T16:33:50.57Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/ff/011cce29accf9257d5974145b733fc653a37985ed6825413a3987cefbfe0/ruff-0.16.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:258f29035a2dd021e7861e631b227a5b3f14e50c1184c9a6a122c5f4576154d7", size = 10534835, upload-time = "2026-08-27T16:33:52.522Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/5a/f0cf109bada9bba0e96c90c21c9f9251803f57225c32d293327a03c710d6/ruff-0.16.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9a4f0432966834019c74d1b7e5c51224305d7713f3d7faf3e7451f1a3be3cde", size = 11252550, upload-time = "2026-08-27T16:33:54.521Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/4d/1d481aaea2046c6a7ed7c291f9004c669cce3c087b6b376ed5b08271e3fe/ruff-0.16.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5eb3a8c3d0ade9cea42b591fd530368e8798380e30e0a308b85a5cf718f09ea", size = 10777949, upload-time = "2026-08-27T16:33:56.88Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/34/ee245ca55f64443233034b3d02b03236b19242004281247c079390b7facd/ruff-0.16.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef0f69e191a13a3c9816f63163c88790cb12cd157bbbb384e9c44745702ab105", size = 10311656, upload-time = "2026-08-27T16:33:59.12Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/4d/c33a333e341c0a2b96c715b52d89a606f5a34cd4ac493cd9b8d0187186b8/ruff-0.16.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0eeab41fbea2c42f98dfb9822cdccda9d24ba38d49f6dc945b5c236d48f0ef29", size = 10532125, upload-time = "2026-08-27T16:34:01.166Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/e1/a64cef78b40192497bb98a27a8aa8f2c98ee9ee15bc97f7712d94ef32937/ruff-0.16.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f0768e9df4300713fff30733c87575f68b6f1d8de41184e505b7fdd9c0c95eaf", size = 10097648, upload-time = "2026-08-27T16:34:03.16Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/4e/4cdc9ed3c3e109d2f71e62572a37457298d7bc7501ec3138babb7ed32bbd/ruff-0.16.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:95cc70cdc7aa80c338de356279d2adbeb2de0f520b9ecd8aba75b94e95e02f91", size = 9829344, upload-time = "2026-08-27T16:34:05.134Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/4a/31ed35ce31729955fc583ee0d176d6e784c1290cb0b0a75cb2134c1ab72a/ruff-0.16.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d185c8398ded1bfd91c0c2cb258346307571eccc473a8490af8c3977399c384a", size = 10277117, upload-time = "2026-08-27T16:34:07.425Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/a0/60356d86687b4b666d593df213f4dc3041750d024cb7bf2cfa81cfd65c2e/ruff-0.16.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fb8e3a3c4c6a784150a7ced53b015f4b253fc2bf97a610886419ead64b4756ef", size = 10711653, upload-time = "2026-08-27T16:34:09.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/20/656d67f5b25ca9bda4e02b1de25867b2954e1d19e03648060f167ad0f4cc/ruff-0.16.5-py3-none-win32.whl", hash = "sha256:288b0a5f080492fe5635db849f9e2e84aa3cce7b7f0e955997d416c507c76a26", size = 10034250, upload-time = "2026-08-27T16:34:11.8Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/42/ee8e68a207b9127fcde6c3d7e197def432f346cb1af159e1fa14ca0d1cdc/ruff-0.16.5-py3-none-win_amd64.whl", hash = "sha256:ddc6385fb2137f616357ca03d6c74f4be987f80fed4008566b754f6032b8546f", size = 10516714, upload-time = "2026-08-27T16:34:13.963Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/e3/7df5a396e445b9ba49ce9a9437439a4d80042c61c0ade199abf8d16de1ac/ruff-0.16.5-py3-none-win_arm64.whl", hash = "sha256:a64abe90968719b851bb7cedffaa8753fbdbdadab483089682db623f3edc587e", size = 10391564, upload-time = "2026-08-27T16:34:16.064Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2189,27 +2186,27 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "ty"
|
||||
version = "0.0.64"
|
||||
version = "0.0.75"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/aa/14c9965d3b173105692473897cc89c34cd91241368b2044e43167e1c17ff/ty-0.0.64.tar.gz", hash = "sha256:d12ddbb05f15158bb518af619378b385486450def95fb06f8ab98037febe9f2c", size = 6350966, upload-time = "2026-07-27T18:32:45.403Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/81/d0/d0c96f898d6974a4a3569ab3efdf9512c04ad99f9203effb55f72497fe97/ty-0.0.75.tar.gz", hash = "sha256:4c5eead33dfbf6e2ebb4f400f74b51ffc9bab702a6f23ddb648a1cbb740387e3", size = 6868326, upload-time = "2026-08-26T20:23:40.399Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/4c/c54937e4ff3fa7b34a99ea3387ec766bf0ad98dc8df8d792e89b388e658e/ty-0.0.64-py3-none-linux_armv6l.whl", hash = "sha256:3830a6675ab43635ced1c4c557f380ac4a49e9414e03975e4e4e8db644c64944", size = 12118357, upload-time = "2026-07-27T18:32:08.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/aa/a839ee2bc78e943d079e6abe199a97b4eeffb7e5c9a57326d69de452186a/ty-0.0.64-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3ff07d7bc32a2135f58afe57393789a32ea2fed1a66216129a8e535e76043903", size = 11790882, upload-time = "2026-07-27T18:32:10.997Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/de/19f14357888a7198438926303753cf749428e3d62e8980ff1e9a72a78402/ty-0.0.64-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4f6d1c7f897cca05d12bacbf1435150d5ffa496099515aa6ed303c8b29e1d0bb", size = 11317394, upload-time = "2026-07-27T18:32:13.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/2f/f54462300535ab99b551eda733177be2eef5dbc2997d3fdb357c4ddd760a/ty-0.0.64-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:138d6c37ad4bf8583aa7a9b29d90954151d7856f9910a03eae3eb34b34c57215", size = 11863042, upload-time = "2026-07-27T18:32:15.307Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/95/dbecf745520ebe8bd7b02fc55eee6441c9be312ebf6addce605eb52740dd/ty-0.0.64-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ed9719d1b7b66fb8efe073d860208a44c40af1f6cd5c2364aa9b323a1e579b4", size = 11910730, upload-time = "2026-07-27T18:32:17.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/26/12cfd40028e51ceed7b3cb645281c61c02eb64ff9fb0c09231d65c30ff25/ty-0.0.64-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d68b23e5169e2137b5f1de7169ab0cebecab6c8eda374c34c1f6394308f58242", size = 12631936, upload-time = "2026-07-27T18:32:19.533Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/d0/65ffc2b0a686347193c6f98e9421a7fc2a96fc3cd0b1001cf7cf284baff9/ty-0.0.64-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f41cb07d89d32626fcaf3ed4d262778fcb28b2628b6ed1e172cd7b18820668d8", size = 13171049, upload-time = "2026-07-27T18:32:22.026Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/a4/975a5961842dcd6fa60f0770a102c0bba7509da909ab652919bfcdcd4fc7/ty-0.0.64-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f24e1504ab9e212f92356b82fe088fdeb3a39f9a2f4ff25e505d2e1d0db9056", size = 12826438, upload-time = "2026-07-27T18:32:24.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/ef/dfb9b7f9bcc032d3b540b0d1f55f532a336e2fb41b1bd539c05ae81a151a/ty-0.0.64-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86db830cb914bb33bb8247b66ccea58de4496c2391bd42658aba744b439f3290", size = 12440880, upload-time = "2026-07-27T18:32:26.341Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/d0/bedac20505e8a8f5501ad73d7d15d8e421a563fef59993909a23036929a4/ty-0.0.64-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:652ee3d6d03bea76cd2fe8949c78bb5970394ebd7c5fd270e9518c0dee1b931d", size = 12782439, upload-time = "2026-07-27T18:32:28.642Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/d5/795733f13ceff1378f08b3de0c49d0f518df220ed856b0dfac869f3b7c81/ty-0.0.64-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4838768295774a86e95f9ec5633e739d016adbbb69dcbdc62f3549578a11f624", size = 11814821, upload-time = "2026-07-27T18:32:30.632Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/3e/9d99cd1e1831003434f508ed9f258a56543194afc3bbe051eba2545fa676/ty-0.0.64-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5a3d700669868599edf39ce5125196682f15a8880cc8c669bc2a83b99984d99b", size = 11928678, upload-time = "2026-07-27T18:32:32.888Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/3d/448f49a3503fb119a34348a5714bb92001f252fadbbb12d645f2e744b557/ty-0.0.64-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b161f0a82a8e2f2432db3bf7702b4d3924fa9486ba0014f6710a160fc157df0d", size = 12202249, upload-time = "2026-07-27T18:32:34.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/9b/75768e562cec990d189dc05807ae72890b20ffbd1e1f43597bb98db63c60/ty-0.0.64-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:39b9dd42908df47c2dc57dda87e656fab97097ffd2618474bbdea986af0d6a9d", size = 12548817, upload-time = "2026-07-27T18:32:36.995Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/89/44cc276ea6ca0245495014758ffeadde1798fb0b5840c9cf36d8d2ed3250/ty-0.0.64-py3-none-win32.whl", hash = "sha256:d0676ab0e0935795e5843baa28dd5e366dc343d7c0945a996df9eab8e0644885", size = 11545474, upload-time = "2026-07-27T18:32:39.389Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/7e/d1c8a871a38d17c8f168b9a6975f6247f7660f8334e517656a5e4b4a4858/ty-0.0.64-py3-none-win_amd64.whl", hash = "sha256:dcb9bd31f54097e362b776c26ab4564d4564cdd1355cb883481167a26c03cc3f", size = 12542987, upload-time = "2026-07-27T18:32:41.412Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/4d/6d18640d0204cacd69abbaca95ad6a34c6d7e9169e9051d0117b17b827ec/ty-0.0.64-py3-none-win_arm64.whl", hash = "sha256:82cc34c1ad9a8feb6059aef193bebcecc656e548f6fab3d518bcd8b57d198d39", size = 11899263, upload-time = "2026-07-27T18:32:43.366Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/6c/b12d03505f17581f0cfa3c12273fe34c1d67b36dfda1bc561a6bdc16512b/ty-0.0.75-py3-none-linux_armv6l.whl", hash = "sha256:e5409f50db2246fd4bd039d93d261e0cfa1daa554a4fb77256f91072c570349a", size = 12972606, upload-time = "2026-08-26T20:22:59.716Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/aa/30f11eecd9215a9f87e8fe8baaf48f3ce905f5d75b8e4aac70f0091f130c/ty-0.0.75-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5e7b8b3472fb9bb2eeab314984b265df08a7a9d518867a9e6020eebc06570be2", size = 12527158, upload-time = "2026-08-26T20:23:02.767Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/11/7fd7001b0b5c6610bfbad7357e47d5fe6f82d4e84e94c53776a478f5e9f8/ty-0.0.75-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c6ccf34169821fe0d23e3360deeef981d217963412f1d087b9bdd32ec57f7a57", size = 12400533, upload-time = "2026-08-26T20:23:04.965Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/7f/1e284ea3d348d7be02f12d83bc22ed9ef193033f863f05b64db99027f141/ty-0.0.75-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:842ebb41e9c6c334b40768704e20b1a69d5c6b08805b289d5e0e2565f49f2de1", size = 12420592, upload-time = "2026-08-26T20:23:07.427Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/ab/d813271543370c47fd74b5118f2066ab32b0983e907b1821f3f9a6d0fa7f/ty-0.0.75-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf7a5a723c5f1e0fab4ffbfe9bd95123a526ed48f206e5f25cb2161ca294007a", size = 12739219, upload-time = "2026-08-26T20:23:09.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/5b/95b49cc5570fd92a7bf63732f649b31906158721e03c7fcb1b5be74ee3bf/ty-0.0.75-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54382f98e5da292fcd7104391afef5105c35bb2f312e29bea6f5fa419935255c", size = 13494046, upload-time = "2026-08-26T20:23:12.191Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/0d/502d2dd68173cf020e1ad2bdbab9544c86776de0b0e2ed15f8c2fe006e3d/ty-0.0.75-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac13b180dc2aade2cd243f56b01650e78bf091a2e522ad3bc947245d7837c613", size = 13938899, upload-time = "2026-08-26T20:23:14.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/5b/f3b12a25c07224456219fc2bd20db0ad7e40b304be0ff6aad728da0135f9/ty-0.0.75-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:752df7951a443219d7f1ff817e3723c85d428565ff449e08a7a93ba821661526", size = 13656711, upload-time = "2026-08-26T20:23:17.145Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/7b/f090ad306e2b15a07b332d647138c5264b89d9758855ecce8b8a10bcb153/ty-0.0.75-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fd399feedf7cee816563c1baec45fc1c0b3c89f1ea42364920b688004b5b7da", size = 13093499, upload-time = "2026-08-26T20:23:19.489Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/4b/f69b99aaaca0c7c65d5f114b186b26b21666f767b0c69eec99a2bdccc061/ty-0.0.75-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:d7625f6f56c7dc1e873579fdc9e432a0e21e302afe847ab60704d2303442a92e", size = 13520580, upload-time = "2026-08-26T20:23:21.789Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/e7/692c5f905c0345a15d2255fc74066d660f030254ae8dcdaf33f5a5c2f279/ty-0.0.75-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:89e7d527e95a2534b70cae29e94c104b84082760ea05927d23bb87280969c104", size = 12524095, upload-time = "2026-08-26T20:23:24.026Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/9a/f42b12cf265ea95344bf554764c4791cfb273bdd628aadd7c209af7cadc3/ty-0.0.75-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0843f134440740706e01bee5f88f4cfc10e9b018bddb9e4ef4c12dc9fc0c9aef", size = 12756591, upload-time = "2026-08-26T20:23:26.126Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/5e/9b180c133cb9cce48179a7d2bf9e1802d992aa8176a918e0e05205760b42/ty-0.0.75-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1bd0ec0e50ee1376875c88891efe6f549c3560fa5b2ddad79a425cd5a6218b9c", size = 12998754, upload-time = "2026-08-26T20:23:28.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/f6/3c6ef5dd550103e29905121c67fb96a374564f31a2f44c6faa1af98c2d61/ty-0.0.75-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1f9eafd561f90110d5e29f589ec3e956c4686e2f6631348d99276436f5cbe4d1", size = 13316474, upload-time = "2026-08-26T20:23:30.857Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/52/12776337874c821076bd5368e352ccd9e67174790abe3b856f749cb3524b/ty-0.0.75-py3-none-win32.whl", hash = "sha256:05063a6fafe2154b794a7f964515d148e51acd186d72d4a3acd347ee9fa19336", size = 12316315, upload-time = "2026-08-26T20:23:33.528Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/e6/bb51e16af5c7138c9f52f8f3d0a401a371c6798d092e3b74926f186a9814/ty-0.0.75-py3-none-win_amd64.whl", hash = "sha256:81cf1ba5f6b7536ad56747865214255d9bc8e80533a689dbb9ddeaad464b09f1", size = 12917267, upload-time = "2026-08-26T20:23:35.978Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/73/4542f829107468b5de4231af67f29927c093bfad11f3c1e5b2c08fb1206b/ty-0.0.75-py3-none-win_arm64.whl", hash = "sha256:541c9af5b7a0ad23d15ec315a7da81150833c359f48124ed3789ff25eacd6f42", size = 12711024, upload-time = "2026-08-26T20:23:38.159Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -39,6 +39,15 @@
|
||||
- `client.threads.stream()` now accepts `transport="sse"` (default) or
|
||||
`transport="websocket"` in place of the previous transport-agnostic default.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Resource-scoped auth decorators now honor `actions=` and reject empty or
|
||||
invalid action lists. Because unmatched custom-auth paths remain allowed,
|
||||
deployments using action-scoped handlers should configure a global
|
||||
default-deny handler; `langgraph-api` 0.10+ warns about uncovered paths at
|
||||
startup. Resource-specific decorators retain matching `resources=` selectors
|
||||
for backward compatibility; use `@auth.on(resources=...)` for other resources.
|
||||
|
||||
### Notes
|
||||
|
||||
- The v3 streaming surface (`AsyncThreadStream`, `SyncThreadStream`, and all
|
||||
|
||||
@@ -3,7 +3,7 @@ from langgraph_sdk.client import get_client, get_sync_client
|
||||
from langgraph_sdk.encryption import Encryption
|
||||
from langgraph_sdk.encryption.types import DecryptResult, EncryptionContext
|
||||
|
||||
__version__ = "0.4.3"
|
||||
__version__ = "0.4.4"
|
||||
|
||||
__all__ = [
|
||||
"Auth",
|
||||
|
||||
@@ -24,7 +24,7 @@ from langchain_core.language_models.chat_model_stream import AsyncChatModelStrea
|
||||
from langchain_protocol import Event, SubscribeParams
|
||||
|
||||
from langgraph_sdk._async.http import HttpClient
|
||||
from langgraph_sdk.schema import QueryParamTypes
|
||||
from langgraph_sdk.schema import LangSmithTracing, QueryParamTypes
|
||||
from langgraph_sdk.stream.controller import _SeenEventIds
|
||||
from langgraph_sdk.stream.decoders import (
|
||||
DataDecoder,
|
||||
@@ -172,6 +172,7 @@ class RunModule:
|
||||
input: Any = None,
|
||||
config: dict[str, Any] | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
langsmith_tracing: LangSmithTracing | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Send `run.start` to the server. Returns the result (`{"run_id": ...}`)."""
|
||||
params: dict[str, Any] = {"assistant_id": self._owner.assistant_id}
|
||||
@@ -181,6 +182,8 @@ class RunModule:
|
||||
params["config"] = config
|
||||
if metadata is not None:
|
||||
params["metadata"] = metadata
|
||||
if langsmith_tracing is not None:
|
||||
params["langsmith_tracer"] = langsmith_tracing
|
||||
loop = asyncio.get_running_loop()
|
||||
gate: asyncio.Future[None] = loop.create_future()
|
||||
self._owner._run_start_ready = gate
|
||||
|
||||
@@ -23,7 +23,7 @@ from langchain_core.language_models.chat_model_stream import ChatModelStream
|
||||
from langchain_protocol import Event, SubscribeParams
|
||||
|
||||
from langgraph_sdk._sync.http import SyncHttpClient
|
||||
from langgraph_sdk.schema import QueryParamTypes
|
||||
from langgraph_sdk.schema import LangSmithTracing, QueryParamTypes
|
||||
from langgraph_sdk.stream.decoders import (
|
||||
DataDecoder,
|
||||
Decoder,
|
||||
@@ -215,6 +215,7 @@ class SyncRunModule:
|
||||
input: Any = None,
|
||||
config: dict[str, Any] | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
langsmith_tracing: LangSmithTracing | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Send `run.start` to the server. Returns the result (`{"run_id": ...}`)."""
|
||||
params: dict[str, Any] = {"assistant_id": self._owner.assistant_id}
|
||||
@@ -224,6 +225,8 @@ class SyncRunModule:
|
||||
params["config"] = config
|
||||
if metadata is not None:
|
||||
params["metadata"] = metadata
|
||||
if langsmith_tracing is not None:
|
||||
params["langsmith_tracer"] = langsmith_tracing
|
||||
result = self._owner._send_command("run.start", params)
|
||||
self._owner._run_seen = True
|
||||
controller = self._owner._controller
|
||||
|
||||
@@ -341,9 +341,15 @@ VUpdate = typing.TypeVar("VUpdate", covariant=True)
|
||||
VRead = typing.TypeVar("VRead", covariant=True)
|
||||
VDelete = typing.TypeVar("VDelete", covariant=True)
|
||||
VSearch = typing.TypeVar("VSearch", covariant=True)
|
||||
ResourceActionT = typing.TypeVar("ResourceActionT", bound=str)
|
||||
|
||||
_ResourceAction = typing.Literal["create", "read", "update", "delete", "search"]
|
||||
_ThreadAction = _ResourceAction | typing.Literal["create_run"]
|
||||
|
||||
|
||||
class _ResourceOn(typing.Generic[VCreate, VRead, VUpdate, VDelete, VSearch]):
|
||||
class _ResourceOn(
|
||||
typing.Generic[VCreate, VRead, VUpdate, VDelete, VSearch, ResourceActionT]
|
||||
):
|
||||
"""
|
||||
Generic base class for resource-specific handlers.
|
||||
"""
|
||||
@@ -392,8 +398,8 @@ class _ResourceOn(typing.Generic[VCreate, VRead, VUpdate, VDelete, VSearch]):
|
||||
def __call__(
|
||||
self,
|
||||
*,
|
||||
resources: str | Sequence[str],
|
||||
actions: str | Sequence[str] | None = None,
|
||||
resources: str | Sequence[str] | None = None,
|
||||
actions: ResourceActionT | Sequence[ResourceActionT] | None = None,
|
||||
) -> Callable[
|
||||
[_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]],
|
||||
_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch],
|
||||
@@ -408,7 +414,7 @@ class _ResourceOn(typing.Generic[VCreate, VRead, VUpdate, VDelete, VSearch]):
|
||||
) = None,
|
||||
*,
|
||||
resources: str | Sequence[str] | None = None,
|
||||
actions: str | Sequence[str] | None = None,
|
||||
actions: ResourceActionT | Sequence[ResourceActionT] | None = None,
|
||||
) -> (
|
||||
_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]
|
||||
| Callable[
|
||||
@@ -416,24 +422,66 @@ class _ResourceOn(typing.Generic[VCreate, VRead, VUpdate, VDelete, VSearch]):
|
||||
_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch],
|
||||
]
|
||||
):
|
||||
if fn is not None:
|
||||
_validate_handler(fn)
|
||||
return typing.cast(
|
||||
"_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]",
|
||||
_register_handler(self.auth, self.resource, "*", fn),
|
||||
)
|
||||
|
||||
def decorator(
|
||||
handler: _ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch],
|
||||
) -> _ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]:
|
||||
_validate_handler(handler)
|
||||
return typing.cast(
|
||||
"_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]",
|
||||
_register_handler(self.auth, self.resource, "*", handler),
|
||||
if resources is None:
|
||||
resource_list = [self.resource]
|
||||
elif isinstance(resources, str):
|
||||
resource_list = [resources]
|
||||
elif isinstance(resources, Sequence):
|
||||
resource_list = list(resources)
|
||||
else:
|
||||
raise TypeError("resources must be a string or sequence of strings")
|
||||
if resource_list != [self.resource]:
|
||||
raise ValueError(
|
||||
f"Resource-specific decorator for {self.resource!r} cannot "
|
||||
f"register handlers for {resource_list!r}. Use @auth.on(...) "
|
||||
"for other or multiple resources."
|
||||
)
|
||||
if actions is None:
|
||||
action_list = ["*"]
|
||||
elif isinstance(actions, str):
|
||||
action_list = [actions]
|
||||
elif isinstance(actions, Sequence):
|
||||
action_list = list(actions)
|
||||
else:
|
||||
raise TypeError("actions must be a string or sequence of strings")
|
||||
if not action_list:
|
||||
raise ValueError("actions must not be empty")
|
||||
if not all(isinstance(action, str) for action in action_list):
|
||||
raise TypeError("actions must be a string or sequence of strings")
|
||||
valid_actions = {
|
||||
value.action
|
||||
for value in vars(self).values()
|
||||
if isinstance(value, _ResourceActionOn)
|
||||
}
|
||||
invalid_actions = (
|
||||
sorted(set(action_list) - valid_actions) if actions is not None else []
|
||||
)
|
||||
if invalid_actions:
|
||||
raise ValueError(
|
||||
f"Invalid action(s) for {self.resource}: {', '.join(invalid_actions)}"
|
||||
)
|
||||
if len(action_list) != len(set(action_list)):
|
||||
raise ValueError("actions must not contain duplicates")
|
||||
for action in action_list:
|
||||
if (self.resource, action) in self.auth._handlers:
|
||||
raise ValueError(
|
||||
f"types.Handler already set for {self.resource}, {action}."
|
||||
)
|
||||
for action in action_list:
|
||||
_register_handler(self.auth, self.resource, action, handler)
|
||||
return handler
|
||||
|
||||
# Accept keyword-only parameters for future filtering behavior; referenced to satisfy linters.
|
||||
_ = resources, actions
|
||||
if fn is not None:
|
||||
return decorator(
|
||||
typing.cast(
|
||||
"_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]",
|
||||
fn,
|
||||
)
|
||||
)
|
||||
return decorator
|
||||
|
||||
|
||||
@@ -444,6 +492,7 @@ class _AssistantsOn(
|
||||
types.AssistantsUpdate,
|
||||
types.AssistantsDelete,
|
||||
types.AssistantsSearch,
|
||||
_ResourceAction,
|
||||
]
|
||||
):
|
||||
value = (
|
||||
@@ -467,6 +516,7 @@ class _ThreadsOn(
|
||||
types.ThreadsUpdate,
|
||||
types.ThreadsDelete,
|
||||
types.ThreadsSearch,
|
||||
_ThreadAction,
|
||||
]
|
||||
):
|
||||
value = (
|
||||
@@ -502,6 +552,7 @@ class _CronsOn(
|
||||
types.CronsUpdate,
|
||||
types.CronsDelete,
|
||||
types.CronsSearch,
|
||||
_ResourceAction,
|
||||
]
|
||||
):
|
||||
value = type[
|
||||
|
||||
@@ -426,11 +426,17 @@ def test_sync_run_start_sends_command():
|
||||
with httpx.Client(transport=fake.transport, base_url="http://test") as raw:
|
||||
threads = SyncThreadsClient(SyncHttpClient(raw))
|
||||
with threads.stream(thread_id="t-1", assistant_id="agent") as thread:
|
||||
result = thread.run.start(input={"x": 1})
|
||||
result = thread.run.start(
|
||||
input={"x": 1},
|
||||
langsmith_tracing={"project_name": "replica-project"},
|
||||
)
|
||||
|
||||
assert result == {"run_id": "run-1"}
|
||||
assert fake.received_commands[0]["method"] == "run.start"
|
||||
assert fake.received_commands[0]["params"]["assistant_id"] == "agent"
|
||||
assert fake.received_commands[0]["params"]["langsmith_tracer"] == {
|
||||
"project_name": "replica-project"
|
||||
}
|
||||
|
||||
|
||||
def test_sync_events_iterates_raw_events():
|
||||
|
||||
@@ -287,7 +287,7 @@ async def test_command_ids_are_monotonic():
|
||||
assert [c["id"] for c in fake.received_commands] == [1, 2]
|
||||
|
||||
|
||||
async def test_run_start_forwards_config_and_metadata():
|
||||
async def test_run_start_forwards_config_metadata_and_langsmith_tracing():
|
||||
fake = FakeServer()
|
||||
transport = httpx.ASGITransport(app=fake.app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://test") as raw:
|
||||
@@ -297,10 +297,18 @@ async def test_run_start_forwards_config_and_metadata():
|
||||
input={"x": 1},
|
||||
config={"recursion_limit": 5},
|
||||
metadata={"trace": "abc"},
|
||||
langsmith_tracing={
|
||||
"project_name": "replica-project",
|
||||
"example_id": "example-1",
|
||||
},
|
||||
)
|
||||
params = fake.received_commands[0]["params"]
|
||||
assert params["config"] == {"recursion_limit": 5}
|
||||
assert params["metadata"] == {"trace": "abc"}
|
||||
assert params["langsmith_tracer"] == {
|
||||
"project_name": "replica-project",
|
||||
"example_id": "example-1",
|
||||
}
|
||||
|
||||
|
||||
async def test_run_start_raises_outside_context_manager():
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import pytest
|
||||
|
||||
from langgraph_sdk import Auth
|
||||
|
||||
|
||||
def test_handler_multiple_resources_and_actions() -> None:
|
||||
auth = Auth()
|
||||
|
||||
@auth.on(resources=["threads", "assistants"], actions=["read", "search"])
|
||||
async def allow_reads(ctx, value):
|
||||
del value
|
||||
return {"owner": ctx.user.identity}
|
||||
|
||||
assert auth._handlers == {
|
||||
("threads", "read"): [allow_reads],
|
||||
("threads", "search"): [allow_reads],
|
||||
("assistants", "read"): [allow_reads],
|
||||
("assistants", "search"): [allow_reads],
|
||||
}
|
||||
|
||||
|
||||
def test_resource_handler_actions_are_scoped() -> None:
|
||||
auth = Auth()
|
||||
|
||||
@auth.on
|
||||
async def deny_all(ctx, value):
|
||||
del ctx, value
|
||||
return False
|
||||
|
||||
@auth.on.threads(actions=["create", "search"])
|
||||
async def handler(ctx, value):
|
||||
del ctx, value
|
||||
return None
|
||||
|
||||
@auth.on.threads(actions="create_run")
|
||||
async def run_handler(ctx, value):
|
||||
del ctx, value
|
||||
return None
|
||||
|
||||
assert auth._handlers == {
|
||||
("threads", "create"): [handler],
|
||||
("threads", "search"): [handler],
|
||||
("threads", "create_run"): [run_handler],
|
||||
}
|
||||
assert auth._global_handlers == [deny_all]
|
||||
|
||||
|
||||
def test_resource_handler_preserves_wildcard() -> None:
|
||||
auth = Auth()
|
||||
|
||||
@auth.on.threads
|
||||
async def handler(ctx, value):
|
||||
del ctx, value
|
||||
return None
|
||||
|
||||
assert auth._handlers == {("threads", "*"): [handler]}
|
||||
|
||||
|
||||
def test_resource_handler_preserves_wildcard_with_parentheses() -> None:
|
||||
auth = Auth()
|
||||
|
||||
@auth.on.threads()
|
||||
async def handler(ctx, value):
|
||||
del ctx, value
|
||||
return None
|
||||
|
||||
assert auth._handlers == {("threads", "*"): [handler]}
|
||||
|
||||
|
||||
def test_resource_handler_accepts_matching_resource() -> None:
|
||||
auth = Auth()
|
||||
|
||||
@auth.on.threads(resources=["threads"], actions="read")
|
||||
async def handler(ctx, value):
|
||||
del ctx, value
|
||||
return None
|
||||
|
||||
assert auth._handlers == {("threads", "read"): [handler]}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"resources", [["assistants"], ["threads", "assistants"], [], [1]]
|
||||
)
|
||||
def test_resource_handler_rejects_nonmatching_resources(resources) -> None:
|
||||
auth = Auth()
|
||||
|
||||
async def handler(ctx, value):
|
||||
del ctx, value
|
||||
return None
|
||||
|
||||
with pytest.raises(ValueError, match=r"Use @auth\.on"):
|
||||
auth.on.threads(resources=resources)(handler)
|
||||
assert auth._handlers == {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("resource", "actions", "error"),
|
||||
[
|
||||
("threads", [], ValueError),
|
||||
("threads", ["reed"], ValueError),
|
||||
("threads", ["create", "create"], ValueError),
|
||||
("threads", {"create": True}, TypeError),
|
||||
("crons", ["create_run"], ValueError),
|
||||
],
|
||||
)
|
||||
def test_resource_handler_rejects_invalid_actions(resource, actions, error) -> None:
|
||||
auth = Auth()
|
||||
|
||||
async def handler(ctx, value):
|
||||
del ctx, value
|
||||
return None
|
||||
|
||||
with pytest.raises(error):
|
||||
getattr(auth.on, resource)(actions=actions)(handler)
|
||||
assert auth._handlers == {}
|
||||
|
||||
|
||||
def test_resource_handler_registration_is_atomic() -> None:
|
||||
auth = Auth()
|
||||
|
||||
@auth.on.threads.read
|
||||
async def read_handler(ctx, value):
|
||||
del ctx, value
|
||||
return None
|
||||
|
||||
async def handler(ctx, value):
|
||||
del ctx, value
|
||||
return None
|
||||
|
||||
with pytest.raises(ValueError, match="already set"):
|
||||
auth.on.threads(actions=["create", "read"])(handler)
|
||||
assert auth._handlers == {("threads", "read"): [read_handler]}
|
||||
Reference in New Issue
Block a user