diff --git a/graphene/__init__.py b/graphene/__init__.py index 7a01ed16..0e6c907c 100644 --- a/graphene/__init__.py +++ b/graphene/__init__.py @@ -26,7 +26,7 @@ if not __SETUP__: InputField, Schema, Scalar, - String, ID, Int, Float, Boolean, + String, ID, Int, Float, Boolean, OmniScalar, List, NonNull, Enum, Argument, diff --git a/graphene/types/__init__.py b/graphene/types/__init__.py index fcb3fbfd..84e39731 100644 --- a/graphene/types/__init__.py +++ b/graphene/types/__init__.py @@ -4,7 +4,7 @@ from .objecttype import ObjectType from .abstracttype import AbstractType from .interface import Interface from .mutation import Mutation -from .scalars import Scalar, String, ID, Int, Float, Boolean +from .scalars import Scalar, String, ID, Int, Float, Boolean, OmniScalar from .schema import Schema from .structures import List, NonNull from .enum import Enum @@ -32,6 +32,7 @@ __all__ = [ 'Int', 'Float', 'Boolean', + 'OmniScalar', 'List', 'NonNull', 'Argument', diff --git a/graphene/types/scalars.py b/graphene/types/scalars.py index 4e6b94b9..d4bf4675 100644 --- a/graphene/types/scalars.py +++ b/graphene/types/scalars.py @@ -9,7 +9,6 @@ from .unmountedtype import UnmountedType class ScalarTypeMeta(type): - def __new__(cls, name, bases, attrs): # Also ensure initialization is only performed for subclasses of Model # (excluding Model class itself). @@ -49,6 +48,7 @@ class Scalar(six.with_metaclass(ScalarTypeMeta, UnmountedType)): ''' return cls + # As per the GraphQL Spec, Integers are only treated as valid when a valid # 32-bit signed integer, providing the broadest support across platforms. # @@ -164,3 +164,43 @@ class ID(Scalar): def parse_literal(ast): if isinstance(ast, (StringValue, IntValue)): return ast.value + + +class OmniScalar(Scalar): + ''' + The `OmniScalar` type represents any kind of scalar type. For values + whose type is nondeterministic, an OmniScalar will parse and serialize it + appropriately. Non-scalar types (lists and objects) are considered null. + ''' + + _scalar_type_map = { + str: String, + unicode: String, + bool: Boolean, + int: Int, + float: Float, + } + + @staticmethod + def serialize(value): + scalar_type = OmniScalar._scalar_type_map.get(type(value)) + if scalar_type: + return scalar_type.serialize(value) + else: + return None + + @staticmethod + def parse_value(value): + scalar_type = OmniScalar._scalar_type_map.get(type(value)) + if scalar_type: + return scalar_type.parse_value(value) + else: + return None + + @staticmethod + def parse_literal(ast): + scalar_type = OmniScalar._scalar_type_map.get(type(ast)) + if scalar_type: + return scalar_type.parse_literal(ast) + else: + return None diff --git a/graphene/types/tests/test_scalars_serialization.py b/graphene/types/tests/test_scalars_serialization.py index eab09e64..6be1c8b9 100644 --- a/graphene/types/tests/test_scalars_serialization.py +++ b/graphene/types/tests/test_scalars_serialization.py @@ -1,4 +1,4 @@ -from ..scalars import Boolean, Float, Int, String +from ..scalars import Boolean, Float, Int, String, OmniScalar def test_serializes_output_int(): @@ -48,3 +48,28 @@ def test_serializes_output_boolean(): assert Boolean.serialize(0) is False assert Boolean.serialize(True) is True assert Boolean.serialize(False) is False + + +def test_serializes_output_omniscalar(): + # Int + assert OmniScalar.serialize(1) == 1 + assert OmniScalar.serialize(0) == 0 + assert OmniScalar.serialize(-1) == -1 + # Float + assert OmniScalar.serialize(0.1) == 0.1 + assert OmniScalar.serialize(1.1) == 1.1 + assert OmniScalar.serialize(-1.1) == -1.1 + # String + assert OmniScalar.serialize('string') == 'string' + assert OmniScalar.serialize(u'\U0001F601') == u'\U0001F601' + # Boolean + assert OmniScalar.serialize(False) is False + assert OmniScalar.serialize(True) is True + # Other + assert OmniScalar.serialize(None) is None + assert OmniScalar.serialize([]) is None + assert OmniScalar.serialize({}) is None + assert OmniScalar.serialize(object()) is None + assert OmniScalar.serialize(object) is None + assert OmniScalar.serialize(lambda _: '') is None +