mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-22 09:36:44 +03:00
Merge branch 'master' into feature/date-improvements
# Conflicts: # graphene/types/datetime.py
This commit is contained in:
commit
85e354c139
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -42,6 +42,7 @@ htmlcov/
|
|||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
.pytest_cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
|
|
|
@ -21,7 +21,7 @@ developer has to write to use them.
|
|||
|
||||
|
||||
> The type metaclasses are now deleted as they are no longer necessary. If your code was depending
|
||||
> on this strategy for creating custom attrs, see an [example on how to do it in 2.0](https://github.com/graphql-python/graphene/blob/2.0/graphene/tests/issues/test_425.py).
|
||||
> on this strategy for creating custom attrs, see an [example on how to do it in 2.0](https://github.com/graphql-python/graphene/blob/v2.0.0/graphene/tests/issues/test_425.py).
|
||||
|
||||
## Deprecations
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ Middleware
|
|||
|
||||
You can use ``middleware`` to affect the evaluation of fields in your schema.
|
||||
|
||||
A middleware is any object that responds to ``resolve(*args, next_middleware)``.
|
||||
A middleware is any object or function that responds to ``resolve(next_middleware, *args)``.
|
||||
|
||||
Inside that method, it should either:
|
||||
|
||||
|
@ -18,10 +18,8 @@ Middlewares ``resolve`` is invoked with several arguments:
|
|||
|
||||
- ``next`` represents the execution chain. Call ``next`` to continue evalution.
|
||||
- ``root`` is the root value object passed throughout the query.
|
||||
- ``args`` is the hash of arguments passed to the field.
|
||||
- ``context`` is the context object passed throughout the query.
|
||||
- ``info`` is the resolver info.
|
||||
|
||||
- ``args`` is the dict of arguments passed to the field.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
@ -42,3 +40,32 @@ And then execute it with:
|
|||
.. code:: python
|
||||
|
||||
result = schema.execute('THE QUERY', middleware=[AuthorizationMiddleware()])
|
||||
|
||||
|
||||
Functional example
|
||||
------------------
|
||||
|
||||
Middleware can also be defined as a function. Here we define a middleware that
|
||||
logs the time it takes to resolve each field
|
||||
|
||||
.. code:: python
|
||||
|
||||
from time import time as timer
|
||||
|
||||
def timing_middleware(next, root, info, **args):
|
||||
start = timer()
|
||||
return_value = next(root, info, **args)
|
||||
duration = timer() - start
|
||||
logger.debug("{parent_type}.{field_name}: {duration} ms".format(
|
||||
parent_type=root._meta.name if root and hasattr(root, '_meta') else '',
|
||||
field_name=info.field_name,
|
||||
duration=round(duration * 1000, 2)
|
||||
))
|
||||
return return_value
|
||||
|
||||
|
||||
And then execute it with:
|
||||
|
||||
.. code:: python
|
||||
|
||||
result = schema.execute('THE QUERY', middleware=[timing_middleware])
|
||||
|
|
|
@ -21,9 +21,9 @@ Useful links
|
|||
- `Relay Cursor Connection Specification`_
|
||||
- `Relay input Object Mutation`_
|
||||
|
||||
.. _Relay: https://facebook.github.io/relay/docs/graphql-relay-specification.html
|
||||
.. _Relay: https://facebook.github.io/relay/docs/en/graphql-server-specification.html
|
||||
.. _Relay specification: https://facebook.github.io/relay/graphql/objectidentification.htm#sec-Node-root-field
|
||||
.. _Getting started with Relay: https://facebook.github.io/relay/docs/graphql-relay-specification.html
|
||||
.. _Getting started with Relay: https://facebook.github.io/relay/docs/en/quick-start-guide.html
|
||||
.. _Relay Global Identification Specification: https://facebook.github.io/relay/graphql/objectidentification.htm
|
||||
.. _Relay Cursor Connection Specification: https://facebook.github.io/relay/graphql/connections.htm
|
||||
.. _Relay input Object Mutation: https://facebook.github.io/relay/graphql/mutations.htm
|
||||
|
|
|
@ -55,12 +55,12 @@ Example of a custom node:
|
|||
return '{}:{}'.format(type, id)
|
||||
|
||||
@staticmethod
|
||||
def get_node_from_global_id(info global_id, only_type=None):
|
||||
def get_node_from_global_id(info, global_id, only_type=None):
|
||||
type, id = global_id.split(':')
|
||||
if only_node:
|
||||
if only_type:
|
||||
# We assure that the node type that we want to retrieve
|
||||
# is the same that was indicated in the field type
|
||||
assert type == only_node._meta.name, 'Received not compatible node.'
|
||||
assert type == only_type._meta.name, 'Received not compatible node.'
|
||||
|
||||
if type == 'User':
|
||||
return get_user(id)
|
||||
|
@ -75,10 +75,10 @@ Accessing node types
|
|||
--------------------
|
||||
|
||||
If we want to retrieve node instances from a ``global_id`` (scalar that identifies an instance by it's type name and id),
|
||||
we can simply do ``Node.get_node_from_global_id(global_id, context, info)``.
|
||||
we can simply do ``Node.get_node_from_global_id(info, global_id)``.
|
||||
|
||||
In the case we want to restrict the instance retrieval to a specific type, we can do:
|
||||
``Node.get_node_from_global_id(global_id, context, info, only_type=Ship)``. This will raise an error
|
||||
``Node.get_node_from_global_id(info, global_id, only_type=Ship)``. This will raise an error
|
||||
if the ``global_id`` doesn't correspond to a Ship type.
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Required library
|
||||
Sphinx==1.5.3
|
||||
# Docs template
|
||||
https://github.com/graphql-python/graphene-python.org/archive/docs.zip
|
||||
http://graphene-python.org/sphinx_graphene_theme.zip
|
||||
|
|
|
@ -54,6 +54,13 @@ the ``Enum.from_enum`` function.
|
|||
|
||||
graphene.Enum.from_enum(AlreadyExistingPyEnum)
|
||||
|
||||
``Enum.from_enum`` supports a ``description`` and ``deprecation_reason`` lambdas as input so
|
||||
you can add description etc. to your enum without changing the original:
|
||||
|
||||
.. code:: python
|
||||
|
||||
graphene.Enum.from_enum(AlreadyExistingPyEnum, description=lambda value: return 'foo' if value == AlreadyExistingPyEnum.Foo else 'bar')
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
|
|
@ -48,3 +48,24 @@ Lists work in a similar way: We can use a type modifier to mark a type as a
|
|||
``List``, which indicates that this field will return a list of that type.
|
||||
It works the same for arguments, where the validation step will expect a list
|
||||
for that value.
|
||||
|
||||
NonNull Lists
|
||||
-------------
|
||||
|
||||
By default items in a list will be considered nullable. To define a list without
|
||||
any nullable items the type needs to be marked as ``NonNull``. For example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
import graphene
|
||||
|
||||
class Character(graphene.ObjectType):
|
||||
appears_in = graphene.List(graphene.NonNull(graphene.String))
|
||||
|
||||
The above results in the type definition:
|
||||
|
||||
.. code::
|
||||
|
||||
type Character {
|
||||
appearsIn: [String!]
|
||||
}
|
||||
|
|
|
@ -1,19 +1,83 @@
|
|||
Scalars
|
||||
=======
|
||||
|
||||
All Scalar types accept the following arguments. All are optional:
|
||||
|
||||
``name``: *string*
|
||||
|
||||
Override the name of the Field.
|
||||
|
||||
``description``: *string*
|
||||
|
||||
A description of the type to show in the GraphiQL browser.
|
||||
|
||||
``required``: *boolean*
|
||||
|
||||
If ``True``, the server will enforce a value for this field. See `NonNull <./list-and-nonnull.html#nonnull>`_. Default is ``False``.
|
||||
|
||||
``deprecation_reason``: *string*
|
||||
|
||||
Provide a deprecation reason for the Field.
|
||||
|
||||
``default_value``: *any*
|
||||
|
||||
Provide a default value for the Field.
|
||||
|
||||
|
||||
|
||||
Base scalars
|
||||
------------
|
||||
|
||||
Graphene defines the following base Scalar Types:
|
||||
|
||||
- ``graphene.String``
|
||||
- ``graphene.Int``
|
||||
- ``graphene.Float``
|
||||
- ``graphene.Boolean``
|
||||
- ``graphene.ID``
|
||||
``graphene.String``
|
||||
|
||||
Represents textual data, represented as UTF-8
|
||||
character sequences. The String type is most often used by GraphQL to
|
||||
represent free-form human-readable text.
|
||||
|
||||
``graphene.Int``
|
||||
|
||||
Represents non-fractional signed whole numeric
|
||||
values. Int can represent values between `-(2^53 - 1)` and `2^53 - 1` since
|
||||
represented in JSON as double-precision floating point numbers specified
|
||||
by `IEEE 754 <http://en.wikipedia.org/wiki/IEEE_floating_point>`_.
|
||||
|
||||
``graphene.Float``
|
||||
|
||||
Represents signed double-precision fractional
|
||||
values as specified by
|
||||
`IEEE 754 <http://en.wikipedia.org/wiki/IEEE_floating_point>`_.
|
||||
|
||||
``graphene.Boolean``
|
||||
|
||||
Represents `true` or `false`.
|
||||
|
||||
``graphene.ID``
|
||||
|
||||
Represents a unique identifier, often used to
|
||||
refetch an object or as key for a cache. The ID type appears in a JSON
|
||||
response as a String; however, it is not intended to be human-readable.
|
||||
When expected as an input type, any string (such as `"4"`) or integer
|
||||
(such as `4`) input value will be accepted as an ID.
|
||||
|
||||
Graphene also provides custom scalars for Dates, Times, and JSON:
|
||||
|
||||
- ``graphene.types.datetime.DateTime``
|
||||
- ``graphene.types.datetime.Time``
|
||||
- ``graphene.types.json.JSONString``
|
||||
``graphene.types.datetime.Date``
|
||||
|
||||
Represents a Date value as specified by `iso8601 <https://en.wikipedia.org/wiki/ISO_8601>`_.
|
||||
|
||||
``graphene.types.datetime.DateTime``
|
||||
|
||||
Represents a DateTime value as specified by `iso8601 <https://en.wikipedia.org/wiki/ISO_8601>`_.
|
||||
|
||||
``graphene.types.datetime.Time``
|
||||
|
||||
Represents a Time value as specified by `iso8601 <https://en.wikipedia.org/wiki/ISO_8601>`_.
|
||||
|
||||
``graphene.types.json.JSONString``
|
||||
|
||||
Represents a JSON string.
|
||||
|
||||
|
||||
Custom scalars
|
||||
|
|
|
@ -12,8 +12,8 @@ The basics:
|
|||
Quick example
|
||||
-------------
|
||||
|
||||
This example model defines a ``Character`` interface with a name. ``Human``
|
||||
and ``Droid`` are two implementations of that interface.
|
||||
This example model defines several ObjectTypes with their own fields.
|
||||
``SearchResult`` is the implementation of ``Union`` of this object types.
|
||||
|
||||
.. code:: python
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ from .utils.resolve_only_args import resolve_only_args
|
|||
from .utils.module_loading import lazy_import
|
||||
|
||||
|
||||
VERSION = (2, 0, 0, 'final', 0)
|
||||
VERSION = (2, 0, 1, 'final', 0)
|
||||
|
||||
__version__ = get_version(VERSION)
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ class Connection(ObjectType):
|
|||
edge = type(edge_name, edge_bases, {})
|
||||
cls.Edge = edge
|
||||
|
||||
_meta.name = name
|
||||
options['name'] = name
|
||||
_meta.node = node
|
||||
_meta.fields = OrderedDict([
|
||||
('page_info', Field(PageInfo, name='pageInfo', required=True)),
|
||||
|
@ -99,16 +99,19 @@ class IterableConnectionField(Field):
|
|||
def type(self):
|
||||
type = super(IterableConnectionField, self).type
|
||||
connection_type = type
|
||||
if is_node(type):
|
||||
if isinstance(type, NonNull):
|
||||
connection_type = type.of_type
|
||||
|
||||
if is_node(connection_type):
|
||||
raise Exception(
|
||||
"ConnectionField's now need a explicit ConnectionType for Nodes.\n"
|
||||
"Read more: https://github.com/graphql-python/graphene/blob/2.0/UPGRADE-v2.0.md#node-connections"
|
||||
"Read more: https://github.com/graphql-python/graphene/blob/v2.0.0/UPGRADE-v2.0.md#node-connections"
|
||||
)
|
||||
|
||||
assert issubclass(connection_type, Connection), (
|
||||
'{} type have to be a subclass of Connection. Received "{}".'
|
||||
).format(self.__class__.__name__, connection_type)
|
||||
return connection_type
|
||||
return type
|
||||
|
||||
@classmethod
|
||||
def resolve_connection(cls, connection_type, args, resolved):
|
||||
|
@ -133,6 +136,9 @@ class IterableConnectionField(Field):
|
|||
def connection_resolver(cls, resolver, connection_type, root, info, **args):
|
||||
resolved = resolver(root, info, **args)
|
||||
|
||||
if isinstance(connection_type, NonNull):
|
||||
connection_type = connection_type.of_type
|
||||
|
||||
on_resolve = partial(cls.resolve_connection, connection_type, args)
|
||||
if is_thenable(resolved):
|
||||
return Promise.resolve(resolved).then(on_resolve)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import pytest
|
||||
|
||||
from ...types import Argument, Field, Int, List, NonNull, ObjectType, String
|
||||
from ...types import Argument, Field, Int, List, NonNull, ObjectType, String, Schema
|
||||
from ..connection import Connection, ConnectionField, PageInfo
|
||||
from ..node import Node
|
||||
|
||||
|
@ -52,6 +52,21 @@ def test_connection_inherit_abstracttype():
|
|||
assert list(fields.keys()) == ['page_info', 'edges', 'extra']
|
||||
|
||||
|
||||
def test_connection_name():
|
||||
custom_name = "MyObjectCustomNameConnection"
|
||||
|
||||
class BaseConnection(object):
|
||||
extra = String()
|
||||
|
||||
class MyObjectConnection(BaseConnection, Connection):
|
||||
|
||||
class Meta:
|
||||
node = MyObject
|
||||
name = custom_name
|
||||
|
||||
assert MyObjectConnection._meta.name == custom_name
|
||||
|
||||
|
||||
def test_edge():
|
||||
class MyObjectConnection(Connection):
|
||||
|
||||
|
@ -125,6 +140,7 @@ def test_connectionfield_node_deprecated():
|
|||
|
||||
assert "ConnectionField's now need a explicit ConnectionType for Nodes." in str(exc_info.value)
|
||||
|
||||
|
||||
def test_connectionfield_custom_args():
|
||||
class MyObjectConnection(Connection):
|
||||
|
||||
|
@ -139,3 +155,23 @@ def test_connectionfield_custom_args():
|
|||
'last': Argument(Int),
|
||||
'extra': Argument(String),
|
||||
}
|
||||
|
||||
|
||||
def test_connectionfield_required():
|
||||
class MyObjectConnection(Connection):
|
||||
|
||||
class Meta:
|
||||
node = MyObject
|
||||
|
||||
class Query(ObjectType):
|
||||
test_connection = ConnectionField(MyObjectConnection, required=True)
|
||||
|
||||
def resolve_test_connection(root, info, **args):
|
||||
return []
|
||||
|
||||
schema = Schema(query=Query)
|
||||
executed = schema.execute(
|
||||
'{ testConnection { edges { cursor } } }'
|
||||
)
|
||||
assert not executed.errors
|
||||
assert executed.data == {'testConnection': {'edges': []}}
|
||||
|
|
|
@ -2,8 +2,11 @@
|
|||
# Adapted for Graphene 2.0
|
||||
|
||||
from graphene.types.objecttype import ObjectType, ObjectTypeOptions
|
||||
from graphene.types.inputobjecttype import InputObjectType, InputObjectTypeOptions
|
||||
from graphene.types.enum import Enum, EnumOptions
|
||||
|
||||
|
||||
# ObjectType
|
||||
class SpecialOptions(ObjectTypeOptions):
|
||||
other_attr = None
|
||||
|
||||
|
@ -40,3 +43,77 @@ def test_special_objecttype_inherit_meta_options():
|
|||
assert MyType._meta.name == 'MyType'
|
||||
assert MyType._meta.default_resolver is None
|
||||
assert MyType._meta.interfaces == ()
|
||||
|
||||
|
||||
# InputObjectType
|
||||
class SpecialInputObjectTypeOptions(ObjectTypeOptions):
|
||||
other_attr = None
|
||||
|
||||
|
||||
class SpecialInputObjectType(InputObjectType):
|
||||
|
||||
@classmethod
|
||||
def __init_subclass_with_meta__(cls, other_attr='default', **options):
|
||||
_meta = SpecialInputObjectTypeOptions(cls)
|
||||
_meta.other_attr = other_attr
|
||||
super(SpecialInputObjectType, cls).__init_subclass_with_meta__(_meta=_meta, **options)
|
||||
|
||||
|
||||
def test_special_inputobjecttype_could_be_subclassed():
|
||||
class MyInputObjectType(SpecialInputObjectType):
|
||||
|
||||
class Meta:
|
||||
other_attr = 'yeah!'
|
||||
|
||||
assert MyInputObjectType._meta.other_attr == 'yeah!'
|
||||
|
||||
|
||||
def test_special_inputobjecttype_could_be_subclassed_default():
|
||||
class MyInputObjectType(SpecialInputObjectType):
|
||||
pass
|
||||
|
||||
assert MyInputObjectType._meta.other_attr == 'default'
|
||||
|
||||
|
||||
def test_special_inputobjecttype_inherit_meta_options():
|
||||
class MyInputObjectType(SpecialInputObjectType):
|
||||
pass
|
||||
|
||||
assert MyInputObjectType._meta.name == 'MyInputObjectType'
|
||||
|
||||
|
||||
# Enum
|
||||
class SpecialEnumOptions(EnumOptions):
|
||||
other_attr = None
|
||||
|
||||
|
||||
class SpecialEnum(Enum):
|
||||
|
||||
@classmethod
|
||||
def __init_subclass_with_meta__(cls, other_attr='default', **options):
|
||||
_meta = SpecialEnumOptions(cls)
|
||||
_meta.other_attr = other_attr
|
||||
super(SpecialEnum, cls).__init_subclass_with_meta__(_meta=_meta, **options)
|
||||
|
||||
|
||||
def test_special_enum_could_be_subclassed():
|
||||
class MyEnum(SpecialEnum):
|
||||
|
||||
class Meta:
|
||||
other_attr = 'yeah!'
|
||||
|
||||
assert MyEnum._meta.other_attr == 'yeah!'
|
||||
|
||||
|
||||
def test_special_enum_could_be_subclassed_default():
|
||||
class MyEnum(SpecialEnum):
|
||||
pass
|
||||
|
||||
assert MyEnum._meta.other_attr == 'default'
|
||||
|
||||
|
||||
def test_special_enum_inherit_meta_options():
|
||||
class MyEnum(SpecialEnum):
|
||||
pass
|
||||
|
||||
assert MyEnum._meta.name == 'MyEnum'
|
||||
|
|
|
@ -7,6 +7,6 @@ class AbstractType(SubclassWithMeta):
|
|||
def __init_subclass__(cls, *args, **kwargs):
|
||||
warn_deprecation(
|
||||
"Abstract type is deprecated, please use normal object inheritance instead.\n"
|
||||
"See more: https://github.com/graphql-python/graphene/blob/2.0/UPGRADE-v2.0.md#deprecations"
|
||||
"See more: https://github.com/graphql-python/graphene/blob/master/UPGRADE-v2.0.md#deprecations"
|
||||
)
|
||||
super(AbstractType, cls).__init_subclass__(*args, **kwargs)
|
||||
|
|
|
@ -21,7 +21,7 @@ class BaseOptions(object):
|
|||
raise Exception("Can't modify frozen Options {0}".format(self))
|
||||
|
||||
def __repr__(self):
|
||||
return "<{} type={}>".format(self.__class__.__name__, self.class_type.__name__)
|
||||
return "<{} name={}>".format(self.__class__.__name__, repr(self.name))
|
||||
|
||||
|
||||
class BaseType(SubclassWithMeta):
|
||||
|
|
|
@ -31,7 +31,10 @@ class Date(Scalar):
|
|||
|
||||
@staticmethod
|
||||
def parse_value(value):
|
||||
try:
|
||||
return parse_date(value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
class DateTime(Scalar):
|
||||
|
@ -55,7 +58,10 @@ class DateTime(Scalar):
|
|||
|
||||
@staticmethod
|
||||
def parse_value(value):
|
||||
try:
|
||||
return parse_datetime(value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
class Time(Scalar):
|
||||
|
@ -79,4 +85,7 @@ class Time(Scalar):
|
|||
|
||||
@classmethod
|
||||
def parse_value(cls, value):
|
||||
try:
|
||||
return parse_time(value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
|
|
@ -27,7 +27,11 @@ class EnumOptions(BaseOptions):
|
|||
class EnumMeta(SubclassWithMeta_Meta):
|
||||
|
||||
def __new__(cls, name, bases, classdict, **options):
|
||||
enum = PyEnum(cls.__name__, OrderedDict(classdict, __eq__=eq_enum))
|
||||
enum_members = OrderedDict(classdict, __eq__=eq_enum)
|
||||
# We remove the Meta attribute from the class to not collide
|
||||
# with the enum values.
|
||||
enum_members.pop('Meta', None)
|
||||
enum = PyEnum(cls.__name__, enum_members)
|
||||
return SubclassWithMeta_Meta.__new__(cls, name, bases, OrderedDict(classdict, __enum__=enum), **options)
|
||||
|
||||
def get(cls, value):
|
||||
|
@ -60,7 +64,8 @@ class EnumMeta(SubclassWithMeta_Meta):
|
|||
class Enum(six.with_metaclass(EnumMeta, UnmountedType, BaseType)):
|
||||
|
||||
@classmethod
|
||||
def __init_subclass_with_meta__(cls, enum=None, **options):
|
||||
def __init_subclass_with_meta__(cls, enum=None, _meta=None, **options):
|
||||
if not _meta:
|
||||
_meta = EnumOptions(cls)
|
||||
_meta.enum = enum or cls.__enum__
|
||||
_meta.deprecation_reason = options.pop('deprecation_reason', None)
|
||||
|
|
|
@ -5,7 +5,6 @@ from .inputfield import InputField
|
|||
from .unmountedtype import UnmountedType
|
||||
from .utils import yank_fields_from_attrs
|
||||
|
||||
|
||||
# For static type checking with Mypy
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
|
@ -14,7 +13,7 @@ if MYPY:
|
|||
|
||||
class InputObjectTypeOptions(BaseOptions):
|
||||
fields = None # type: Dict[str, InputField]
|
||||
create_container = None # type: Callable
|
||||
container = None # type: InputObjectTypeContainer
|
||||
|
||||
|
||||
class InputObjectTypeContainer(dict, BaseType):
|
||||
|
@ -23,8 +22,8 @@ class InputObjectTypeContainer(dict, BaseType):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
dict.__init__(self, *args, **kwargs)
|
||||
for key, value in self.items():
|
||||
setattr(self, key, value)
|
||||
for key in self._meta.fields.keys():
|
||||
setattr(self, key, self.get(key, None))
|
||||
|
||||
def __init_subclass__(cls, *args, **kwargs):
|
||||
pass
|
||||
|
@ -41,7 +40,8 @@ class InputObjectType(UnmountedType, BaseType):
|
|||
'''
|
||||
|
||||
@classmethod
|
||||
def __init_subclass_with_meta__(cls, container=None, **options):
|
||||
def __init_subclass_with_meta__(cls, container=None, _meta=None, **options):
|
||||
if not _meta:
|
||||
_meta = InputObjectTypeOptions(cls)
|
||||
|
||||
fields = OrderedDict()
|
||||
|
@ -54,7 +54,8 @@ class InputObjectType(UnmountedType, BaseType):
|
|||
if container is None:
|
||||
container = type(cls.__name__, (InputObjectTypeContainer, cls), {})
|
||||
_meta.container = container
|
||||
super(InputObjectType, cls).__init_subclass_with_meta__(_meta=_meta, **options)
|
||||
super(InputObjectType, cls).__init_subclass_with_meta__(
|
||||
_meta=_meta, **options)
|
||||
|
||||
@classmethod
|
||||
def get_type(cls):
|
||||
|
|
|
@ -50,7 +50,8 @@ class Mutation(ObjectType):
|
|||
warn_deprecation((
|
||||
"Please use {name}.Arguments instead of {name}.Input."
|
||||
"Input is now only used in ClientMutationID.\n"
|
||||
"Read more: https://github.com/graphql-python/graphene/blob/2.0/UPGRADE-v2.0.md#mutation-input"
|
||||
"Read more:"
|
||||
" https://github.com/graphql-python/graphene/blob/v2.0.0/UPGRADE-v2.0.md#mutation-input"
|
||||
).format(name=cls.__name__))
|
||||
|
||||
if input_class:
|
||||
|
@ -72,10 +73,17 @@ class Mutation(ObjectType):
|
|||
_meta.resolver = resolver
|
||||
_meta.arguments = arguments
|
||||
|
||||
super(Mutation, cls).__init_subclass_with_meta__(_meta=_meta, **options)
|
||||
super(Mutation, cls).__init_subclass_with_meta__(
|
||||
_meta=_meta, **options)
|
||||
|
||||
@classmethod
|
||||
def Field(cls, *args, **kwargs):
|
||||
def Field(cls, name=None, description=None, deprecation_reason=None, required=False):
|
||||
return Field(
|
||||
cls._meta.output, args=cls._meta.arguments, resolver=cls._meta.resolver
|
||||
cls._meta.output,
|
||||
args=cls._meta.arguments,
|
||||
resolver=cls._meta.resolver,
|
||||
name=name,
|
||||
description=description,
|
||||
deprecation_reason=deprecation_reason,
|
||||
required=required,
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import inspect
|
||||
|
||||
from graphql import GraphQLSchema, graphql, is_type
|
||||
from graphql import GraphQLSchema, graphql, is_type, GraphQLObjectType
|
||||
from graphql.type.directives import (GraphQLDirective, GraphQLIncludeDirective,
|
||||
GraphQLSkipDirective)
|
||||
from graphql.type.introspection import IntrospectionSchema
|
||||
|
@ -12,6 +12,17 @@ from .objecttype import ObjectType
|
|||
from .typemap import TypeMap, is_graphene_type
|
||||
|
||||
|
||||
def assert_valid_root_type(_type):
|
||||
if _type is None:
|
||||
return
|
||||
is_graphene_objecttype = inspect.isclass(
|
||||
_type) and issubclass(_type, ObjectType)
|
||||
is_graphql_objecttype = isinstance(_type, GraphQLObjectType)
|
||||
assert is_graphene_objecttype or is_graphql_objecttype, (
|
||||
"Type {} is not a valid ObjectType."
|
||||
).format(_type)
|
||||
|
||||
|
||||
class Schema(GraphQLSchema):
|
||||
'''
|
||||
Schema Definition
|
||||
|
@ -20,21 +31,23 @@ class Schema(GraphQLSchema):
|
|||
query and mutation (optional).
|
||||
'''
|
||||
|
||||
def __init__(self, query=None, mutation=None, subscription=None,
|
||||
directives=None, types=None, auto_camelcase=True):
|
||||
assert inspect.isclass(query) and issubclass(query, ObjectType), (
|
||||
'Schema query must be Object Type but got: {}.'
|
||||
).format(query)
|
||||
def __init__(self,
|
||||
query=None,
|
||||
mutation=None,
|
||||
subscription=None,
|
||||
directives=None,
|
||||
types=None,
|
||||
auto_camelcase=True):
|
||||
assert_valid_root_type(query)
|
||||
assert_valid_root_type(mutation)
|
||||
assert_valid_root_type(subscription)
|
||||
self._query = query
|
||||
self._mutation = mutation
|
||||
self._subscription = subscription
|
||||
self.types = types
|
||||
self.auto_camelcase = auto_camelcase
|
||||
if directives is None:
|
||||
directives = [
|
||||
GraphQLIncludeDirective,
|
||||
GraphQLSkipDirective
|
||||
]
|
||||
directives = [GraphQLIncludeDirective, GraphQLSkipDirective]
|
||||
|
||||
assert all(isinstance(d, GraphQLDirective) for d in directives), \
|
||||
'Schema directives must be List[GraphQLDirective] if provided but got: {}.'.format(
|
||||
|
@ -61,7 +74,8 @@ class Schema(GraphQLSchema):
|
|||
'''
|
||||
_type = super(Schema, self).get_type(type_name)
|
||||
if _type is None:
|
||||
raise AttributeError('Type "{}" not found in the Schema'.format(type_name))
|
||||
raise AttributeError(
|
||||
'Type "{}" not found in the Schema'.format(type_name))
|
||||
if isinstance(_type, GrapheneGraphQLType):
|
||||
return _type.graphene_type
|
||||
return _type
|
||||
|
@ -73,7 +87,8 @@ class Schema(GraphQLSchema):
|
|||
return _type
|
||||
if is_graphene_type(_type):
|
||||
graphql_type = self.get_type(_type._meta.name)
|
||||
assert graphql_type, "Type {} not found in this schema.".format(_type._meta.name)
|
||||
assert graphql_type, "Type {} not found in this schema.".format(
|
||||
_type._meta.name)
|
||||
assert graphql_type.graphene_type == _type
|
||||
return graphql_type
|
||||
raise Exception("{} is not a valid GraphQL type.".format(_type))
|
||||
|
@ -102,4 +117,8 @@ class Schema(GraphQLSchema):
|
|||
]
|
||||
if self.types:
|
||||
initial_types += self.types
|
||||
self._type_map = TypeMap(initial_types, auto_camelcase=self.auto_camelcase, schema=self)
|
||||
self._type_map = TypeMap(
|
||||
initial_types,
|
||||
auto_camelcase=self.auto_camelcase,
|
||||
schema=self
|
||||
)
|
||||
|
|
|
@ -2,6 +2,8 @@ import datetime
|
|||
|
||||
import pytz
|
||||
|
||||
from graphql import GraphQLError
|
||||
|
||||
from ..datetime import DateTime, Date, Time
|
||||
from ..objecttype import ObjectType
|
||||
from ..schema import Schema
|
||||
|
@ -34,7 +36,7 @@ def test_datetime_query():
|
|||
assert result.data == {'datetime': isoformat}
|
||||
|
||||
|
||||
def test_datetime_query():
|
||||
def test_date_query():
|
||||
now = datetime.datetime.now().replace(tzinfo=pytz.utc).date()
|
||||
isoformat = now.isoformat()
|
||||
|
||||
|
@ -53,6 +55,32 @@ def test_time_query():
|
|||
assert not result.errors
|
||||
assert result.data == {'time': isoformat}
|
||||
|
||||
def test_bad_datetime_query():
|
||||
not_a_date = "Some string that's not a date"
|
||||
|
||||
result = schema.execute('''{ datetime(in: "%s") }''' % not_a_date)
|
||||
|
||||
assert len(result.errors) == 1
|
||||
assert isinstance(result.errors[0], GraphQLError)
|
||||
assert result.data == None
|
||||
|
||||
def test_bad_date_query():
|
||||
not_a_date = "Some string that's not a date"
|
||||
|
||||
result = schema.execute('''{ date(in: "%s") }''' % not_a_date)
|
||||
|
||||
assert len(result.errors) == 1
|
||||
assert isinstance(result.errors[0], GraphQLError)
|
||||
assert result.data == None
|
||||
|
||||
def test_bad_time_query():
|
||||
not_a_date = "Some string that's not a date"
|
||||
|
||||
result = schema.execute('''{ time(at: "%s") }''' % not_a_date)
|
||||
|
||||
assert len(result.errors) == 1
|
||||
assert isinstance(result.errors[0], GraphQLError)
|
||||
assert result.data == None
|
||||
|
||||
def test_datetime_query_variable():
|
||||
now = datetime.datetime.now().replace(tzinfo=pytz.utc)
|
||||
|
|
|
@ -80,7 +80,8 @@ def test_enum_from_builtin_enum_accepts_lambda_description():
|
|||
return 'meh' if value == Episode.NEWHOPE else None
|
||||
|
||||
PyEpisode = PyEnum('PyEpisode', 'NEWHOPE,EMPIRE,JEDI')
|
||||
Episode = Enum.from_enum(PyEpisode, description=custom_description, deprecation_reason=custom_deprecation_reason)
|
||||
Episode = Enum.from_enum(PyEpisode, description=custom_description,
|
||||
deprecation_reason=custom_deprecation_reason)
|
||||
|
||||
class Query(ObjectType):
|
||||
foo = Episode()
|
||||
|
@ -214,3 +215,19 @@ def test_enum_to_enum_comparison_should_differ():
|
|||
assert RGB1.RED != RGB2.RED
|
||||
assert RGB1.GREEN != RGB2.GREEN
|
||||
assert RGB1.BLUE != RGB2.BLUE
|
||||
|
||||
|
||||
def test_enum_skip_meta_from_members():
|
||||
class RGB1(Enum):
|
||||
class Meta:
|
||||
name = 'RGB'
|
||||
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
BLUE = 3
|
||||
|
||||
assert dict(RGB1._meta.enum.__members__) == {
|
||||
'RED': RGB1.RED,
|
||||
'GREEN': RGB1.GREEN,
|
||||
'BLUE': RGB1.BLUE,
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ from ..inputfield import InputField
|
|||
from ..inputobjecttype import InputObjectType
|
||||
from ..objecttype import ObjectType
|
||||
from ..unmountedtype import UnmountedType
|
||||
from ..scalars import String, Boolean
|
||||
from ..schema import Schema
|
||||
|
||||
|
||||
class MyType(object):
|
||||
|
@ -51,7 +53,8 @@ def test_ordered_fields_in_inputobjecttype():
|
|||
field = MyScalar()
|
||||
asa = InputField(MyType)
|
||||
|
||||
assert list(MyInputObjectType._meta.fields.keys()) == ['b', 'a', 'field', 'asa']
|
||||
assert list(MyInputObjectType._meta.fields.keys()) == [
|
||||
'b', 'a', 'field', 'asa']
|
||||
|
||||
|
||||
def test_generate_inputobjecttype_unmountedtype():
|
||||
|
@ -86,7 +89,8 @@ def test_generate_inputobjecttype_inherit_abstracttype():
|
|||
field2 = MyScalar(MyType)
|
||||
|
||||
assert list(MyInputObjectType._meta.fields.keys()) == ['field1', 'field2']
|
||||
assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [InputField, InputField]
|
||||
assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [
|
||||
InputField, InputField]
|
||||
|
||||
|
||||
def test_generate_inputobjecttype_inherit_abstracttype_reversed():
|
||||
|
@ -97,4 +101,34 @@ def test_generate_inputobjecttype_inherit_abstracttype_reversed():
|
|||
field2 = MyScalar(MyType)
|
||||
|
||||
assert list(MyInputObjectType._meta.fields.keys()) == ['field1', 'field2']
|
||||
assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [InputField, InputField]
|
||||
assert [type(x) for x in MyInputObjectType._meta.fields.values()] == [
|
||||
InputField, InputField]
|
||||
|
||||
|
||||
def test_inputobjecttype_of_input():
|
||||
class Child(InputObjectType):
|
||||
first_name = String()
|
||||
last_name = String()
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
return "{} {}".format(self.first_name, self.last_name)
|
||||
|
||||
class Parent(InputObjectType):
|
||||
child = InputField(Child)
|
||||
|
||||
class Query(ObjectType):
|
||||
is_child = Boolean(parent=Parent())
|
||||
|
||||
def resolve_is_child(self, info, parent):
|
||||
return isinstance(parent.child, Child) and parent.child.full_name == "Peter Griffin"
|
||||
|
||||
schema = Schema(query=Query)
|
||||
result = schema.execute('''query basequery {
|
||||
isChild(parent: {child: {firstName: "Peter", lastName: "Griffin"}})
|
||||
}
|
||||
''')
|
||||
assert not result.errors
|
||||
assert result.data == {
|
||||
'isChild': True
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ from ..mutation import Mutation
|
|||
from ..objecttype import ObjectType
|
||||
from ..scalars import String
|
||||
from ..schema import Schema
|
||||
from ..structures import NonNull
|
||||
|
||||
|
||||
def test_generate_mutation_no_args():
|
||||
|
@ -133,3 +134,27 @@ def test_mutation_no_fields_output():
|
|||
'name': None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_mutation_allow_to_have_custom_args():
|
||||
class CreateUser(Mutation):
|
||||
|
||||
class Arguments:
|
||||
name = String()
|
||||
|
||||
name = String()
|
||||
|
||||
def mutate(self, info, name):
|
||||
return CreateUser(name=name)
|
||||
|
||||
class MyMutation(ObjectType):
|
||||
create_user = CreateUser.Field(
|
||||
description='Create a user',
|
||||
deprecation_reason='Is deprecated',
|
||||
required=True
|
||||
)
|
||||
|
||||
field = MyMutation._meta.fields['create_user']
|
||||
assert field.description == 'Create a user'
|
||||
assert field.deprecation_reason == 'Is deprecated'
|
||||
assert field.type == NonNull(CreateUser)
|
||||
|
|
|
@ -44,6 +44,8 @@ def test_generate_objecttype():
|
|||
assert MyObjectType._meta.description == "Documentation"
|
||||
assert MyObjectType._meta.interfaces == tuple()
|
||||
assert MyObjectType._meta.fields == {}
|
||||
assert repr(
|
||||
MyObjectType) == "<MyObjectType meta=<ObjectTypeOptions name='MyObjectType'>>"
|
||||
|
||||
|
||||
def test_generate_objecttype_with_meta():
|
||||
|
@ -66,7 +68,6 @@ def test_generate_lazy_objecttype():
|
|||
class InnerObjectType(ObjectType):
|
||||
field = Field(MyType)
|
||||
|
||||
|
||||
assert MyObjectType._meta.name == "MyObjectType"
|
||||
example_field = MyObjectType._meta.fields['example']
|
||||
assert isinstance(example_field.type, NonNull)
|
||||
|
@ -115,7 +116,8 @@ def test_generate_objecttype_inherit_abstracttype():
|
|||
assert MyObjectType._meta.interfaces == ()
|
||||
assert MyObjectType._meta.name == "MyObjectType"
|
||||
assert list(MyObjectType._meta.fields.keys()) == ['field1', 'field2']
|
||||
assert list(map(type, MyObjectType._meta.fields.values())) == [Field, Field]
|
||||
assert list(map(type, MyObjectType._meta.fields.values())) == [
|
||||
Field, Field]
|
||||
|
||||
|
||||
def test_generate_objecttype_inherit_abstracttype_reversed():
|
||||
|
@ -129,7 +131,8 @@ def test_generate_objecttype_inherit_abstracttype_reversed():
|
|||
assert MyObjectType._meta.interfaces == ()
|
||||
assert MyObjectType._meta.name == "MyObjectType"
|
||||
assert list(MyObjectType._meta.fields.keys()) == ['field1', 'field2']
|
||||
assert list(map(type, MyObjectType._meta.fields.values())) == [Field, Field]
|
||||
assert list(map(type, MyObjectType._meta.fields.values())) == [
|
||||
Field, Field]
|
||||
|
||||
|
||||
def test_generate_objecttype_unmountedtype():
|
||||
|
@ -145,7 +148,8 @@ def test_parent_container_get_fields():
|
|||
|
||||
|
||||
def test_parent_container_interface_get_fields():
|
||||
assert list(ContainerWithInterface._meta.fields.keys()) == ['ifield', 'field1', 'field2']
|
||||
assert list(ContainerWithInterface._meta.fields.keys()) == [
|
||||
'ifield', 'field1', 'field2']
|
||||
|
||||
|
||||
def test_objecttype_as_container_only_args():
|
||||
|
@ -182,7 +186,8 @@ def test_objecttype_as_container_invalid_kwargs():
|
|||
with pytest.raises(TypeError) as excinfo:
|
||||
Container(unexisting_field="3")
|
||||
|
||||
assert "'unexisting_field' is an invalid keyword argument for Container" == str(excinfo.value)
|
||||
assert "'unexisting_field' is an invalid keyword argument for Container" == str(
|
||||
excinfo.value)
|
||||
|
||||
|
||||
def test_objecttype_container_benchmark(benchmark):
|
||||
|
@ -238,7 +243,6 @@ def test_objecttype_no_fields_output():
|
|||
def resolve_user(self, info):
|
||||
return User()
|
||||
|
||||
|
||||
schema = Schema(query=Query)
|
||||
result = schema.execute(''' query basequery {
|
||||
user {
|
||||
|
@ -252,3 +256,12 @@ def test_objecttype_no_fields_output():
|
|||
'name': None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_abstract_objecttype_can_str():
|
||||
class MyObjectType(ObjectType):
|
||||
class Meta:
|
||||
abstract = True
|
||||
field = MyScalar()
|
||||
|
||||
assert str(MyObjectType) == "MyObjectType"
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import pytest
|
||||
|
||||
from graphql.type import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue,
|
||||
GraphQLField, GraphQLInputObjectField,
|
||||
GraphQLInputObjectType, GraphQLInterfaceType,
|
||||
GraphQLObjectType, GraphQLString)
|
||||
|
||||
from ..structures import List, NonNull
|
||||
from ..dynamic import Dynamic
|
||||
from ..enum import Enum
|
||||
from ..field import Field
|
||||
|
@ -11,8 +13,8 @@ from ..inputfield import InputField
|
|||
from ..inputobjecttype import InputObjectType
|
||||
from ..interface import Interface
|
||||
from ..objecttype import ObjectType
|
||||
from ..scalars import String
|
||||
from ..typemap import TypeMap
|
||||
from ..scalars import String, Int
|
||||
from ..typemap import TypeMap, resolve_type
|
||||
|
||||
|
||||
def test_enum():
|
||||
|
@ -38,7 +40,8 @@ def test_enum():
|
|||
assert graphql_enum.description == 'Description'
|
||||
values = graphql_enum.values
|
||||
assert values == [
|
||||
GraphQLEnumValue(name='foo', value=1, description='Description foo=1', deprecation_reason='Is deprecated'),
|
||||
GraphQLEnumValue(name='foo', value=1, description='Description foo=1',
|
||||
deprecation_reason='Is deprecated'),
|
||||
GraphQLEnumValue(name='bar', value=2, description='Description bar=2'),
|
||||
]
|
||||
|
||||
|
@ -46,7 +49,8 @@ def test_enum():
|
|||
def test_objecttype():
|
||||
class MyObjectType(ObjectType):
|
||||
'''Description'''
|
||||
foo = String(bar=String(description='Argument description', default_value='x'), description='Field description')
|
||||
foo = String(bar=String(description='Argument description',
|
||||
default_value='x'), description='Field description')
|
||||
bar = String(name='gizmo')
|
||||
|
||||
def resolve_foo(self, bar):
|
||||
|
@ -91,8 +95,10 @@ def test_dynamic_objecttype():
|
|||
def test_interface():
|
||||
class MyInterface(Interface):
|
||||
'''Description'''
|
||||
foo = String(bar=String(description='Argument description', default_value='x'), description='Field description')
|
||||
bar = String(name='gizmo', first_arg=String(), other_arg=String(name='oth_arg'))
|
||||
foo = String(bar=String(description='Argument description',
|
||||
default_value='x'), description='Field description')
|
||||
bar = String(name='gizmo', first_arg=String(),
|
||||
other_arg=String(name='oth_arg'))
|
||||
own = Field(lambda: MyInterface)
|
||||
|
||||
def resolve_foo(self, args, info):
|
||||
|
@ -119,10 +125,18 @@ def test_interface():
|
|||
|
||||
|
||||
def test_inputobject():
|
||||
class OtherObjectType(InputObjectType):
|
||||
thingy = NonNull(Int)
|
||||
|
||||
class MyInnerObjectType(InputObjectType):
|
||||
some_field = String()
|
||||
some_other_field = List(OtherObjectType)
|
||||
|
||||
class MyInputObjectType(InputObjectType):
|
||||
'''Description'''
|
||||
foo_bar = String(description='Field description')
|
||||
bar = String(name='gizmo')
|
||||
baz = NonNull(MyInnerObjectType)
|
||||
own = InputField(lambda: MyInputObjectType)
|
||||
|
||||
def resolve_foo_bar(self, args, info):
|
||||
|
@ -135,15 +149,28 @@ def test_inputobject():
|
|||
assert graphql_type.name == 'MyInputObjectType'
|
||||
assert graphql_type.description == 'Description'
|
||||
|
||||
# Container
|
||||
container = graphql_type.create_container({'bar': 'oh!'})
|
||||
other_graphql_type = typemap['OtherObjectType']
|
||||
inner_graphql_type = typemap['MyInnerObjectType']
|
||||
container = graphql_type.create_container({
|
||||
'bar': 'oh!',
|
||||
'baz': inner_graphql_type.create_container({
|
||||
'some_other_field': [
|
||||
other_graphql_type.create_container({'thingy': 1}),
|
||||
other_graphql_type.create_container({'thingy': 2})
|
||||
]
|
||||
})
|
||||
})
|
||||
assert isinstance(container, MyInputObjectType)
|
||||
assert 'bar' in container
|
||||
assert container.bar == 'oh!'
|
||||
assert 'foo_bar' not in container
|
||||
assert container.foo_bar is None
|
||||
assert container.baz.some_field is None
|
||||
assert container.baz.some_other_field[0].thingy == 1
|
||||
assert container.baz.some_other_field[1].thingy == 2
|
||||
|
||||
fields = graphql_type.fields
|
||||
assert list(fields.keys()) == ['fooBar', 'gizmo', 'own']
|
||||
assert list(fields.keys()) == ['fooBar', 'gizmo', 'baz', 'own']
|
||||
own_field = fields['own']
|
||||
assert own_field.type == graphql_type
|
||||
foo_field = fields['fooBar']
|
||||
|
@ -206,3 +233,22 @@ def test_objecttype_with_possible_types():
|
|||
assert graphql_type.is_type_of
|
||||
assert graphql_type.is_type_of({}, None) is True
|
||||
assert graphql_type.is_type_of(MyObjectType(), None) is False
|
||||
|
||||
|
||||
def test_resolve_type_with_missing_type():
|
||||
class MyObjectType(ObjectType):
|
||||
foo_bar = String()
|
||||
|
||||
class MyOtherObjectType(ObjectType):
|
||||
fizz_buzz = String()
|
||||
|
||||
def resolve_type_func(root, info):
|
||||
return MyOtherObjectType
|
||||
|
||||
typemap = TypeMap([MyObjectType])
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
resolve_type(
|
||||
resolve_type_func, typemap, 'MyOtherObjectType', {}, {}
|
||||
)
|
||||
|
||||
assert 'MyOtherObjectTyp' in str(excinfo.value)
|
||||
|
|
|
@ -46,7 +46,10 @@ def resolve_type(resolve_type_func, map, type_name, root, info):
|
|||
|
||||
if inspect.isclass(_type) and issubclass(_type, ObjectType):
|
||||
graphql_type = map.get(_type._meta.name)
|
||||
assert graphql_type and graphql_type.graphene_type == _type, (
|
||||
assert graphql_type, "Can't find type {} in schema".format(
|
||||
_type._meta.name
|
||||
)
|
||||
assert graphql_type.graphene_type == _type, (
|
||||
'The type {} does not match with the associated graphene type {}.'
|
||||
).format(_type, graphql_type.graphene_type)
|
||||
return graphql_type
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import re
|
||||
|
||||
|
||||
# From this response in Stackoverflow
|
||||
# Adapted from this response in Stackoverflow
|
||||
# http://stackoverflow.com/a/19053800/1072990
|
||||
def to_camel_case(snake_str):
|
||||
components = snake_str.split('_')
|
||||
# We capitalize the first letter of each component except the first one
|
||||
# with the 'title' method and join them together.
|
||||
return components[0] + "".join(x.title() if x else '_' for x in components[1:])
|
||||
# with the 'capitalize' method and join them together.
|
||||
return components[0] + ''.join(x.capitalize() if x else '_' for x in components[1:])
|
||||
|
||||
|
||||
# From this response in Stackoverflow
|
||||
|
|
|
@ -6,9 +6,15 @@ from .props import props
|
|||
|
||||
|
||||
class SubclassWithMeta_Meta(InitSubclassMeta):
|
||||
_meta = None
|
||||
|
||||
def __str__(cls):
|
||||
if cls._meta:
|
||||
return cls._meta.name
|
||||
return cls.__name__
|
||||
|
||||
def __repr__(cls):
|
||||
return cls._meta.name
|
||||
return "<{} meta={}>".format(cls.__name__, repr(cls._meta))
|
||||
|
||||
|
||||
class SubclassWithMeta(six.with_metaclass(SubclassWithMeta_Meta)):
|
||||
|
@ -24,7 +30,8 @@ class SubclassWithMeta(six.with_metaclass(SubclassWithMeta_Meta)):
|
|||
elif isclass(_Meta):
|
||||
_meta_props = props(_Meta)
|
||||
else:
|
||||
raise Exception("Meta have to be either a class or a dict. Received {}".format(_Meta))
|
||||
raise Exception(
|
||||
"Meta have to be either a class or a dict. Received {}".format(_Meta))
|
||||
delattr(cls, "Meta")
|
||||
options = dict(meta_options, **_meta_props)
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ def test_camel_case():
|
|||
assert to_camel_case('snakes_on_a_plane') == 'snakesOnAPlane'
|
||||
assert to_camel_case('snakes_on_a__plane') == 'snakesOnA_Plane'
|
||||
assert to_camel_case('i_phone_hysteria') == 'iPhoneHysteria'
|
||||
assert to_camel_case('field_i18n') == 'fieldI18n'
|
||||
|
||||
|
||||
def test_to_const():
|
||||
|
|
2
setup.py
2
setup.py
|
@ -83,7 +83,7 @@ setup(
|
|||
|
||||
keywords='api graphql protocol rest relay graphene',
|
||||
|
||||
packages=find_packages(exclude=['tests', 'tests.*']),
|
||||
packages=find_packages(exclude=['tests', 'tests.*', 'examples']),
|
||||
|
||||
install_requires=[
|
||||
'six>=1.10.0,<2',
|
||||
|
|
Loading…
Reference in New Issue
Block a user