From bdacb71ab123501f37d09d2c33fa176dc9036475 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 14 May 2026 20:15:45 +0200 Subject: [PATCH 1/3] Fix truncation to 300 for `get_versions_links` due to shared cursor iterations --- ayon_api/graphql.py | 6 ++ tests/test_graphql_queries.py | 117 ++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) diff --git a/ayon_api/graphql.py b/ayon_api/graphql.py index d377d94b0..afc9009c7 100644 --- a/ayon_api/graphql.py +++ b/ayon_api/graphql.py @@ -959,6 +959,12 @@ def get_filters(self) -> dict[str, Any]: if total > self._limit: limit_amount = self._limit - self._fetched_counter + if self.child_has_edges: + # Nested edge fields share a single cursor argument in the query. + # Query one parent item at a time so child pagination can't be + # overwritten by another parent from the same outer page. + limit_amount = min(limit_amount, 1) + filters[limit_key] = limit_amount if self._cursor: diff --git a/tests/test_graphql_queries.py b/tests/test_graphql_queries.py index 731ec5e8c..a275c693f 100644 --- a/tests/test_graphql_queries.py +++ b/tests/test_graphql_queries.py @@ -1,9 +1,12 @@ +from types import SimpleNamespace + import pytest from ayon_api.graphql import GraphQlQuery from ayon_api.graphql_queries import ( project_graphql_query, folders_graphql_query, + versions_graphql_query, ) from .conftest import project_name_fixture @@ -96,6 +99,120 @@ def test_get_variables_values(keys, values, types): assert query.get_variables_values() == expected +class DummyConnection: + def __init__(self, responses): + self._responses = list(responses) + self.calls = [] + + def query_graphql(self, query_str, variables): + self.calls.append((query_str, dict(variables))) + response = self._responses.pop(0) + return SimpleNamespace(errors=None, data={"data": response}) + + +def _version_link_edges(count, prefix): + return [ + {"id": f"{prefix}-{idx}"} + for idx in range(count) + ] + + +def test_nested_edge_pagination_queries_one_parent_at_a_time(): + query = versions_graphql_query({"id", "links.id"}) + query.set_variable_value("projectName", "test_project") + + con = DummyConnection([ + { + "project": { + "versions": { + "edges": [{ + "cursor": "version-1", + "node": { + "id": "version-1", + "links": { + "edges": _version_link_edges(300, "link-a"), + "pageInfo": { + "endCursor": "link-page-1", + "hasNextPage": True, + } + } + } + }], + "pageInfo": { + "endCursor": "versions-page-1", + "hasNextPage": True, + } + } + } + }, + { + "project": { + "versions": { + "edges": [{ + "cursor": "version-1", + "node": { + "id": "version-1", + "links": { + "edges": _version_link_edges(1, "link-b"), + "pageInfo": { + "endCursor": "link-page-2", + "hasNextPage": False, + } + } + } + }], + "pageInfo": { + "endCursor": "versions-page-1", + "hasNextPage": True, + } + } + } + }, + { + "project": { + "versions": { + "edges": [{ + "cursor": "version-2", + "node": { + "id": "version-2", + "links": { + "edges": _version_link_edges(1, "link-c"), + "pageInfo": { + "endCursor": "link-page-3", + "hasNextPage": False, + } + } + } + }], + "pageInfo": { + "endCursor": "versions-page-2", + "hasNextPage": False, + } + } + } + } + ]) + + output = query.query(con) + + versions = output["project"]["versions"] + assert len(versions) == 2 + assert versions[0]["id"] == "version-1" + assert len(versions[0]["links"]) == 301 + assert versions[0]["links"][0]["id"] == "link-a-0" + assert versions[0]["links"][-1]["id"] == "link-b-0" + assert versions[1]["id"] == "version-2" + assert versions[1]["links"] == [{"id": "link-c-0"}] + + first_query, second_query, third_query = [ + query_str for query_str, _variables in con.calls + ] + assert "versions(first: 1)" in first_query + assert 'links(first: 300)' in first_query + assert 'links(first: 300, after: "link-page-1")' in second_query + assert 'versions(first: 1, after: "versions-page-1")' in third_query + + """ def test_filtering(empty_query): assert empty_query._children == [] From 1255a732833e6a5a7ebf06c9213240562dc5d826 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 14 May 2026 20:33:42 +0200 Subject: [PATCH 2/3] Fix truncation to 300 for `get_versions_links` due to shared cursor iterations --- tests/test_graphql_queries.py | 117 ---------------------------------- 1 file changed, 117 deletions(-) diff --git a/tests/test_graphql_queries.py b/tests/test_graphql_queries.py index a275c693f..731ec5e8c 100644 --- a/tests/test_graphql_queries.py +++ b/tests/test_graphql_queries.py @@ -1,12 +1,9 @@ -from types import SimpleNamespace - import pytest from ayon_api.graphql import GraphQlQuery from ayon_api.graphql_queries import ( project_graphql_query, folders_graphql_query, - versions_graphql_query, ) from .conftest import project_name_fixture @@ -99,120 +96,6 @@ def test_get_variables_values(keys, values, types): assert query.get_variables_values() == expected -class DummyConnection: - def __init__(self, responses): - self._responses = list(responses) - self.calls = [] - - def query_graphql(self, query_str, variables): - self.calls.append((query_str, dict(variables))) - response = self._responses.pop(0) - return SimpleNamespace(errors=None, data={"data": response}) - - -def _version_link_edges(count, prefix): - return [ - {"id": f"{prefix}-{idx}"} - for idx in range(count) - ] - - -def test_nested_edge_pagination_queries_one_parent_at_a_time(): - query = versions_graphql_query({"id", "links.id"}) - query.set_variable_value("projectName", "test_project") - - con = DummyConnection([ - { - "project": { - "versions": { - "edges": [{ - "cursor": "version-1", - "node": { - "id": "version-1", - "links": { - "edges": _version_link_edges(300, "link-a"), - "pageInfo": { - "endCursor": "link-page-1", - "hasNextPage": True, - } - } - } - }], - "pageInfo": { - "endCursor": "versions-page-1", - "hasNextPage": True, - } - } - } - }, - { - "project": { - "versions": { - "edges": [{ - "cursor": "version-1", - "node": { - "id": "version-1", - "links": { - "edges": _version_link_edges(1, "link-b"), - "pageInfo": { - "endCursor": "link-page-2", - "hasNextPage": False, - } - } - } - }], - "pageInfo": { - "endCursor": "versions-page-1", - "hasNextPage": True, - } - } - } - }, - { - "project": { - "versions": { - "edges": [{ - "cursor": "version-2", - "node": { - "id": "version-2", - "links": { - "edges": _version_link_edges(1, "link-c"), - "pageInfo": { - "endCursor": "link-page-3", - "hasNextPage": False, - } - } - } - }], - "pageInfo": { - "endCursor": "versions-page-2", - "hasNextPage": False, - } - } - } - } - ]) - - output = query.query(con) - - versions = output["project"]["versions"] - assert len(versions) == 2 - assert versions[0]["id"] == "version-1" - assert len(versions[0]["links"]) == 301 - assert versions[0]["links"][0]["id"] == "link-a-0" - assert versions[0]["links"][-1]["id"] == "link-b-0" - assert versions[1]["id"] == "version-2" - assert versions[1]["links"] == [{"id": "link-c-0"}] - - first_query, second_query, third_query = [ - query_str for query_str, _variables in con.calls - ] - assert "versions(first: 1)" in first_query - assert 'links(first: 300)' in first_query - assert 'links(first: 300, after: "link-page-1")' in second_query - assert 'versions(first: 1, after: "versions-page-1")' in third_query - - """ def test_filtering(empty_query): assert empty_query._children == [] From ed18e4508a8e6095203b4e509f712638cb34249f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 15 May 2026 12:21:10 +0200 Subject: [PATCH 3/3] Use explicit 1 --- ayon_api/graphql.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ayon_api/graphql.py b/ayon_api/graphql.py index afc9009c7..a0b872bec 100644 --- a/ayon_api/graphql.py +++ b/ayon_api/graphql.py @@ -963,7 +963,7 @@ def get_filters(self) -> dict[str, Any]: # Nested edge fields share a single cursor argument in the query. # Query one parent item at a time so child pagination can't be # overwritten by another parent from the same outer page. - limit_amount = min(limit_amount, 1) + limit_amount = 1 filters[limit_key] = limit_amount