From 5021505d2982a94dadb563ce09e84b4b7e3465dd Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Mon, 12 Oct 2026 14:09:00 +0200 Subject: [PATCH] Make _add_sharable_to_page_info_type idempotent So far, `build_schema` would raise an error if it was called multiple times with a schema using PageInfo, because it tried to add the `@sharable` directive multiple times to PageInfo. --- graphene_federation/schema.py | 6 ++++++ tests/test_shareable.py | 28 +++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/graphene_federation/schema.py b/graphene_federation/schema.py index 91423f6..7e1ef3b 100644 --- a/graphene_federation/schema.py +++ b/graphene_federation/schema.py @@ -7,6 +7,7 @@ build_schema as build_directive_schema, directive_decorator, ) +from graphene_directives import utils as graphene_directives_utils from graphene_directives.schema import Schema from .apollo_versions import ( @@ -53,6 +54,11 @@ def _add_sharable_to_page_info_type( try: # PageInfo needs @sharable directive sharable = get_directive_from_name("shareable", federation_version) + if graphene_directives_utils.has_non_field_attribute( + page_info.graphene_type, sharable + ): + # already added, skip + return types.append( directive_decorator(target_directive=sharable)(field=None)( page_info.graphene_type diff --git a/tests/test_shareable.py b/tests/test_shareable.py index 4eaefa0..869070c 100644 --- a/tests/test_shareable.py +++ b/tests/test_shareable.py @@ -2,7 +2,7 @@ import graphene import pytest -from graphene import ObjectType +from graphene import ObjectType, relay from graphene_directives import DirectiveValidationError from graphene_federation import LATEST_VERSION, build_schema @@ -90,3 +90,29 @@ class Query(ObjectType): ) assert "@shareable cannot be used for SearchResult" in str(err.value) + + +def test_add_sharable_to_page_info_type_idempotent(): + """ + build_schema must not raise DirectiveValidationError when called multiple times + with a schema that includes PageInfo (e.g. via relay connections). + _add_sharable_to_page_info_type mutates the global PageInfo class in-place, + so a second call would previously attempt to apply @shareable again and crash. + """ + + class Item(graphene.ObjectType): + class Meta: + interfaces = (relay.Node,) + + name = graphene.String() + + class ItemConnection(relay.Connection): + class Meta: + node = Item + + class Query(ObjectType): + items = relay.ConnectionField(ItemConnection) + + build_schema(query=Query, federation_version=LATEST_VERSION) + # Second call must not raise DirectiveValidationError + build_schema(query=Query, federation_version=LATEST_VERSION)