Compare commits

...
Author SHA1 Message Date
John Kennedyandopen-swe[bot] <open-swe@users.noreply.github.com> e837585311 fix: honor resource auth actions
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-07-09 03:21:57 +00:00
2 changed files with 73 additions and 16 deletions
+29 -16
View File
@@ -392,7 +392,7 @@ class _ResourceOn(typing.Generic[VCreate, VRead, VUpdate, VDelete, VSearch]):
def __call__( def __call__(
self, self,
*, *,
resources: str | Sequence[str], resources: str | Sequence[str] | None = None,
actions: str | Sequence[str] | None = None, actions: str | Sequence[str] | None = None,
) -> Callable[ ) -> Callable[
[_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]], [_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]],
@@ -416,25 +416,38 @@ class _ResourceOn(typing.Generic[VCreate, VRead, VUpdate, VDelete, VSearch]):
_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch], _ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch],
] ]
): ):
if fn is not None: def register(
_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], handler: _ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch],
) -> _ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]: ) -> _ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]:
_validate_handler(handler) _validate_handler(handler)
return typing.cast( if isinstance(resources, str):
"_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]", resource_list = [resources]
_register_handler(self.auth, self.resource, "*", handler), else:
) resource_list = (
list(resources) if resources is not None else [self.resource]
)
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 multiple resources."
)
if isinstance(actions, str):
action_list = [actions]
else:
action_list = list(actions) if actions is not None else ["*"]
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. if fn is not None:
_ = resources, actions return register(
return decorator typing.cast(
"_ActionHandler[VCreate | VUpdate | VRead | VDelete | VSearch]",
fn,
)
)
return register
class _AssistantsOn( class _AssistantsOn(
+44
View File
@@ -0,0 +1,44 @@
import pytest
from langgraph_sdk import Auth
def _handler():
async def handler(ctx, value):
return ctx is not None and value is not None
return handler
def test_resource_decorator_registers_specific_actions():
auth = Auth()
handler = auth.on.threads(actions=["read", "search"])(_handler())
assert auth._handlers == {
("threads", "read"): [handler],
("threads", "search"): [handler],
}
def test_resource_decorator_registers_single_action():
auth = Auth()
handler = auth.on.threads(actions="read")(_handler())
assert auth._handlers == {("threads", "read"): [handler]}
def test_resource_decorator_without_actions_registers_resource_wildcard():
auth = Auth()
handler = auth.on.threads(_handler())
assert auth._handlers == {("threads", "*"): [handler]}
def test_resource_decorator_rejects_mismatched_resources():
auth = Auth()
with pytest.raises(ValueError, match=r"Use @auth\.on"):
auth.on.threads(resources="assistants", actions="read")(_handler())