# -*- coding: utf-8 -*-
from enum import IntEnum
from typing import Any, Type, Union
from .. import (
Argument,
Directive,
EnumType,
EnumValue,
Field,
InputField,
InputObjectType,
InterfaceType,
NamedType,
ObjectType,
UnionType,
)
[docs]class SchemaChangeSeverity(IntEnum):
"""
Severity level of a schema change.
"""
#: Change should be safe for all clients.
COMPATIBLE = 0
#: Change could break some clients or create silent issues depending on
#: which part of the schema they use.
DANGEROUS = 1
#: Change will break most clients.
BREAKING = 2
# TODO: Add docstring describing the reasoning behind every change on each
# SchemaChange subclass.
[docs]class SchemaChange:
severity = NotImplemented # type: SchemaChangeSeverity
format_str = NotImplemented # type: str
@property
def message(self) -> str:
return self.format_str.format(self=self)
def __eq__(self, other: Any) -> bool:
return isinstance(other, type(self)) and str(self) == str(other)
[docs]class TypeChangedKind(SchemaChange):
severity = SchemaChangeSeverity.BREAKING
format_str = (
"{self.type_name} changed from "
"{self.old_kind_name} type to {self.new_kind_name} type."
)
def __init__(
self,
type_name: str,
old_kind: Type[NamedType],
new_kind: Type[NamedType],
):
self.type_name = type_name
self.old_kind = old_kind
self.new_kind = new_kind
self.old_kind_name = old_kind.__name__[:-4]
self.new_kind_name = new_kind.__name__[:-4]
[docs]class TypeRemoved(SchemaChange):
severity = SchemaChangeSeverity.BREAKING
format_str = "Type {self.type_name} was removed."
def __init__(self, type_name: str):
self.type_name = type_name
[docs]class TypeAdded(SchemaChange):
severity = SchemaChangeSeverity.COMPATIBLE
format_str = "Type {self.type_name} was added."
def __init__(self, type_name: str):
self.type_name = type_name
[docs]class TypeRemovedFromUnion(SchemaChange):
severity = SchemaChangeSeverity.BREAKING
format_str = (
"{self.type_name} was removed from union type {self.union.name}."
)
def __init__(self, type_name: str, union: UnionType):
self.type_name = type_name
self.union = union
[docs]class TypeAddedToUnion(SchemaChange):
severity = SchemaChangeSeverity.DANGEROUS
format_str = "{self.type_name} was added to union type {self.union.name}."
def __init__(self, type_name: str, union: UnionType):
self.type_name = type_name
self.union = union
[docs]class TypeRemovedFromInterface(SchemaChange):
severity = SchemaChangeSeverity.BREAKING
format_str = "{self.type.name} no longer implements {self.interface.name}."
def __init__(self, interface: InterfaceType, object_type: ObjectType):
self.interface = interface
self.type = object_type
[docs]class TypeAddedToInterface(SchemaChange):
severity = SchemaChangeSeverity.DANGEROUS
format_str = "{self.type.name} now implements {self.interface.name}."
def __init__(self, interface: InterfaceType, object_type: ObjectType):
self.interface = interface
self.type = object_type
[docs]class EnumValueRemoved(SchemaChange):
severity = SchemaChangeSeverity.BREAKING
format_str = "{self.value.name} was removed from enum {self.enum.name}."
def __init__(self, enum: EnumType, value: EnumValue):
self.enum = enum
self.value = value
[docs]class EnumValueAdded(SchemaChange):
severity = SchemaChangeSeverity.DANGEROUS
format_str = "{self.value.name} was added to enum {self.enum.name}."
def __init__(self, enum: EnumType, value: EnumValue):
self.enum = enum
self.value = value
[docs]class EnumValueDeprecated(SchemaChange):
severity = SchemaChangeSeverity.COMPATIBLE
format_str = (
"{self.old_value.name} from enum {self.enum.name} was deprecated."
)
def __init__(
self, enum: EnumType, old_value: EnumValue, new_value: EnumValue
):
self.enum = enum
self.old_value = old_value
self.new_value = new_value
[docs]class EnumValueDeprecationRemoved(SchemaChange):
severity = SchemaChangeSeverity.COMPATIBLE
format_str = (
"{self.old_value.name} from enum {self.enum.name} "
"is no longer deprecated."
)
def __init__(
self, enum: EnumType, old_value: EnumValue, new_value: EnumValue
):
self.enum = enum
self.old_value = old_value
self.new_value = new_value
[docs]class EnumValueDeprecationReasonChanged(SchemaChange):
severity = SchemaChangeSeverity.COMPATIBLE
format_str = (
"{self.old_value.name} from enum {self.enum.name} "
"has changed deprecation reason."
)
def __init__(
self, enum: EnumType, old_value: EnumValue, new_value: EnumValue
):
self.enum = enum
self.old_value = old_value
self.new_value = new_value
[docs]class DirectiveRemoved(SchemaChange):
severity = SchemaChangeSeverity.BREAKING
format_str = "Directive {self.directive.name} was removed."
def __init__(self, directive: Directive):
self.directive = directive
[docs]class DirectiveAdded(SchemaChange):
severity = SchemaChangeSeverity.COMPATIBLE
format_str = "Directive {self.directive.name} was added."
def __init__(self, directive: Directive):
self.directive = directive
[docs]class DirectiveLocationRemoved(SchemaChange):
severity = SchemaChangeSeverity.BREAKING
format_str = (
"Location {self.location} was removed from directive "
"{self.directive.name}."
)
def __init__(self, directive: Directive, location: str):
self.directive = directive
self.location = location
[docs]class DirectiveLocationAdded(SchemaChange):
severity = SchemaChangeSeverity.COMPATIBLE
format_str = (
"Location {self.location} was added to directive "
"{self.directive.name}."
)
def __init__(self, directive: Directive, location: str):
self.directive = directive
self.location = location
[docs]class DirectiveArgumentRemoved(SchemaChange):
severity = SchemaChangeSeverity.BREAKING
format_str = (
"Argument {self.argument.name} was removed from directive "
"{self.directive.name}."
)
def __init__(self, directive: Directive, argument: Argument):
self.directive = directive
self.argument = argument
[docs]class DirectiveArgumentAdded(SchemaChange):
format_str = (
"{self.required_str} argument {self.argument.name} was added to "
"directive {self.directive.name}."
)
def __init__(self, directive: Directive, argument: Argument):
self.directive = directive
self.argument = argument
self.required_str = "Required" if argument.required else "Optional"
self.severity = (
SchemaChangeSeverity.BREAKING
if argument.required
else SchemaChangeSeverity.COMPATIBLE
)
[docs]class DirectiveArgumentDefaultValueChange(SchemaChange):
severity = SchemaChangeSeverity.DANGEROUS
format_str = (
"Argument {self.old_argument.name} of directive "
"{self.directive.name} has changed default value."
)
def __init__(
self,
directive: Directive,
old_argument: Argument,
new_argument: Argument,
):
self.directive = directive
self.old_argument = old_argument
self.new_argument = new_argument
[docs]class DirectiveArgumentChangedType(SchemaChange):
severity = SchemaChangeSeverity.BREAKING
format_str = (
"Argument {self.old_argument.name} of directive {self.directive.name} "
"has changed type from {self.old_argument.type} to {self.new_argument.type}."
)
def __init__(
self,
directive: Directive,
old_argument: Argument,
new_argument: Argument,
):
self.directive = directive
self.old_argument = old_argument
self.new_argument = new_argument
[docs]class FieldArgumentRemoved(SchemaChange):
severity = SchemaChangeSeverity.BREAKING
format_str = (
"Argument {self.argument.name} was removed from field "
"{self.field.name} of {self.context_str} {self.type.name}."
)
def __init__(
self,
parent_type: Union[ObjectType, InterfaceType],
field: Field,
argument: Argument,
):
self.type = parent_type
self.context_str = (
"type" if isinstance(parent_type, ObjectType) else "interface"
)
self.field = field
self.argument = argument
[docs]class FieldArgumentAdded(SchemaChange):
format_str = (
"{self.required_str} argument {self.argument.name} was added to field "
"{self.field.name} of {self.context_str} {self.type.name}."
)
def __init__(
self,
parent_type: Union[ObjectType, InterfaceType],
field: Field,
argument: Argument,
):
self.type = parent_type
self.context_str = (
"type" if isinstance(parent_type, ObjectType) else "interface"
)
self.field = field
self.argument = argument
self.required_str = "Required" if argument.required else "Optional"
self.severity = (
SchemaChangeSeverity.BREAKING
if argument.required
else SchemaChangeSeverity.COMPATIBLE
)
[docs]class FieldArgumentDefaultValueChange(SchemaChange):
severity = SchemaChangeSeverity.DANGEROUS
format_str = (
"Argument {self.old_argument.name} of field "
"{self.field.name} of {self.context_str} {self.type.name} "
"has changed default value."
)
def __init__(
self,
parent_type: Union[ObjectType, InterfaceType],
field: Field,
old_argument: Argument,
new_argument: Argument,
):
self.type = parent_type
self.context_str = (
"type" if isinstance(parent_type, ObjectType) else "interface"
)
self.field = field
self.old_argument = old_argument
self.new_argument = new_argument
[docs]class FieldArgumentChangedType(SchemaChange):
severity = SchemaChangeSeverity.BREAKING
format_str = (
"Argument {self.old_argument.name} of field "
"{self.field.name} of {self.context_str} {self.type.name} "
"has changed type from {self.old_argument.type} "
"to {self.new_argument.type}."
)
def __init__(
self,
parent_type: Union[ObjectType, InterfaceType],
field: Field,
old_argument: Argument,
new_argument: Argument,
):
self.type = parent_type
self.context_str = (
"type" if isinstance(parent_type, ObjectType) else "interface"
)
self.field = field
self.old_argument = old_argument
self.new_argument = new_argument
[docs]class FieldChangedType(SchemaChange):
severity = SchemaChangeSeverity.BREAKING
format_str = (
"Field {self.old_field.name} of {self.context_str} {self.type.name} has "
"changed type from {self.old_field.type} to {self.new_field.type}."
)
def __init__(
self,
parent_type: Union[ObjectType, InterfaceType],
old_field: Field,
new_field: Field,
):
self.type = parent_type
self.context_str = (
"type" if isinstance(parent_type, ObjectType) else "interface"
)
self.old_field = old_field
self.new_field = new_field
[docs]class FieldRemoved(SchemaChange):
severity = SchemaChangeSeverity.BREAKING
format_str = (
"Field {self.field.name} was removed "
"from {self.context_str} {self.type.name}."
)
def __init__(
self, parent_type: Union[ObjectType, InterfaceType], field: Field
):
self.type = parent_type
self.context_str = (
"type" if isinstance(parent_type, ObjectType) else "interface"
)
self.field = field
[docs]class FieldAdded(SchemaChange):
severity = SchemaChangeSeverity.COMPATIBLE
format_str = (
"Field {self.field.name} was added to "
"{self.context_str} {self.type.name}."
)
def __init__(
self, parent_type: Union[ObjectType, InterfaceType], field: Field
):
self.type = parent_type
self.context_str = (
"type" if isinstance(parent_type, ObjectType) else "interface"
)
self.field = field
[docs]class FieldDeprecated(SchemaChange):
severity = SchemaChangeSeverity.COMPATIBLE
format_str = (
"Field {self.old_field.name} of {self.context_str} {self.type.name} "
"was deprecated."
)
def __init__(
self,
parent_type: Union[ObjectType, InterfaceType],
old_field: Field,
new_field: Field,
):
self.type = parent_type
self.context_str = (
"type" if isinstance(parent_type, ObjectType) else "interface"
)
self.old_field = old_field
self.new_field = new_field
[docs]class FieldDeprecationRemoved(SchemaChange):
severity = SchemaChangeSeverity.COMPATIBLE
format_str = (
"Field {self.old_field.name} of {self.context_str} {self.type.name} "
"is no longer deprecated."
)
def __init__(
self,
parent_type: Union[ObjectType, InterfaceType],
old_field: Field,
new_field: Field,
):
self.type = parent_type
self.context_str = (
"type" if isinstance(parent_type, ObjectType) else "interface"
)
self.old_field = old_field
self.new_field = new_field
[docs]class FieldDeprecationReasonChanged(SchemaChange):
severity = SchemaChangeSeverity.COMPATIBLE
format_str = (
"Field {self.old_field.name} of {self.context_str} {self.type.name} has "
"changed deprecation reason."
)
def __init__(
self,
parent_type: Union[ObjectType, InterfaceType],
old_field: Field,
new_field: Field,
):
self.type = parent_type
self.context_str = (
"type" if isinstance(parent_type, ObjectType) else "interface"
)
self.old_field = old_field
self.new_field = new_field