Skip to content
This repository was archived by the owner on Feb 6, 2025. It is now read-only.

Commit 3c71465

Browse files
Implement scalar renaming and suppression (#993)
1 parent d6c0df5 commit 3c71465

File tree

3 files changed

+78
-97
lines changed

3 files changed

+78
-97
lines changed

graphql_compiler/schema_transformation/rename_schema.py

Lines changed: 17 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@
7979
- Suppressing unions.
8080
- 1-1 and 1-many renamings for fields belonging to object types.
8181
- Suppressions for fields belonging to object types.
82+
- Renamings and suppressions for scalar types.
8283
8384
Operations that are not yet supported but will be implemented:
8485
- Suppressions for enums, interfaces, and object types that implement interfaces.
85-
- Renamings and suppressions for scalar types.
8686
- Renamings and suppressions for fields that belong to either interface types or object types that
8787
implement interfaces.
8888
- Renamings and suppressions for enum values.
@@ -146,7 +146,6 @@
146146
builtin_scalar_type_names,
147147
check_ast_schema_is_valid,
148148
get_copy_of_node_with_new_name,
149-
get_custom_scalar_names,
150149
get_query_type_name,
151150
is_valid_nonreserved_name,
152151
)
@@ -235,15 +234,12 @@ def rename_schema(
235234

236235
schema = build_ast_schema(schema_ast)
237236
query_type = get_query_type_name(schema)
238-
custom_scalar_names = get_custom_scalar_names(schema)
239237

240-
_validate_renamings(
241-
schema_ast, type_renamings, field_renamings, query_type, custom_scalar_names
242-
)
238+
_validate_renamings(schema_ast, type_renamings, field_renamings, query_type)
243239

244240
# Rename types, interfaces, enums, unions and suppress types, unions
245241
schema_ast, reverse_name_map, reverse_field_name_map = _rename_and_suppress_types_and_fields(
246-
schema_ast, type_renamings, field_renamings, query_type, custom_scalar_names
242+
schema_ast, type_renamings, field_renamings, query_type
247243
)
248244

249245
schema_ast = _rename_and_suppress_query_type_fields(schema_ast, type_renamings, query_type)
@@ -260,14 +256,12 @@ def _validate_renamings(
260256
type_renamings: Mapping[str, Optional[str]],
261257
field_renamings: Mapping[str, Mapping[str, Set[str]]],
262258
query_type: str,
263-
custom_scalar_names: Set[str],
264259
) -> None:
265260
"""Validate the type_renamings argument before attempting to rename the schema.
266261
267262
Check for fields with suppressed types or unions whose members were all suppressed. Also,
268263
confirm type_renamings contains no enums, interfaces, or interface implementation suppressions
269-
because that hasn't been implemented yet. Confirm that no scalars would be suppressed or
270-
renamed.
264+
because that hasn't been implemented yet.
271265
272266
The input AST will not be modified.
273267
@@ -282,16 +276,14 @@ def _validate_renamings(
282276
field names belonging to the type to a set of field names for the
283277
renamed schema
284278
query_type: name of the query type, e.g. 'RootSchemaQuery'
285-
custom_scalar_names: set of all user defined scalars used in the schema (excluding
286-
builtin scalars)
287279
288280
Raises:
289281
- CascadingSuppressionError if a type/field suppression would require further suppressions
290282
- NotImplementedError if type_renamings attempts to suppress an enum, an interface, or a
291283
type implementing an interface
292284
"""
293285
_ensure_no_cascading_type_suppressions(schema_ast, type_renamings, field_renamings, query_type)
294-
_ensure_no_unsupported_operations(schema_ast, type_renamings, custom_scalar_names)
286+
_ensure_no_unsupported_operations(schema_ast, type_renamings)
295287

296288

297289
def _ensure_no_cascading_type_suppressions(
@@ -350,28 +342,23 @@ def _ensure_no_cascading_type_suppressions(
350342
def _ensure_no_unsupported_operations(
351343
schema_ast: DocumentNode,
352344
type_renamings: Mapping[str, Optional[str]],
353-
custom_scalar_names: Set[str],
354345
) -> None:
355346
"""Check for unsupported type renaming or suppression operations."""
356-
_ensure_no_unsupported_scalar_operations(type_renamings, custom_scalar_names)
347+
_ensure_no_unsupported_scalar_operations(type_renamings)
357348
_ensure_no_unsupported_suppressions(schema_ast, type_renamings)
358349

359350

360351
def _ensure_no_unsupported_scalar_operations(
361352
type_renamings: Mapping[str, Optional[str]],
362-
custom_scalar_names: Set[str],
363353
) -> None:
364354
"""Check for unsupported scalar operations."""
365355
unsupported_scalar_operations = {
366-
scalar_name: type_renamings[scalar_name]
367-
for scalar_name in custom_scalar_names.union(builtin_scalar_type_names)
368-
if scalar_name in type_renamings
356+
scalar_name for scalar_name in builtin_scalar_type_names if scalar_name in type_renamings
369357
}
370358
if unsupported_scalar_operations:
371359
raise NotImplementedError(
372-
f"Scalar renaming and suppression is not implemented yet, but type_renamings attempted "
373-
f"to modify the following scalars: {unsupported_scalar_operations}. To fix this, "
374-
f"remove them from type_renamings."
360+
f"Type_renamings contained renamings for the following built-in scalar types: "
361+
f"{unsupported_scalar_operations}. To fix this, remove them from type_renamings."
375362
)
376363

377364

@@ -422,7 +409,6 @@ def _rename_and_suppress_types_and_fields(
422409
type_renamings: Mapping[str, Optional[str]],
423410
field_renamings: Mapping[str, Mapping[str, Set[str]]],
424411
query_type: str,
425-
custom_scalar_names: Set[str],
426412
) -> Tuple[DocumentNode, Dict[str, str], Dict[str, Dict[str, str]]]:
427413
"""Rename and suppress types, enums, interfaces, fields using renamings.
428414
@@ -439,8 +425,6 @@ def _rename_and_suppress_types_and_fields(
439425
field names belonging to the type to a set of field names for the
440426
renamed schema
441427
query_type: name of the query type, e.g. 'RootSchemaQuery'
442-
custom_scalar_names: set of all user defined scalars used in the schema (excluding
443-
builtin scalars)
444428
445429
Returns:
446430
Tuple containing the modified version of the schema AST, the renamed type name to original
@@ -452,9 +436,7 @@ def _rename_and_suppress_types_and_fields(
452436
- SchemaRenameNameConflictError if the rename causes name conflicts
453437
- NoOpRenamingError if renamings contains no-op renamings
454438
"""
455-
visitor = RenameSchemaTypesVisitor(
456-
type_renamings, field_renamings, query_type, custom_scalar_names
457-
)
439+
visitor = RenameSchemaTypesVisitor(type_renamings, field_renamings, query_type)
458440
renamed_schema_ast = visit(schema_ast, visitor)
459441
if visitor.invalid_type_names or visitor.invalid_field_names:
460442
explanation = (
@@ -614,7 +596,6 @@ class RenameSchemaTypesVisitor(Visitor):
614596
"ObjectValueNode",
615597
"OperationDefinitionNode",
616598
"OperationTypeDefinitionNode",
617-
"ScalarTypeDefinitionNode",
618599
"SchemaDefinitionNode",
619600
"SelectionSetNode",
620601
"StringValueNode",
@@ -646,6 +627,7 @@ class RenameSchemaTypesVisitor(Visitor):
646627
"InterfaceTypeDefinitionNode",
647628
"NamedTypeNode",
648629
"ObjectTypeDefinitionNode",
630+
"ScalarTypeDefinitionNode",
649631
"UnionTypeDefinitionNode",
650632
}
651633
)
@@ -661,8 +643,8 @@ class RenameSchemaTypesVisitor(Visitor):
661643
type_renamed_to_builtin_scalar_conflicts: Dict[str, str]
662644

663645
# reverse_name_map maps renamed type name to original type name, containing all non-suppressed
664-
# non-scalar types, including those that were unchanged. Must contain unchanged names to prevent
665-
# type renaming conflicts and raise SchemaRenameNameConflictError when they arise
646+
# types, including those that were unchanged. Must contain unchanged names to prevent type
647+
# renaming conflicts and raise SchemaRenameNameConflictError when they arise
666648
reverse_name_map: Dict[str, str]
667649

668650
# Collects invalid type names in type_renamings. If type_renamings["Foo"] is a string that is
@@ -719,7 +701,6 @@ def __init__(
719701
type_renamings: Mapping[str, Optional[str]],
720702
field_renamings: Mapping[str, Mapping[str, Set[str]]],
721703
query_type: str,
722-
custom_scalar_names: Set[str],
723704
) -> None:
724705
"""Create a visitor for renaming types in a schema AST.
725706
@@ -731,16 +712,13 @@ def __init__(
731712
field names belonging to the type to a set of field names for the
732713
renamed schema
733714
query_type: name of the query type (e.g. RootSchemaQuery), which will not be renamed
734-
custom_scalar_names: set of all user defined scalars used in the schema (excluding
735-
builtin scalars)
736715
"""
737716
self.type_renamings = type_renamings
738717
self.reverse_name_map = {}
739718
self.type_name_conflicts = {}
740719
self.type_renamed_to_builtin_scalar_conflicts = {}
741720
self.invalid_type_names = {}
742721
self.query_type = query_type
743-
self.custom_scalar_names = frozenset(custom_scalar_names)
744722
self.suppressed_type_names = set()
745723
self.field_renamings = field_renamings
746724
self.reverse_field_name_map = {}
@@ -772,11 +750,7 @@ def _rename_or_suppress_or_ignore_name_and_add_to_record(
772750
"""
773751
type_name = node.name.value
774752

775-
if (
776-
type_name == self.query_type
777-
or type_name in self.custom_scalar_names
778-
or type_name in builtin_scalar_type_names
779-
):
753+
if type_name == self.query_type or type_name in builtin_scalar_type_names:
780754
return IDLE
781755

782756
desired_type_name = self.type_renamings.get(type_name, type_name) # Default use original
@@ -788,39 +762,9 @@ def _rename_or_suppress_or_ignore_name_and_add_to_record(
788762
self.invalid_type_names[type_name] = desired_type_name
789763

790764
# Renaming conflict arises when two types with different names in the original schema have
791-
# the same name in the new schema. There are two ways to produce this conflict:
792-
# 1. when neither type is a custom scalar
793-
# 2. when one type is a custom scalar and the other is not
794-
#
795-
# If neither type is a custom scalar, then desired_type_name will be in
796-
# self.reverse_name_map (because self.reverse_name_map records all non-scalar types in
797-
# the schema). The types named self.reverse_name_map[desired_type_name] and type_name in
798-
# the old schema would both get mapped to desired_type_name in the new schema, even though
799-
# self.reverse_name_map[desired_type_name] != type_name.
800-
#
801-
# If one type in the conflict is a custom scalar, then the conflict arises from
802-
# attempting to rename a non-scalar type (from type_name to desired_type_name) when
803-
# there already exists a custom scalar type named desired_type_name. Custom scalar
804-
# renaming has not been implemented yet so we know for sure that the scalar's name (in
805-
# both the original and new schema) is desired_type_name.
806-
#
807-
# It's also possible to produce a similar conflict where one type is not a custom scalar but
808-
# rather a built-in scalar, e.g. String. That case is handled separately because renaming a
809-
# type to a built-in scalar will never be allowed in any schema, whereas the conflicts here
810-
# arise from conflicting with the existence of other types in the schema.
811-
if (
812-
self.reverse_name_map.get(desired_type_name, type_name) != type_name
813-
or desired_type_name in self.custom_scalar_names
814-
):
815-
# If neither type in this conflict is a custom scalar, the two types causing conflict
816-
# were named type_name and self.reverse_name_map[desired_type_name] in the original
817-
# schema.
818-
# If one type in this conflict is a custom scalar, the two types causing conflict were
819-
# named type_name and desired_type_name (the latter being the custom scalar name) in the
820-
# original schema.
821-
conflictingly_renamed_type_name = self.reverse_name_map.get(
822-
desired_type_name, desired_type_name
823-
)
765+
# the same name in the new schema.
766+
if self.reverse_name_map.get(desired_type_name, type_name) != type_name:
767+
conflictingly_renamed_type_name = self.reverse_name_map[desired_type_name]
824768

825769
# Collect all types in the original schema that would be named desired_type_name in the
826770
# new schema

graphql_compiler/schema_transformation/utils.py

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
UnionTypeDefinitionNode,
2424
)
2525
from graphql.language.visitor import Visitor, visit
26-
from graphql.type.definition import GraphQLScalarType
2726
from graphql.utilities.assert_valid_name import re_name
2827
from graphql.validation import validate
2928
import six
@@ -299,6 +298,7 @@ def __str__(self) -> str:
299298
InterfaceTypeDefinitionNode,
300299
NamedTypeNode,
301300
ObjectTypeDefinitionNode,
301+
ScalarTypeDefinitionNode,
302302
UnionTypeDefinitionNode,
303303
]
304304
RenameTypesT = TypeVar("RenameTypesT", bound=RenameTypes)
@@ -371,27 +371,6 @@ def get_query_type_name(schema: GraphQLSchema) -> str:
371371
return schema.query_type.name
372372

373373

374-
def get_custom_scalar_names(schema: GraphQLSchema) -> Set[str]:
375-
"""Get names of all custom scalars used in the input schema.
376-
377-
Includes all user defined scalars; excludes builtin scalars.
378-
379-
Note: If the user defined a scalar that shares its name with a builtin introspection type
380-
(such as __Schema, __Directive, etc), it will not be listed in type_map and thus will not
381-
be included in the output.
382-
383-
Returns:
384-
set of names of scalars used in the schema
385-
"""
386-
type_map = schema.type_map
387-
custom_scalar_names = {
388-
type_name
389-
for type_name, type_object in six.iteritems(type_map)
390-
if isinstance(type_object, GraphQLScalarType) and type_name not in builtin_scalar_type_names
391-
}
392-
return custom_scalar_names
393-
394-
395374
def try_get_ast_by_name_and_type(
396375
asts: Optional[Sequence[Node]], target_name: str, target_type: Type[Node]
397376
) -> Optional[Node]:
@@ -488,6 +467,7 @@ def get_copy_of_node_with_new_name(node: RenameNodesT, new_name: str) -> RenameN
488467
"InterfaceTypeDefinitionNode",
489468
"NamedTypeNode",
490469
"ObjectTypeDefinitionNode",
470+
"ScalarTypeDefinitionNode",
491471
"UnionTypeDefinitionNode",
492472
)
493473
)

graphql_compiler/tests/schema_transformation_tests/test_rename_schema.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -615,8 +615,65 @@ def test_multiple_interfaces_rename(self) -> None:
615615
self.assertEqual({}, renamed_schema.reverse_field_name_map)
616616

617617
def test_scalar_rename(self) -> None:
618-
with self.assertRaises(NotImplementedError):
619-
rename_schema(parse(ISS.scalar_schema), {"Date": "NewDate"}, {})
618+
renamed_schema = rename_schema(parse(ISS.scalar_schema), {"Date": "NewDate"}, {})
619+
renamed_schema_string = dedent(
620+
"""\
621+
schema {
622+
query: SchemaQuery
623+
}
624+
625+
directive @stitch(source_field: String!, sink_field: String!) on FIELD_DEFINITION
626+
627+
type Human {
628+
id: String
629+
birthday: NewDate
630+
}
631+
632+
scalar NewDate
633+
634+
type SchemaQuery {
635+
Human: Human
636+
}
637+
"""
638+
)
639+
compare_schema_texts_order_independently(
640+
self, renamed_schema_string, print_ast(renamed_schema.schema_ast)
641+
)
642+
self.assertEqual(
643+
{"NewDate": "Date"},
644+
renamed_schema.reverse_name_map,
645+
)
646+
self.assertEqual({}, renamed_schema.reverse_field_name_map)
647+
648+
def test_scalar_suppress(self) -> None:
649+
renamed_schema = rename_schema(
650+
parse(ISS.scalar_schema), {"Date": None}, {"Human": {"birthday": set()}}
651+
)
652+
renamed_schema_string = dedent(
653+
"""\
654+
schema {
655+
query: SchemaQuery
656+
}
657+
658+
directive @stitch(source_field: String!, sink_field: String!) on FIELD_DEFINITION
659+
660+
type Human {
661+
id: String
662+
}
663+
664+
type SchemaQuery {
665+
Human: Human
666+
}
667+
"""
668+
)
669+
compare_schema_texts_order_independently(
670+
self, renamed_schema_string, print_ast(renamed_schema.schema_ast)
671+
)
672+
self.assertEqual(
673+
{},
674+
renamed_schema.reverse_name_map,
675+
)
676+
self.assertEqual({}, renamed_schema.reverse_field_name_map)
620677

621678
def test_builtin_rename(self) -> None:
622679
with self.assertRaises(NotImplementedError):

0 commit comments

Comments
 (0)