|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | | -from asyncio import sleep |
| 3 | +from asyncio import Event, sleep |
4 | 4 | from typing import TYPE_CHECKING, Any, NamedTuple, cast |
5 | 5 |
|
6 | 6 | import pytest |
@@ -1206,6 +1206,220 @@ async def separately_emits_nested_defer_frags_var_subfields_same_prio_diff_level |
1206 | 1206 | }, |
1207 | 1207 | ] |
1208 | 1208 |
|
| 1209 | + async def initiates_deferred_grouped_field_sets_only_if_released_as_pending(): |
| 1210 | + """Initiates deferred grouped field sets only if released as pending |
| 1211 | +
|
| 1212 | + Initiates deferred grouped field sets only if they have been released |
| 1213 | + as pending. |
| 1214 | + """ |
| 1215 | + document = parse( |
| 1216 | + """ |
| 1217 | + query { |
| 1218 | + ... @defer { |
| 1219 | + a { |
| 1220 | + ... @defer { |
| 1221 | + b { |
| 1222 | + c { d } |
| 1223 | + } |
| 1224 | + } |
| 1225 | + } |
| 1226 | + } |
| 1227 | + ... @defer { |
| 1228 | + a { |
| 1229 | + someField |
| 1230 | + ... @defer { |
| 1231 | + b { |
| 1232 | + e { f } |
| 1233 | + } |
| 1234 | + } |
| 1235 | + } |
| 1236 | + } |
| 1237 | + } |
| 1238 | + """ |
| 1239 | + ) |
| 1240 | + |
| 1241 | + slow_field_event = Event() |
| 1242 | + c_resolver_called = False |
| 1243 | + e_resolver_called = False |
| 1244 | + |
| 1245 | + async def resolve_slow_field(_info): |
| 1246 | + await slow_field_event.wait() |
| 1247 | + return "someField" |
| 1248 | + |
| 1249 | + def resolve_c(_info): |
| 1250 | + nonlocal c_resolver_called |
| 1251 | + c_resolver_called = True |
| 1252 | + return {"d": "d"} |
| 1253 | + |
| 1254 | + def resolve_e(_info): |
| 1255 | + nonlocal e_resolver_called |
| 1256 | + e_resolver_called = True |
| 1257 | + return {"f": "f"} |
| 1258 | + |
| 1259 | + execute_result = experimental_execute_incrementally( |
| 1260 | + schema, |
| 1261 | + document, |
| 1262 | + root_value={ |
| 1263 | + "a": { |
| 1264 | + "someField": resolve_slow_field, |
| 1265 | + "b": { |
| 1266 | + "c": resolve_c, |
| 1267 | + "e": resolve_e, |
| 1268 | + }, |
| 1269 | + } |
| 1270 | + }, |
| 1271 | + enable_early_execution=False, |
| 1272 | + ) |
| 1273 | + |
| 1274 | + assert isinstance(execute_result, ExperimentalIncrementalExecutionResults) |
| 1275 | + |
| 1276 | + result1 = execute_result.initial_result |
| 1277 | + assert result1.formatted == { |
| 1278 | + "data": {}, |
| 1279 | + "pending": [{"id": "0", "path": []}, {"id": "1", "path": []}], |
| 1280 | + "hasNext": True, |
| 1281 | + } |
| 1282 | + |
| 1283 | + iterator = execute_result.subsequent_results |
| 1284 | + |
| 1285 | + assert c_resolver_called is False |
| 1286 | + assert e_resolver_called is False |
| 1287 | + |
| 1288 | + result2 = await anext(iterator) |
| 1289 | + assert result2.formatted == { |
| 1290 | + "pending": [{"id": "2", "path": ["a"]}], |
| 1291 | + "incremental": [ |
| 1292 | + {"data": {"a": {}}, "id": "0"}, |
| 1293 | + {"data": {"b": {}}, "id": "2"}, |
| 1294 | + {"data": {"c": {"d": "d"}}, "id": "2", "subPath": ["b"]}, |
| 1295 | + ], |
| 1296 | + "completed": [{"id": "0"}, {"id": "2"}], |
| 1297 | + "hasNext": True, |
| 1298 | + } |
| 1299 | + |
| 1300 | + assert c_resolver_called is True |
| 1301 | + assert e_resolver_called is False |
| 1302 | + |
| 1303 | + slow_field_event.set() |
| 1304 | + |
| 1305 | + result3 = await anext(iterator) |
| 1306 | + assert result3.formatted == { |
| 1307 | + "pending": [{"id": "3", "path": ["a"]}], |
| 1308 | + "incremental": [ |
| 1309 | + {"data": {"someField": "someField"}, "id": "1", "subPath": ["a"]}, |
| 1310 | + {"data": {"e": {"f": "f"}}, "id": "3", "subPath": ["b"]}, |
| 1311 | + ], |
| 1312 | + "completed": [{"id": "1"}, {"id": "3"}], |
| 1313 | + "hasNext": False, |
| 1314 | + } |
| 1315 | + |
| 1316 | + assert e_resolver_called is True |
| 1317 | + |
| 1318 | + with pytest.raises(StopAsyncIteration): |
| 1319 | + await anext(iterator) |
| 1320 | + |
| 1321 | + async def initiates_unique_deferred_grouped_field_sets_after_sibling_defers(): |
| 1322 | + """Initiates unique deferred grouped field sets after sibling defers. |
| 1323 | +
|
| 1324 | + Initiates unique deferred grouped field sets after those that are common |
| 1325 | + to sibling defers. |
| 1326 | + """ |
| 1327 | + document = parse( |
| 1328 | + """ |
| 1329 | + query { |
| 1330 | + ... @defer { |
| 1331 | + a { |
| 1332 | + ... @defer { |
| 1333 | + b { |
| 1334 | + c { d } |
| 1335 | + } |
| 1336 | + } |
| 1337 | + } |
| 1338 | + } |
| 1339 | + ... @defer { |
| 1340 | + a { |
| 1341 | + ... @defer { |
| 1342 | + b { |
| 1343 | + c { d } |
| 1344 | + e { f } |
| 1345 | + } |
| 1346 | + } |
| 1347 | + } |
| 1348 | + } |
| 1349 | + } |
| 1350 | + """ |
| 1351 | + ) |
| 1352 | + |
| 1353 | + c_event = Event() |
| 1354 | + c_resolver_called = False |
| 1355 | + e_resolver_called = False |
| 1356 | + |
| 1357 | + async def resolve_c(_info): |
| 1358 | + nonlocal c_resolver_called |
| 1359 | + c_resolver_called = True |
| 1360 | + await c_event.wait() |
| 1361 | + return {"d": "d"} |
| 1362 | + |
| 1363 | + def resolve_e(_info): |
| 1364 | + nonlocal e_resolver_called |
| 1365 | + e_resolver_called = True |
| 1366 | + return {"f": "f"} |
| 1367 | + |
| 1368 | + execute_result = experimental_execute_incrementally( |
| 1369 | + schema, |
| 1370 | + document, |
| 1371 | + root_value={ |
| 1372 | + "a": { |
| 1373 | + "b": { |
| 1374 | + "c": resolve_c, |
| 1375 | + "e": resolve_e, |
| 1376 | + } |
| 1377 | + } |
| 1378 | + }, |
| 1379 | + enable_early_execution=False, |
| 1380 | + ) |
| 1381 | + |
| 1382 | + assert isinstance(execute_result, ExperimentalIncrementalExecutionResults) |
| 1383 | + |
| 1384 | + result1 = execute_result.initial_result |
| 1385 | + assert result1.formatted == { |
| 1386 | + "data": {}, |
| 1387 | + "pending": [{"id": "0", "path": []}, {"id": "1", "path": []}], |
| 1388 | + "hasNext": True, |
| 1389 | + } |
| 1390 | + |
| 1391 | + iterator = execute_result.subsequent_results |
| 1392 | + |
| 1393 | + assert c_resolver_called is False |
| 1394 | + assert e_resolver_called is False |
| 1395 | + |
| 1396 | + result2 = await anext(iterator) |
| 1397 | + assert result2.formatted == { |
| 1398 | + "pending": [{"id": "2", "path": ["a"]}, {"id": "3", "path": ["a"]}], |
| 1399 | + "incremental": [{"data": {"a": {}}, "id": "0"}], |
| 1400 | + "completed": [{"id": "0"}, {"id": "1"}], |
| 1401 | + "hasNext": True, |
| 1402 | + } |
| 1403 | + |
| 1404 | + await sleep(0) # let resolve_c start and suspend at c_event.wait() |
| 1405 | + c_event.set() |
| 1406 | + |
| 1407 | + assert c_resolver_called is True |
| 1408 | + assert e_resolver_called is False |
| 1409 | + |
| 1410 | + result3 = await anext(iterator) |
| 1411 | + assert result3.formatted == { |
| 1412 | + "incremental": [ |
| 1413 | + {"data": {"b": {"c": {"d": "d"}}}, "id": "2"}, |
| 1414 | + {"data": {"e": {"f": "f"}}, "id": "3", "subPath": ["b"]}, |
| 1415 | + ], |
| 1416 | + "completed": [{"id": "2"}, {"id": "3"}], |
| 1417 | + "hasNext": False, |
| 1418 | + } |
| 1419 | + |
| 1420 | + with pytest.raises(StopAsyncIteration): |
| 1421 | + await anext(iterator) |
| 1422 | + |
1209 | 1423 | async def can_deduplicate_multiple_defers_on_the_same_object(): |
1210 | 1424 | """Can deduplicate multiple defers on the same object""" |
1211 | 1425 | document = parse( |
|
0 commit comments