Source code for py_gql.schema.transforms.visibility

# -*- coding: utf-8 -*-
from typing import Optional, TypeVar

from ..._utils import map_and_filter
from ...schema import (
    SPECIFIED_SCALAR_TYPES,
    Directive,
    EnumType,
    InputField,
    InputObjectType,
    InterfaceType,
    NamedType,
    ObjectType,
    ScalarType,
    SchemaVisitor,
    UnionType,
    unwrap_type,
)
from ...schema.introspection import INTROPSPECTION_TYPES


TNamedType = TypeVar("TNamedType", bound=NamedType)


[docs]class VisibilitySchemaTransform(SchemaVisitor): """ Remove elements from a schema. User should subclass this and override the various visibility hooks. The following elements are affected directly: - Named types (is_type_visible) - Object and interface fields (is_field_visible) - Directives (is_directive_visible) - Input type fields (is_input_field_visible) The following elements are affected indirectly: - Object fields of a type that is now hidden - Input fields of a type that is now hidden - Arguments of a type that is now hidden Warning: Mandatory input fields and arguments being removed could break resolvers if they were not built anticipating this (e.g. expected an argument as a mandatory keyword argument). This should not allow modifying a schema to be off-spec and as such the following elements (or their children) cannot be hidden: - The query type - Specified scalar types - Introspection types - Specified directives Users should use this transform before using the schema to make sure that validation, execution and introspection take the modifications into account. """ def is_type_visible(self, name: str) -> bool: return True def is_directive_visible(self, name: str) -> bool: return True def is_field_visible(self, typename: str, fieldname: str) -> bool: return True def is_input_field_visible(self, typename: str, fieldname: str) -> bool: return True def _is_type_visible(self, named_type: NamedType) -> bool: return ( named_type in SPECIFIED_SCALAR_TYPES or named_type in INTROPSPECTION_TYPES or self.is_type_visible(named_type.name) ) def _filter_type( self, named_type: Optional[TNamedType] ) -> Optional[TNamedType]: if named_type is None or not self._is_type_visible(named_type): return None return named_type def on_scalar(self, scalar_type: ScalarType) -> Optional[ScalarType]: return self._filter_type(super().on_scalar(scalar_type)) def on_object(self, object_type: ObjectType) -> Optional[ObjectType]: if not self._is_type_visible(object_type): return None updated_fields = map_and_filter( lambda field: ( field if self.is_field_visible(object_type.name, field.name) else None ), object_type.fields, ) if updated_fields != object_type.fields: object_type.fields = updated_fields return super().on_object(object_type) def on_interface( self, interface_type: InterfaceType ) -> Optional[InterfaceType]: if not self._is_type_visible(interface_type): return None def _filter_field(field): return ( field if self.is_field_visible(interface_type.name, field.name) else None ) updated_fields = map_and_filter(_filter_field, interface_type.fields) if updated_fields != interface_type.fields: interface_type.fields = updated_fields return super().on_interface(interface_type) def on_union(self, union_type: UnionType) -> Optional[UnionType]: return self._filter_type(super().on_union(union_type)) def on_enum(self, enum_type: EnumType) -> Optional[EnumType]: return self._filter_type(super().on_enum(enum_type)) def on_input_object( self, input_object_type: InputObjectType ) -> Optional[InputObjectType]: updated_fields = map_and_filter( lambda input_field: ( input_field if self.is_input_field_visible( input_object_type.name, input_field.name ) else None ), input_object_type.fields, ) if updated_fields != input_object_type.fields: input_object_type.fields = updated_fields return self._filter_type(super().on_input_object(input_object_type)) def on_input_field(self, field: InputField) -> Optional[InputField]: if self._is_type_visible(unwrap_type(field.type)): return super().on_input_field(field) return None def on_directive(self, directive: Directive) -> Optional[Directive]: if directive and not self.is_directive_visible(directive.name): return None return super().on_directive(directive)