Repository: strawberry-graphql/strawberry

Issue:
Types representation is not helpful
Currently the representations for types and enums aren't really helpful, especially while debugging:

```
>>> import strawberry
>>> @strawberry.input
... class Example:
...     arg: str
...
>>> Example
<class '__main__.Example'>
>>> x = Example("abc")
>>> x
input Example {
  arg: String!
}
```

I think we should move the current representation to be used only when using the class and not an instance, for the instances we can keep what dataclasses is giving us:


    >>> @dataclasses.dataclass()
    ... class Example:
    ...     arg: str
    ...
    >>> Example("abc")
    Example(arg='abc')



Discussion:
This isn't really possible to do using a class decorator, since customizing the `repr` for a type involves metaclasses and you can't change that after the type has been created. There's a few alternatives here:

1. Introduce a metaclass + base class instead of the `strawberry.type` decorator, which would let you do this. Changes how you use the library, diverges from how you expect dataclasses to work, probably not worth it.
2. Use dataclasses' default repr behavior, and add a repr to the actual GraphQL type, i.e. `repr(Example.field)`. Requires either monkeypatching or extending `graphql` types, or upstreaming this behavior to graphql-core-next.
3. Remove all custom `repr` behavior.

I'd personally lean towards #3, or maybe #2 if upstream would be interested in changing `__repr__` on types.
We could have a custom print that works with our types, I don't think graphql-core will want to change behaviours :)

So I'd do 3 plus a utility function that knows how to print our types. I think in future we will probably customise the printer anyway (to support schema directives), so I think it is fine using a custom function :)

Resolving patch:
diff --git a/strawberry/enum.py b/strawberry/enum.py
--- a/strawberry/enum.py
+++ b/strawberry/enum.py
@@ -1,7 +1,6 @@
 from enum import EnumMeta
 
 from graphql import GraphQLEnumType, GraphQLEnumValue
-from graphql.utilities.schema_printer import print_type
 
 from .exceptions import NotAnEnum
 from .type_converter import REGISTRY
@@ -16,11 +15,6 @@ def _process_enum(cls, name=None, description=None):
 
     REGISTRY[name] = cls
 
-    def repr_(self):
-        return print_type(self.field)
-
-    cls.__repr__ = repr_
-
     description = description or cls.__doc__
 
     cls.field = GraphQLEnumType(
diff --git a/strawberry/printer.py b/strawberry/printer.py
new file mode 100644
--- /dev/null
+++ b/strawberry/printer.py
@@ -0,0 +1,7 @@
+from graphql.utilities.schema_printer import print_type as graphql_print
+
+
+def print_type(type_) -> str:
+    """Returns a string representation of a strawberry type"""
+
+    return graphql_print(type_.field)
diff --git a/strawberry/type.py b/strawberry/type.py
--- a/strawberry/type.py
+++ b/strawberry/type.py
@@ -2,7 +2,6 @@
 
 import dataclasses
 from graphql import GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType
-from graphql.utilities.schema_printer import print_type
 
 from .constants import IS_STRAWBERRY_FIELD, IS_STRAWBERRY_INPUT, IS_STRAWBERRY_INTERFACE
 from .field import field, strawberry_field
@@ -35,11 +34,6 @@ def _process_type(cls, *, is_input=False, is_interface=False, description=None):
     name = cls.__name__
     REGISTRY[name] = cls
 
-    def repr_(self):
-        return print_type(self.field)
-
-    cls.__repr__ = repr_
-
     def _get_fields(wrapped):
         class_fields = dataclasses.fields(wrapped)
 
@@ -98,7 +92,7 @@ def _get_fields(wrapped):
             if hasattr(klass, IS_STRAWBERRY_INTERFACE)
         ]
 
-    wrapped = dataclasses.dataclass(cls, repr=False)
+    wrapped = dataclasses.dataclass(cls)
     wrapped.field = TypeClass(name, lambda: _get_fields(wrapped), **extra_kwargs)
 
     return wrapped


Test patch:
diff --git a/tests/test_cyclic/test_cyclic.py b/tests/test_cyclic/test_cyclic.py
--- a/tests/test_cyclic/test_cyclic.py
+++ b/tests/test_cyclic/test_cyclic.py
@@ -1,6 +1,7 @@
 import textwrap
 
 import strawberry
+from strawberry.printer import print_type
 
 
 def test_cyclic_import():
@@ -13,7 +14,7 @@ class Query:
         b: TypeB
 
     assert (
-        repr(Query(None, None))
+        print_type(Query(None, None))
         == textwrap.dedent(
             """
             type Query {
@@ -25,7 +26,7 @@ class Query:
     )
 
     assert (
-        repr(TypeA())
+        print_type(TypeA())
         == textwrap.dedent(
             """
             type TypeA {
@@ -36,7 +37,7 @@ class Query:
     )
 
     assert (
-        repr(TypeB())
+        print_type(TypeB())
         == textwrap.dedent(
             """
             type TypeB {
diff --git a/tests/test_declaring_types.py b/tests/test_declaring_types.py
--- a/tests/test_declaring_types.py
+++ b/tests/test_declaring_types.py
@@ -2,6 +2,7 @@
 from typing import Optional
 
 import strawberry
+from strawberry.printer import print_type
 
 
 def test_simple_required_types():
@@ -13,7 +14,7 @@ class MyType:
         f: float
         id: strawberry.ID
 
-    expected_representation = """
+    expected_type = """
     type MyType {
       s: String!
       i: Int!
@@ -24,8 +25,8 @@ class MyType:
     """
 
     assert (
-        repr(MyType("a", 1, True, 3.2, "123"))
-        == textwrap.dedent(expected_representation).strip()
+        print_type(MyType("a", 1, True, 3.2, "123"))
+        == textwrap.dedent(expected_type).strip()
     )
 
 
@@ -34,13 +35,13 @@ def test_recursive_type():
     class MyType:
         s: "MyType"
 
-    expected_representation = """
+    expected_type = """
     type MyType {
       s: MyType!
     }
     """
 
-    assert repr(MyType("a")) == textwrap.dedent(expected_representation).strip()
+    assert print_type(MyType("a")) == textwrap.dedent(expected_type).strip()
 
 
 def test_optional():
@@ -48,10 +49,10 @@ def test_optional():
     class MyType:
         s: Optional[str]
 
-    expected_representation = """
+    expected_type = """
     type MyType {
       s: String
     }
     """
 
-    assert repr(MyType("a")) == textwrap.dedent(expected_representation).strip()
+    assert print_type(MyType("a")) == textwrap.dedent(expected_type).strip()
diff --git a/tests/test_input_types.py b/tests/test_input_types.py
--- a/tests/test_input_types.py
+++ b/tests/test_input_types.py
@@ -1,6 +1,7 @@
 import textwrap
 
 import strawberry
+from strawberry.printer import print_type
 
 
 def test_simple_required_types():
@@ -12,7 +13,7 @@ class MyInput:
         f: float
         id: strawberry.ID
 
-    expected_representation = """
+    expected_type = """
     input MyInput {
       s: String!
       i: Int!
@@ -23,6 +24,6 @@ class MyInput:
     """
 
     assert (
-        repr(MyInput("a", 1, True, 3.2, "123"))
-        == textwrap.dedent(expected_representation).strip()
+        print_type(MyInput("a", 1, True, 3.2, "123"))
+        == textwrap.dedent(expected_type).strip()
     )
diff --git a/tests/test_interface.py b/tests/test_interface.py
--- a/tests/test_interface.py
+++ b/tests/test_interface.py
@@ -1,6 +1,7 @@
 import textwrap
 
 import strawberry
+from strawberry.printer import print_type
 
 
 def test_interface():
@@ -8,13 +9,13 @@ def test_interface():
     class Node:
         id: strawberry.ID
 
-    expected_representation = """
+    expected_type = """
     interface Node {
       id: ID!
     }
     """
 
-    assert repr(Node("a")) == textwrap.dedent(expected_representation).strip()
+    assert print_type(Node("a")) == textwrap.dedent(expected_type).strip()
 
 
 def test_implementing_interface():
@@ -26,11 +27,11 @@ class Node:
     class Post(Node):
         title: str
 
-    expected_representation = """
+    expected_type = """
     type Post implements Node {
       id: ID!
       title: String!
     }
     """
 
-    assert repr(Post("a", "abc")) == textwrap.dedent(expected_representation).strip()
+    assert print_type(Post("a", "abc")) == textwrap.dedent(expected_type).strip()
diff --git a/tests/test_repr.py b/tests/test_repr.py
new file mode 100644
--- /dev/null
+++ b/tests/test_repr.py
@@ -0,0 +1,28 @@
+from enum import Enum
+
+import strawberry
+
+
+def test_repr_type():
+    @strawberry.type
+    class MyType:
+        s: str
+        i: int
+        b: bool
+        f: float
+        id: strawberry.ID
+
+    assert (
+        repr(MyType("a", 1, True, 3.2, "123"))
+        == "test_repr_type.<locals>.MyType(s='a', i=1, b=True, f=3.2, id='123')"
+    )
+
+
+def test_repr_enum():
+    @strawberry.enum()
+    class Test(Enum):
+        A = 1
+        B = 2
+        C = 3
+
+    assert repr(Test(1)) == "<Test.A: 1>"
diff --git a/tests/test_subscription.py b/tests/test_subscription.py
--- a/tests/test_subscription.py
+++ b/tests/test_subscription.py
@@ -6,6 +6,7 @@
 import strawberry
 from graphql.language import parse
 from graphql.subscription import subscribe
+from strawberry.printer import print_type
 
 
 def test_subscription_type():
@@ -15,13 +16,13 @@ class MySub:
         async def x(self, info) -> typing.AsyncGenerator[str, None]:
             yield "Hi"
 
-    expected_representation = """
+    expected_type = """
     type MySub {
       x: String!
     }
     """
 
-    assert repr(MySub()) == textwrap.dedent(expected_representation).strip()
+    assert print_type(MySub()) == textwrap.dedent(expected_type).strip()
 
 
 @pytest.mark.asyncio