authzed-py icon indicating copy to clipboard operation
authzed-py copied to clipboard

Async Insecure Client

Open Sjhunt93 opened this issue 10 months ago • 4 comments

When using the InsecureClient it does not correctly create async channel. After establishing a channel with fake_credentials the following code defaults to using a sync connection:

insecure_channel = grpc.insecure_channel(target, options, compression)

The class method create_channel switches automatically between grpc. and grpc.aio. Should this behaviour also apply in the insecure client?

Sjhunt93 avatar Feb 26 '25 10:02 Sjhunt93

async def rr():

    client = Client(
        "localhost:50051",
        insecure_bearer_token_credentials("123")
    )

    post_one = ObjectReference(object_type="org/brief", object_id="1")
    emilia = SubjectReference(object=ObjectReference(
        object_type="org/tag/user",
        object_id="emilia",
    ))

    resp = await client.CheckPermission(CheckPermissionRequest(
        resource=post_one,
        permission="viewer",
        subject=emilia,
    ))
    assert resp.permissionship != CheckPermissionResponse.PERMISSIONSHIP_HAS_PERMISSION

# force async loop
asyncio.run(rr())

The above works as expected

Running the same but with:

    client = InsecureClient(
        "localhost:50051",
        "123"
    )

results in:

TypeError: object CheckPermissionResponse can't be used in 'await' expression

Sjhunt93 avatar Feb 26 '25 11:02 Sjhunt93

I'll have a look at this.

tstirrat15 avatar Mar 03 '25 23:03 tstirrat15

I've got a PR in progress... the python gRPC client makes this a pain.

tstirrat15 avatar Mar 07 '25 19:03 tstirrat15

Related to https://github.com/grpc/grpc/issues/33618

tstirrat15 avatar Mar 07 '25 22:03 tstirrat15

This code helped for me


class BaseAuthInterceptor:
    def __init__(self, token: str):
        self._token = token

    def _add_auth(self, client_call_details):
        metadata = list(client_call_details.metadata or [])
        metadata.append(("authorization", f"Bearer {self._token}"))

        return grpc.aio.ClientCallDetails(
            method=client_call_details.method,
            timeout=client_call_details.timeout,
            metadata=metadata,
            credentials=client_call_details.credentials,
            wait_for_ready=client_call_details.wait_for_ready,
        )


class AsyncAuthUnaryUnaryInterceptor(
    BaseAuthInterceptor,
    grpc.aio.UnaryUnaryClientInterceptor,
):
    async def intercept_unary_unary(self, continuation, client_call_details, request):
        return await continuation(self._add_auth(client_call_details), request)


class AsyncAuthUnaryStreamInterceptor(
    BaseAuthInterceptor,
    grpc.aio.UnaryStreamClientInterceptor,
):
    async def intercept_unary_stream(self, continuation, client_call_details, request):
        return await continuation(self._add_auth(client_call_details), request)


class AsyncAuthStreamUnaryInterceptor(
    BaseAuthInterceptor,
    grpc.aio.StreamUnaryClientInterceptor,
):
    async def intercept_stream_unary(self, continuation, client_call_details, request_iterator):
        return await continuation(self._add_auth(client_call_details), request_iterator)


class AsyncAuthStreamStreamInterceptor(
    BaseAuthInterceptor,
    grpc.aio.StreamStreamClientInterceptor,
):
    async def intercept_stream_stream(self, continuation, client_call_details, request_iterator):
        return await continuation(self._add_auth(client_call_details), request_iterator)


class AsyncInsecureClient(Client):
    def __init__(self, target: str, token: str, options=None, compression=None):
        channel = grpc.aio.insecure_channel(
            target,
            options,
            compression,
            [
                AsyncAuthUnaryUnaryInterceptor(token),
                AsyncAuthUnaryStreamInterceptor(token),
                AsyncAuthStreamUnaryInterceptor(token),
                AsyncAuthStreamStreamInterceptor(token),
            ],
        )

        self.init_stubs(channel)

Created this with chatgpt and not tested much, but in my scenario it is working

pavel7002007 avatar Jul 30 '25 10:07 pavel7002007

+1 for @pavel7002007 solution

chero858 avatar Oct 16 '25 11:10 chero858