1

I want to do something like this:

def need_session(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        session = SessionLocal()
        try:
            func(session, *args, **kwargs)
        finally:
            session.close()

I'm using the wraps function, because I'm using Strawberry, which depends on argument types.

As you can see, func is given an extra session argument.

How can I make this work?

If I try to run a GraphQL server using Strawberry that has functions that are decorated using the decorator above, it gives me this error:

TypeError: Mutation fields cannot be resolved. Unexpected type '<class 'sqlalchemy.orm.session.Session'>'

If I change the decorator to this:

def need_session(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        session = SessionLocal()
        kwargs['session'] = session
        try:
            func(*args, **kwargs)
        finally:
            session.close()

And change the decorated function's argument list to:

def some_func(some_arg: SomeClass, **kwargs):
    ...

I still get this error:

strawberry.exceptions.MissingArgumentsAnnotationsError: Missing annotation for argument "kwargs" in field "login_user", did you forget to add it?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
pooooky
  • 490
  • 2
  • 12

1 Answers1

1

Why the error?

You get this exception because Strawberry doesn't support **kwargs and every strawberry.field must have annotations for all arguments. And they are processed by the strawberry.type decorator, so you can’t modify the function signature.

Possible solution 1:

You can provide the session inside the Strawberry Info by writing a schema extension.

Example:

from strawberry.extensions import Extension
from mydb import get_db_session


class DbSessionMiddleWare(Extension):
    def on_request_start(self):
        self.execution_context.context["db"] = get_db_session()

    def on_request_end(self):
        self.execution_context.context["db"].close()

schema = strawberry.Schema(
    query=Queries,
    mutation=Mutations,
    extensions=[
        DbSessionMiddleWare,
    ],
)

Some resolver:

from strawberry.types import Info


def resolve_name(info: Info):
    name = info.context['db'].execute("SELECT ...")

Possible solution 2:

You can wrap Strawberry decorators with your own decorator and inject some annotations as you wish.
Remember that you should clear any *args or **kwargs from the function signature other ways strawberry would complain about this.

Example:

from strawberry.arguments import StrawberryArgument, StrawberryAnnotation, UNSET
import inspect

def create_strawberry_argument(
    python_name: str, graphql_name: str, type_, default=None
):
    return StrawberryArgument(
        python_name=python_name,
        graphql_name=graphql_name,
        type_annotation=StrawberryAnnotation(type_),
        default=default or UNSET,
    )

def hide_args_kwargs(field):

    sig = inspect.signature(field)
    cleared = tuple(
        p for p in sig.parameters.values() if p.name not in ("kwargs", "args")
    )
    field.__signature__ = inspect.signature(field).replace(parameters=(cleared))
    return field

def inject_mutation(field, arguments: tuple[str, type]):
    field = hide_args_kwargs(field)
    field = strawberry.mutation(field, description=field.__doc__)
    for arg_tuple in arguments:
        arg = create_strawberry_argument(arg_tuple[0], arg_tuple[0], arg_tuple[1])
        field.arguments.append(arg)
    return field

@inject_mutation(('arg2', int))
def some_resolver(info, arg1, **kwargs) -> SomeOutputType:
    f = kwargs.get('arg2', None)
ניר
  • 1,204
  • 1
  • 8
  • 28
  • 1
    Thanks for giving the explanation - to me this whole decorator based approach in strawberry seems to me very *unpythonic* because a lot of stuff happens implicit in the background, there is no IDE support and you can not interfere with it in a trivial manner. Do you have any insights what the advantages of such an approach are? – capitalg Nov 07 '22 at 14:49
  • 1
    1. clean syntax. 2. dataclasses style. 3. simpler for developers (instaed of using metaclasses hacks). BTW there is IDE support for both PyCharm and VS code. @capitalg of course there is a price that it is less flexible user wise.. – ניר Nov 07 '22 at 16:51
  • After working some weeks with strawberry now I think its decorator based approach is its biggest weakness, at least it gives me lots of frustration - its not really a dataclass as there is logic inside and the example above shows that a class is much more easy to inspect, handle, debug and modify than a decorator/function. It is also unclear what happens behind the curtains without reading closely the source code as inspection of a function/object is much more limited at "compile"time than that of a class, so I think the IDE support is indeed non existent – capitalg Nov 08 '22 at 15:47
  • 1
    @capitalg I totally agree and I have tried in my PR to move to more class driven approuch though it is not about to be merged any time soon... Ref: https://github.com/strawberry-graphql/strawberry/pull/2117 – ניר Nov 08 '22 at 16:27
  • And IDE support - https://strawberry.rocks/docs/editors/vscode#install-pylance – ניר Nov 08 '22 at 16:31