def _get_entity_type(type_map: TypeMap): # https://www.apollographql.com/docs/apollo-server/federation/federation-spec/#resolve-requests-for-entities # To implement the _Entity union, each type annotated with @key # should be added to the _Entity union. federation_key_types = [ type.implementation for type in type_map.values() if _has_federation_keys(type.definition) ] # If no types are annotated with the key directive, then the _Entity # union and Query._entities field should be removed from the schema. if not federation_key_types: return None entity_type = GraphQLUnionType("_Entity", federation_key_types) # type: ignore def _resolve_type(self, value, _type): return type_map[self._type_definition.name].implementation entity_type.resolve_type = _resolve_type return entity_type
def union(name: str, types: typing.Tuple[typing.Type], *, description=None): """Creates a new named Union type. Example usages: >>> strawberry.union( >>> "Name", >>> (A, B), >>> ) >>> strawberry.union( >>> "Name", >>> (A, B), >>> ) """ from .type_converter import get_graphql_type_for_annotation def _resolve_type(root, info, _type): if not hasattr(root, "graphql_type"): raise WrongReturnTypeForUnion(info.field_name, str(type(root))) if is_generic(type(root)): return _find_type_for_generic_union(root) if root.graphql_type not in _type.types: raise UnallowedReturnTypeForUnion(info.field_name, str(type(root)), _type.types) return root.graphql_type # TODO: union types don't work with scalar types # so we want to return a nice error # also we want to make sure we have been passed # strawberry types graphql_type = GraphQLUnionType( name, [ get_graphql_type_for_annotation(type, name, force_optional=True) for type in types ], description=description, ) graphql_type.resolve_type = _resolve_type # This is currently a temporary solution, this is ok for now # But in future we might want to change this so that it works # properly with mypy, but there's no way to return a type like NewType does # so we return this class instance as it allows us to reuse the rest of # our code without doing too many changes class X: def __init__(self, graphql_type): self.graphql_type = graphql_type def __call__(self): raise ValueError("Cannot use union type directly") return X(graphql_type)
def get_graphql_type_for_annotation(annotation, field_name: str, force_optional: bool = False): # TODO: this might lead to issues with types that have a field value is_field_optional = force_optional if hasattr(annotation, "field"): graphql_type = annotation.field else: annotation_name = getattr(annotation, "_name", None) if annotation_name == "List": list_of_type = get_graphql_type_for_annotation( annotation.__args__[0], field_name) return GraphQLList(list_of_type) annotation_origin = getattr(annotation, "__origin__", None) if annotation_origin == AsyncGenerator: # async generators are used in subscription, we only need the yield type # https://docs.python.org/3/library/typing.html#typing.AsyncGenerator return get_graphql_type_for_annotation(annotation.__args__[0], field_name) elif is_union(annotation): types = annotation.__args__ non_none_types = [x for x in types if x != None.__class__] # noqa:E721 # optionals are represented as Union[type, None] if len(non_none_types) == 1: is_field_optional = True graphql_type = get_graphql_type_for_annotation( non_none_types[0], field_name, force_optional=True) else: is_field_optional = None.__class__ in types def _resolve_type(self, value, _type): if not hasattr(self, "field"): raise WrongReturnTypeForUnion(value.field_name, str(type(self))) if self.field not in _type.types: raise UnallowedReturnTypeForUnion( value.field_name, str(type(self)), _type.types) return self.field # TODO: union types don't work with scalar types # so we want to return a nice error # also we want to make sure we have been passed # strawberry types graphql_type = GraphQLUnionType(field_name, [type.field for type in types]) graphql_type.resolve_type = _resolve_type else: graphql_type = REGISTRY.get(annotation) if not graphql_type: raise ValueError(f"Unable to get GraphQL type for {annotation}") if is_field_optional: return graphql_type return GraphQLNonNull(graphql_type)