Skip to content

Commit

Permalink
feat(coercer): handle list sequentially/concurrently coerced at engin…
Browse files Browse the repository at this point in the history
…e level
  • Loading branch information
Maximilien-R committed Nov 11, 2020
1 parent 2a1142a commit f65955e
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 14 deletions.
5 changes: 5 additions & 0 deletions tartiflette/__init__.py
Expand Up @@ -33,6 +33,7 @@ async def create_engine(
query_cache_decorator: Optional[Callable] = UNDEFINED_VALUE,
json_loader: Optional[Callable[[str], Dict[str, Any]]] = None,
custom_default_arguments_coercer: Optional[Callable] = None,
coerce_list_concurrently: Optional[bool] = None,
) -> "Engine":
"""
Create an engine by analyzing the SDL and connecting it with the imported
Expand All @@ -56,6 +57,8 @@ async def create_engine(
:param json_loader: A callable that will replace default python
json module.loads for ast_json loading
:param custom_default_arguments_coercer: callable that will replace the
:param coerce_list_concurrently: whether or not list will be coerced
concurrently
tartiflette `default_arguments_coercer
:type sdl: Union[str, List[str]]
:type schema_name: str
Expand All @@ -66,6 +69,7 @@ async def create_engine(
:type query_cache_decorator: Optional[Callable]
:type json_loader: Optional[Callable[[str], Dict[str, Any]]]
:type custom_default_arguments_coercer: Optional[Callable]
:type coerce_list_concurrently: Optional[bool]
:return: a Cooked Engine instance
:rtype: Engine
Expand All @@ -90,6 +94,7 @@ async def create_engine(
query_cache_decorator=query_cache_decorator,
json_loader=json_loader,
custom_default_arguments_coercer=custom_default_arguments_coercer,
coerce_list_concurrently=coerce_list_concurrently,
)

return e
13 changes: 13 additions & 0 deletions tartiflette/engine.py
Expand Up @@ -139,6 +139,8 @@ class Engine:
Tartiflette GraphQL engine.
"""

# pylint: disable=too-many-instance-attributes

def __init__(
self,
sdl=None,
Expand All @@ -150,6 +152,7 @@ def __init__(
query_cache_decorator=UNDEFINED_VALUE,
json_loader=None,
custom_default_arguments_coercer=None,
coerce_list_concurrently=None,
) -> None:
"""
Creates an uncooked Engine instance.
Expand All @@ -163,6 +166,7 @@ def __init__(
self._custom_default_arguments_coercer = (
custom_default_arguments_coercer
)
self._coerce_list_concurrently = coerce_list_concurrently
self._modules = modules
self._query_cache_decorator = (
query_cache_decorator
Expand All @@ -189,6 +193,7 @@ async def cook(
query_cache_decorator: Optional[Callable] = UNDEFINED_VALUE,
json_loader: Optional[Callable[[str], Dict[str, Any]]] = None,
custom_default_arguments_coercer: Optional[Callable] = None,
coerce_list_concurrently: Optional[bool] = None,
schema_name: Optional[str] = None,
) -> None:
"""
Expand All @@ -213,6 +218,8 @@ async def cook(
json module.loads for ast_json loading
:param custom_default_arguments_coercer: callable that will replace the
tartiflette `default_arguments_coercer`
:param coerce_list_concurrently: whether or not list will be coerced
concurrently
:param schema_name: name of the SDL
:type sdl: Union[str, List[str]]
:type error_coercer: Callable[[Exception, Dict[str, Any]], Dict[str, Any]]
Expand All @@ -222,6 +229,7 @@ async def cook(
:type query_cache_decorator: Optional[Callable]
:type json_loader: Optional[Callable[[str], Dict[str, Any]]]
:type custom_default_arguments_coercer: Optional[Callable]
:type coerce_list_concurrently: Optional[bool]
:type schema_name: Optional[str]
"""
# pylint: disable=too-many-arguments,too-many-locals
Expand Down Expand Up @@ -294,6 +302,11 @@ async def cook(
custom_default_resolver,
custom_default_type_resolver,
custom_default_arguments_coercer,
(
coerce_list_concurrently
if coerce_list_concurrently is not None
else self._coerce_list_concurrently
),
)
self._build_response = partial(
build_response, error_coercer=self._error_coercer
Expand Down
5 changes: 5 additions & 0 deletions tartiflette/schema/bakery.py
Expand Up @@ -33,6 +33,7 @@ async def bake(
custom_default_resolver: Optional[Callable] = None,
custom_default_type_resolver: Optional[Callable] = None,
custom_default_arguments_coercer: Optional[Callable] = None,
coerce_list_concurrently: Optional[bool] = None,
) -> "GraphQLSchema":
"""
Bakes and returns a GraphQLSchema instance.
Expand All @@ -44,10 +45,13 @@ async def bake(
to deduct the type of a result)
:param custom_default_arguments_coercer: callable that will replace the
tartiflette `default_arguments_coercer`
:param coerce_list_concurrently: whether or not list will be coerced
concurrently
:type schema_name: str
:type custom_default_resolver: Optional[Callable]
:type custom_default_type_resolver: Optional[Callable]
:type custom_default_arguments_coercer: Optional[Callable]
:type coerce_list_concurrently: Optional[bool]
:return: a baked GraphQLSchema instance
:rtype: GraphQLSchema
"""
Expand All @@ -56,5 +60,6 @@ async def bake(
custom_default_resolver,
custom_default_type_resolver,
custom_default_arguments_coercer,
coerce_list_concurrently,
)
return schema
9 changes: 9 additions & 0 deletions tartiflette/schema/schema.py
Expand Up @@ -179,6 +179,7 @@ def __init__(self, name: str = "default") -> None:
self.name = name
self.default_type_resolver: Optional[Callable] = None
self.default_arguments_coercer: Optional[Callable] = None
self.coerce_list_concurrently: Optional[bool] = None

# Operation type names
self.query_operation_name: str = _DEFAULT_QUERY_OPERATION_NAME
Expand Down Expand Up @@ -1115,6 +1116,7 @@ async def bake(
custom_default_resolver: Optional[Callable] = None,
custom_default_type_resolver: Optional[Callable] = None,
custom_default_arguments_coercer: Optional[Callable] = None,
coerce_list_concurrently: Optional[bool] = None,
) -> None:
"""
Bake the final schema (it should not change after this) used for
Expand All @@ -1126,16 +1128,23 @@ async def bake(
to deduct the type of a result)
:param custom_default_arguments_coercer: callable that will replace the
tartiflette `default_arguments_coercer`
:param coerce_list_concurrently: TODO:
:type custom_default_resolver: Optional[Callable]
:type custom_default_type_resolver: Optional[Callable]
:type custom_default_arguments_coercer: Optional[Callable]
:type coerce_list_concurrently: Optional[bool]
"""
self.default_type_resolver = (
custom_default_type_resolver or default_type_resolver
)
self.default_arguments_coercer = (
custom_default_arguments_coercer or gather_arguments_coercer
)
self.coerce_list_concurrently = (
coerce_list_concurrently
if coerce_list_concurrently is not None
else True
)
self._inject_introspection_fields()

self._validate_extensions() # Validate this before bake
Expand Down
4 changes: 2 additions & 2 deletions tartiflette/types/field.py
Expand Up @@ -169,8 +169,8 @@ def bake(
self.concurrently = self.subscription_concurrently
elif self.query_concurrently is not None:
self.concurrently = self.query_concurrently
else: # TODO: handle a default value at schema level
self.concurrently = True
else:
self.concurrently = schema.coerce_list_concurrently

# Directives
directives_definition = compute_directive_nodes(
Expand Down
53 changes: 41 additions & 12 deletions tests/functional/regressions/issue278/test_issue457.py
Expand Up @@ -5,16 +5,7 @@

from tartiflette import Resolver, create_engine

_BOOKS = [
{"id": 1, "title": "Book #1"},
{"id": 2, "title": "Book #2"},
{"id": 3, "title": "Book #3"},
{"id": 4, "title": "Book #4"},
{"id": 5, "title": "Book #5"},
{"id": 6, "title": "Book #6"},
{"id": 7, "title": "Book #7"},
{"id": 8, "title": "Book #8"},
]
_BOOKS = [{"id": i, "title": f"Book #{i}"} for i in range(25)]

_SDL = """
type Book {
Expand All @@ -40,7 +31,7 @@ async def test_query_books(parent, args, ctx, info):

@Resolver("Book.id", schema_name=random_schema_name)
async def test_book_id(parent, args, ctx, info):
await asyncio.sleep(random.randint(1, 10) / 10)
await asyncio.sleep(random.randint(0, 10) / 100)
books_parsing_order.append(parent["id"])
return parent["id"]

Expand All @@ -61,7 +52,7 @@ async def test_query_books(parent, args, ctx, info):

@Resolver("Book.id", schema_name=random_schema_name)
async def test_book_id(parent, args, ctx, info):
await asyncio.sleep(random.randint(1, 10) / 10)
await asyncio.sleep(random.randint(0, 10) / 100)
books_parsing_order.append(parent["id"])
return parent["id"]

Expand All @@ -70,3 +61,41 @@ async def test_book_id(parent, args, ctx, info):
"data": {"books": _BOOKS}
}
assert books_parsing_order != [book["id"] for book in _BOOKS]


@pytest.mark.asyncio
async def test_issue_457_sequentially_schema_level(random_schema_name):
books_parsing_order = []

@Resolver("Book.id", schema_name=random_schema_name)
async def test_book_id(parent, args, ctx, info):
await asyncio.sleep(random.randint(0, 10) / 100)
books_parsing_order.append(parent["id"])
return parent["id"]

engine = await create_engine(
_SDL, coerce_list_concurrently=False, schema_name=random_schema_name
)
assert await engine.execute(
"{ books { id title } }", initial_value={"books": _BOOKS}
) == {"data": {"books": _BOOKS}}
assert books_parsing_order == [book["id"] for book in _BOOKS]


@pytest.mark.asyncio
async def test_issue_457_concurrently_schema_level(random_schema_name):
books_parsing_order = []

@Resolver("Book.id", schema_name=random_schema_name)
async def test_book_id(parent, args, ctx, info):
await asyncio.sleep(random.randint(0, 10) / 100)
books_parsing_order.append(parent["id"])
return parent["id"]

engine = await create_engine(
_SDL, coerce_list_concurrently=True, schema_name=random_schema_name
)
assert await engine.execute(
"{ books { id title } }", initial_value={"books": _BOOKS}
) == {"data": {"books": _BOOKS}}
assert books_parsing_order != [book["id"] for book in _BOOKS]

0 comments on commit f65955e

Please sign in to comment.