ariadne-codegen icon indicating copy to clipboard operation
ariadne-codegen copied to clipboard

Can we generate client with Sync and ASync function

Open imadmoussa1 opened this issue 2 years ago • 3 comments

I am using the codegen to create a python client, in my schema I have subscriptions, query and mutations. The subscriptions should be Async but at the same time need the query and mutations to be sync. is there a way to do it ?

imadmoussa1 avatar Dec 15 '23 09:12 imadmoussa1

Hey, currently we don't support mixing of sync and async methods in generated client, but it can be done using plugins API and custom base client options, e.g:

Plugin:

import ast
from typing import Union, cast

from graphql import OperationDefinitionNode, OperationType

from ariadne_codegen.plugins.base import Plugin


class MixedClientPlugin(Plugin):
    def generate_client_method(
        self,
        method_def: Union[ast.FunctionDef, ast.AsyncFunctionDef],
        operation_definition: OperationDefinitionNode,
    ) -> Union[ast.FunctionDef, ast.AsyncFunctionDef]:
        if operation_definition.operation == OperationType.SUBSCRIPTION or isinstance(
            method_def, ast.FunctionDef
        ):
            return method_def  # we do not change anything for subscriptions or already sync methods

        sync_method_def = ast.FunctionDef(
            name=method_def.name,
            args=method_def.args,
            body=method_def.body,
            decorator_list=method_def.decorator_list,
            returns=method_def.returns,
            lineno=method_def.lineno,
        )  # we create sync method with the same data as async source method
        async_execute_assign = cast(
            ast.Assign, method_def.body[-3]
        )  # ast for `response = await `self.execute(...)``
        sync_method_def.body[-3] = ast.Assign(
            targets=async_execute_assign.targets,
            value=cast(ast.Await, async_execute_assign.value).value,
            lineno=async_execute_assign.lineno,
        )  # we "unpack" await, `response = await self.execute(...)` becomes `response = self.execute(...)`
        return sync_method_def

A compatible base client, should have async execute_ws and sync execute.

Assuming this base client is called CustomMixedBaseClient then configuration will look like this:

[tool.ariadne-codegen]
...

base_client_name = "CustomMixedBaseClient"
base_client_file_path = ".../custom_mixed_base_client.py"
async_client = true

plugins = ["....MixedClientPlugin"]

mat-sop avatar Dec 15 '23 11:12 mat-sop

Shower though: what if base client's code was a simple template that we would render a final Python file from when client package is created? Template file could be Python file itself, but with simple template logic:

# ARIADNE: ASYNC
... This part will be included if async mode is enabled
# ENDARIADNE
# ARIADNE: SYNC
... This part will be included if sync mode is enabled
# ENDARIADNE

This can be achieved with a simple regex. We would still need to generate sync and async client methods at same time tho. Likely the sync methods would then be suffixed with _sync as is already convention in other GraphQL tooling we are dealing with.

rafalp avatar Apr 25 '24 11:04 rafalp

Would be great to have sync and async client functions generated in one library.

debegr92 avatar May 16 '24 04:05 debegr92