Repository: mirumee/ariadne

Issue:
make_executable_schema resolvers arg should accept dict of dicts or list of dicts of dicts
The `make_executable_schema` utility should optionally take list of dicts of dicts (AKA "resolvers map"), this would allow larger projects to easily split and compose resolvers as needed:

```python
from ariadne import make_executable_schema

from products.graphql import resolvers as products_resolvers
from users.graphql import resolvers as users_resolvers


typedefs = "..."
resolvers = [products_resolvers, users_resolvers]

schema = make_executable_schema(typedefs, resolvers)
```

This task will likely require #13 to be done first, so we are 100% certain that all resolver mappings are dicts.


Discussion:

Resolving patch:
diff --git a/ariadne/build_schema.py b/ariadne/build_schema.py
--- a/ariadne/build_schema.py
+++ b/ariadne/build_schema.py
@@ -1,5 +1,3 @@
-from typing import List, Union
-
 from graphql import GraphQLSchema, parse
 from graphql.language.ast import (
     Document,
@@ -12,12 +10,7 @@
 from graphql.utils.build_ast_schema import build_ast_schema
 
 
-def build_schema_from_type_definitions(
-    type_defs: Union[str, List[str]]
-) -> GraphQLSchema:
-    if isinstance(type_defs, list):
-        type_defs = concatenate_type_defs(type_defs)
-
+def build_schema_from_type_definitions(type_defs: str) -> GraphQLSchema:
     document = parse(type_defs)
 
     if not document_has_schema(document):
@@ -27,13 +20,6 @@ def build_schema_from_type_definitions(
     return build_ast_schema(document)
 
 
-def concatenate_type_defs(type_defs: List[str]) -> str:
-    resolved_type_defs = []
-    for type_def in type_defs:
-        resolved_type_defs.append(type_def.strip())
-    return "\n\n".join(resolved_type_defs)
-
-
 def build_default_schema(document: Document) -> SchemaDefinition:
     defined_types = [
         td.name.value
diff --git a/ariadne/executable_schema.py b/ariadne/executable_schema.py
--- a/ariadne/executable_schema.py
+++ b/ariadne/executable_schema.py
@@ -1,4 +1,6 @@
-from typing import List, Union
+from collections import defaultdict
+from itertools import chain
+from typing import Iterator, List, Union
 
 from graphql import GraphQLSchema
 
@@ -6,9 +8,39 @@
 from .resolvers import add_resolve_functions_to_schema
 
 
+def decompose_maps(resolvers_maps: List[dict]) -> Iterator[tuple]:
+    def flatten(rm):
+        for key, value in rm.items():
+            for resolver_name, resolver in value.items():
+                yield (key, resolver_name, resolver)
+
+    return chain.from_iterable(flatten(m) for m in resolvers_maps)
+
+
+def merge_resolvers(resolver_list: Iterator[tuple]) -> dict:
+    output = defaultdict(dict)  # type: dict
+    for key, resolver_name, resolver in resolver_list:
+        output[key][resolver_name] = resolver
+    return output
+
+
+def join_type_defs(type_defs: List[str]) -> str:
+    return "\n\n".join(t.strip() for t in type_defs)
+
+
 def make_executable_schema(
-    type_defs: Union[str, List[str]], resolvers: dict
+    type_defs: Union[str, List[str]], resolvers: Union[dict, List[dict]]
 ) -> GraphQLSchema:
+    if isinstance(type_defs, list):
+        type_defs = join_type_defs(type_defs)
+
     schema = build_schema_from_type_definitions(type_defs)
-    add_resolve_functions_to_schema(schema, resolvers)
+
+    if isinstance(resolvers, list):
+        add_resolve_functions_to_schema(
+            schema, merge_resolvers(decompose_maps(resolvers))
+        )
+    elif isinstance(resolvers, dict):
+        add_resolve_functions_to_schema(schema, resolvers)
+
     return schema


Test patch:
diff --git a/tests/test_modularization.py b/tests/test_modularization.py
--- a/tests/test_modularization.py
+++ b/tests/test_modularization.py
@@ -1,6 +1,8 @@
+from unittest.mock import Mock
+
 from graphql import graphql
 
-from ariadne import make_executable_schema
+from ariadne import make_executable_schema, resolve_to
 
 root_typedef = """
     type Query {
@@ -51,3 +53,42 @@ def test_override_typedef_outdated_field():
     assert result.errors is not None
     assert str(result.errors[0]) == 'Cannot query field "username" on type "User".'
     assert result.data is None
+
+
+def test_accept_list_of_resolvers_maps():
+    type_defs = """
+        type Query {
+            user: User
+            test(data: TestInput): Int
+        }
+
+        type User {
+            firstName: String
+        }
+
+        input TestInput {
+            value: Int
+        }
+    """
+
+    def resolve_test(*_, data):
+        assert data == {"value": 4}
+        return "42"
+
+    resolvers = [
+        {
+            "Query": {"user": lambda *_: Mock(first_name="Joe")},
+            "User": {"firstName": resolve_to("first_name")},
+        },
+        {"Query": {"test": resolve_test}},
+    ]
+
+    schema = make_executable_schema(type_defs, resolvers)
+
+    result = graphql(schema, "{ user { firstName } }")
+    assert result.errors is None
+    assert result.data == {"user": {"firstName": "Joe"}}
+
+    result = graphql(schema, "{ test(data: { value: 4 }) }")
+    assert result.errors is None
+    assert result.data == {"test": 42}