Repository: strawberry-graphql/strawberry

Issue:
Only pass info when resolver asks for it
I was thinking of making the API slightly easier for resolvers

so instead of having:

```python
@strawberry.type
class Query:
    @strawberry.field
    def user(self, info) -> User:
        return User(name="Patrick", age=100)
```

we could have this:

```python
@strawberry.type
class Query:
    @strawberry.field
    def user(self) -> User:
        return User(name="Patrick", age=100)
```

the difference is minimal, but it should make the library slightly more user-friendly 😊


Discussion:
one example of practical usage of `info` is queries optimization like in here https://github.com/tfoxy/graphene-django-optimizer

and also context is attached to info, which is sometimes used to share some data in one query context

I am not against the idea, just wanted to point out usages of `info` in resolvers.
Yup, info is also useful for getting the current user for example. The idea here is to only pass it when it is in the resolver signature, kinda like dependency injection. If you use info it will be there, if you don’t use it, then you don’t have to put in the function signature. 

Same for the root object when using function resolvers (as opposed to method resolvers). 

Hope this is clearer (I’m on mobile otherwise I’d drop a couple of code samples). 
Cool, in general, I like that concept. What about queries with arguments and the order of arguments? Could `info` be placed in whatever position or always after self? 
@mtszsobczak we would pass info as a keyword argument, so the position won't matter anymore:

```python
# not using info
@strawberry.type
class Query:
    @strawberry.field
    def user(self) -> User:
        return User(name="Patrick", age=100)
```

```python
# using info
@strawberry.type
class Query:
    @strawberry.field
    def user(self, info) -> User:
        return User(name=info.context.request.user.name, age=100)
```

```python
# not using info, with args
@strawberry.type
class Query:
    @strawberry.field
    def user(self, age: int) -> User:
        return User(name="Patrick", age=age)
```

```python
# using info, with args
@strawberry.type
class Query:
    @strawberry.field
    def user(self, age: int, info) -> User:
        return User(name=info.context.request.user.name, age=age)
```

I think where this gets even nicer is with function resolvers:

```python
# current function resolvers:
def get_user(root, info):
    return User(name="Patrick", age=age)

@strawberry.type
class Query:
    user: User = strawberry.field(resolver=get_user)
```


```python
# without passing root and info:
def get_user():
    return User(name="Patrick", age=age)

@strawberry.type
class Query:
    user: User = strawberry.field(resolver=get_user)
```



It simplifies basic usage and it does not limit the API :+1: . My only concern is that users might be confused by the dynamic position of info. But it might not be an issue after all.
How about context variables? https://docs.python.org/3/library/contextvars.html#module-contextvars

It would require 3.7+ but that wouldn't be a bad thing for such a young project.

```python

@strawberry.type
class User:
  @strawberry.field
  def resolver(self):
    request = strawberry.context.get().request
```
@sarendsen interesting, I'm not familiar with contextvars, at a quick glance it looks a bit like flask's request object, am I right?


@patrick91 The idea is similar. But context vars being async safe. 


@sarendsen I see, I'm a bit conflicted, to be honest, I like the idea, but it feels a bit magic 😊

Do you have a strong opinion on this? I'd personally chose the function params if I have to :)

@jaydenwindle @marcoacierno @mtszsobczak what do you think?

Hey, context vars look cool, but as you mentioned a bit magic.

I would also prefer function params as they look easier to reason about and test + the flow of information is explicit. But I didn't put much time for thinking about the context vars and using global context concept. I am curious to see how both approaches would scale taking into account testing :cat:


I think the magic feeling lies in the fact that its a quit new python feature?
I actually quit like it and expect it to see it a lot more in the async world. I think its heavily inspired by Go. (don't quote me on this)


Heres a more complete example that hopefully shows that the actual use is not that magical.

```python
@dataclasses.dataclass
class Context:
    schema: GraphQLSchema
    request: Optional[Any] = None
    variable_values: Optional[Any] = None

```

```python
from strawberry.context import Context

context: contextvars.ContextVar[Context] = contextvars.ContextVar('context')

async def execute(
    schema: GraphQLSchema,
    query: str,
    context_value: typing.Any = None,
    variable_values: typing.Dict[str, typing.Any] = None,
    operation_name: str = None,
):
   ...
    context.set(
        Context(
            schema = schema,
            request = request,
            variable_values = variable_values
        )
    )
   ...
```


```python
from strawberry import context, field, type

@type
class User:

    @field
    def name(self):
        schema = context.get().schema
```
> I think the magic feeling lies in the fact that its a quit new python feature?

it is more about having request information coming from a "global" variable, if that makes sense

I'm going to read a bit about this, I'm +1 on using new features, but not sure in this case we really need it 😊

Resolving patch:
diff --git a/strawberry/field.py b/strawberry/field.py
--- a/strawberry/field.py
+++ b/strawberry/field.py
@@ -214,8 +214,27 @@ def resolver(source, info, **kwargs):
         if check_permission:
             check_permission(source, info, **kwargs)
 
+        # the following code allows to omit info and root arguments
+        # by inspecting the original resolver arguments,
+        # if it asks for self, the source will be passed as first argument
+        # if it asks for root, the source it will be passed as kwarg
+        # if it asks for info, the info will be passed as kwarg
+
         kwargs = convert_args(kwargs, arguments_annotations)
-        result = wrap(source, info, **kwargs)
+        function_args = get_func_args(wrap)
+
+        args = []
+
+        if "self" in function_args:
+            args.append(wrap)
+
+        if "root" in function_args:
+            kwargs["root"] = source
+
+        if "info" in function_args:
+            kwargs["info"] = info
+
+        result = wrap(*args, **kwargs)
 
         # graphql-core expects a resolver for an Enum type to return
         # the enum's *value* (not its name or an instance of the enum).


Test patch:
diff --git a/tests/test_schema.py b/tests/test_schema.py
--- a/tests/test_schema.py
+++ b/tests/test_schema.py
@@ -1,11 +1,11 @@
 import textwrap
 import typing
+from dataclasses import InitVar, dataclass
 from enum import Enum
 
 import pytest
 
 import strawberry
-from dataclasses import InitVar, dataclass
 from graphql import DirectiveLocation, graphql, graphql_sync
 
 
@@ -499,7 +499,7 @@ def assortment(self, info) -> typing.List[Cheese]:
     ]
 
 
-def test_can_return_compatibile_type():
+def test_can_return_compatible_type():
     """Test that we can return a different type that has the same fields,
     for example when returning a Django Model."""
 
@@ -529,3 +529,95 @@ def assortment(self, info) -> Cheese:
 
     assert not result.errors
     assert result.data["assortment"]["name"] == "Asiago"
+
+
+def test_optional_info_and_root_params_function_resolver():
+    def function_resolver() -> str:
+        return "I'm a function resolver"
+
+    def function_resolver_with_root(root) -> str:
+        return root._example
+
+    def function_resolver_with_params(x: str) -> str:
+        return f"I'm {x}"
+
+    @strawberry.type
+    class Query:
+        hello: str = strawberry.field(resolver=function_resolver)
+        hello_with_root: str = strawberry.field(resolver=function_resolver_with_root)
+        hello_with_params: str = strawberry.field(
+            resolver=function_resolver_with_params
+        )
+
+        def __post_init__(self):
+            self._example = "Example"
+
+    schema = strawberry.Schema(query=Query)
+
+    query = """{
+        hello
+        helloWithRoot
+        helloWithParams(x: "abc")
+    }"""
+
+    result = graphql_sync(schema, query, root_value=Query())
+
+    assert not result.errors
+    assert result.data["hello"] == "I'm a function resolver"
+    assert result.data["helloWithParams"] == "I'm abc"
+    assert result.data["helloWithRoot"] == "Example"
+
+
+def test_optional_info_and_root_params():
+    @strawberry.type
+    class Query:
+        @strawberry.field
+        def hello(self) -> str:
+            return "I'm a function resolver"
+
+        @strawberry.field
+        def hello_with_params(self, x: str) -> str:
+            return f"I'm {x}"
+
+    schema = strawberry.Schema(query=Query)
+
+    query = """{
+        hello
+        helloWithParams(x: "abc")
+    }"""
+
+    result = graphql_sync(schema, query)
+
+    assert not result.errors
+    assert result.data["hello"] == "I'm a function resolver"
+    assert result.data["helloWithParams"] == "I'm abc"
+
+
+def test_only_info_function_resolvers():
+    def function_resolver(info) -> str:
+        return f"I'm a function resolver for {info.field_name}"
+
+    def function_resolver_with_params(info, x: str) -> str:
+        return f"I'm {x} for {info.field_name}"
+
+    @strawberry.type
+    class Query:
+        hello: str = strawberry.field(resolver=function_resolver)
+        hello_with_params: str = strawberry.field(
+            resolver=function_resolver_with_params
+        )
+
+    schema = strawberry.Schema(query=Query)
+
+    query = """{
+        hello
+        helloWithParams(x: "abc")
+    }"""
+
+    result = graphql_sync(schema, query)
+
+    assert not result.errors
+    assert result.data["hello"] == "I'm a function resolver for hello"
+    # TODO: in future, should we map names of info.field_name to the matching
+    # dataclass field name?
+    assert result.data["helloWithParams"] == "I'm abc for helloWithParams"