Merge pull request #1 from graphql-python/master

Sync with original repo.
This commit is contained in:
abawchen 2018-03-20 07:30:53 +08:00 committed by GitHub
commit dbc2ee4da9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 124 additions and 30 deletions

1
.gitignore vendored
View File

@ -42,6 +42,7 @@ htmlcov/
.coverage
.coverage.*
.cache
.pytest_cache
nosetests.xml
coverage.xml
*,cover

View File

@ -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

View File

@ -55,12 +55,12 @@ Example of a custom node:
return '{}:{}'.format(type, id)
@staticmethod
def get_node_from_global_id(info, global_id, only_node=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)

View File

@ -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

View File

@ -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!]
}

View File

@ -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)

View File

@ -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
@ -155,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': []}}

View File

@ -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/v2.0.0/UPGRADE-v2.0.md#deprecations"
)
super(AbstractType, cls).__init_subclass__(*args, **kwargs)

View File

@ -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):

View File

@ -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:
@ -75,7 +76,12 @@ class Mutation(ObjectType):
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):
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,
)

View File

@ -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
)

View File

@ -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,
}

View File

@ -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',

View File

@ -1,5 +1,5 @@
[tox]
envlist = flake8,py27,py33,py34,py35,pypy
envlist = flake8,py27,py33,py34,py35,py36,pypy
skipsdist = true
[testenv]