Skip to content

Commit 129999d

Browse files
committed
Improved types as containers
1 parent 2958cc1 commit 129999d

File tree

15 files changed

+255
-345
lines changed

15 files changed

+255
-345
lines changed

README.md

Lines changed: 18 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# ![Graphene Logo](http://graphene-python.org/favicon.png) [Graphene](http://graphene-python.org) [![Build Status](https://travis-ci.org/graphql-python/graphene.svg?branch=master)](https://travis-ci.org/graphql-python/graphene) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphene/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphene?branch=master)
1+
# ![Graphene Logo](http://graphene-python.org/favicon.png) [Graphene](http://graphene-python.org) [![Build Status](https://travis-ci.org/graphql-python/graphene.svg?branch=master)](https://travis-ci.org/graphql-python/graphene) [![PyPI version](https://badge.fury.io/py/graphene.svg)](https://badge.fury.io/py/graphene) [![Coverage Status](https://coveralls.io/repos/graphql-python/graphene/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-python/graphene?branch=master)
22

33

44
Graphene is a Python library for building GraphQL schemas/types fast and easily.
55
* **Easy to use:** It maps the models/fields to internal GraphQL objects without effort.
66
* **Relay:** Graphene has builtin support for Relay
7-
* **Django:** Automatic [Django models](#djangorelay-schema) conversion. *See an [example Django](http://github.com/graphql-python/swapi-graphene) implementation*
7+
* **Django:** Automatic *Django model* mapping to Graphene Types. *See an [example Django](http://github.com/graphql-python/swapi-graphene) implementation*
88

99

1010
## Installation
@@ -16,26 +16,21 @@ pip install graphene
1616
```
1717

1818

19-
## Usage
19+
## Examples
2020

21-
Example code of a GraphQL schema using Graphene:
22-
23-
### Schema definition
21+
Here is one example for get you started:
2422

2523
```python
26-
class Character(graphene.Interface):
27-
id = graphene.IDField()
28-
name = graphene.StringField()
29-
friends = graphene.ListField('self')
24+
class Query(graphene.ObjectType):
25+
hello = graphene.StringField(description='A typical hello world')
26+
ping = graphene.StringField(description='Ping someone',
27+
to=graphene.Argument(graphene.String))
3028

31-
def resolve_friends(self, args, *_):
32-
return [Human(f) for f in self.instance.friends]
29+
def resolve_hello(self, args, info):
30+
return 'World'
3331

34-
class Human(Character):
35-
homePlanet = graphene.StringField()
36-
37-
class Query(graphene.ObjectType):
38-
human = graphene.Field(Human)
32+
def resolve_ping(self, args, info):
33+
return 'Pinging {}'.format(args.get('to'))
3934

4035
schema = graphene.Schema(query=Query)
4136
```
@@ -44,48 +39,19 @@ Then Querying `graphene.Schema` is as simple as:
4439

4540
```python
4641
query = '''
47-
query HeroNameQuery {
48-
hero {
49-
name
50-
}
42+
query SayHello {
43+
hello
44+
ping(to:'peter')
5145
}
5246
'''
5347
result = schema.execute(query)
5448
```
5549

56-
### Relay Schema
57-
58-
Graphene also supports Relay, check the [Starwars Relay example](tests/starwars_relay)!
59-
60-
```python
61-
class Ship(relay.Node):
62-
name = graphene.StringField()
63-
64-
@classmethod
65-
def get_node(cls, id):
66-
return Ship(your_ship_instance)
67-
68-
69-
class Query(graphene.ObjectType):
70-
ships = relay.ConnectionField(Ship)
71-
node = relay.NodeField()
72-
73-
```
74-
75-
### Django+Relay Schema
50+
If you want to learn even more, you can also check the following examples:
7651

77-
If you want to use graphene with your Django Models check the [Starwars Django example](tests/starwars_django)!
52+
* Relay Schema: [Starwars Relay example](tests/starwars_relay)
53+
* Django: [Starwars Django example](tests/starwars_django)
7854

79-
```python
80-
class Ship(DjangoNode):
81-
class Meta:
82-
model = YourDjangoModelHere
83-
# only_fields = ('id', 'name') # Only map this fields from the model
84-
# exclude_fields ('field_to_exclude', ) # Exclude mapping this fields from the model
85-
86-
class Query(graphene.ObjectType):
87-
node = relay.NodeField()
88-
```
8955

9056
## Contributing
9157

graphene/contrib/django/types.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,20 @@ def add_extra_fields(cls):
3737
cls.add_to_class(field.name, converted_field)
3838

3939

40-
class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, BaseObjectType)):
40+
class InstanceObjectType(BaseObjectType):
41+
def __init__(self, instance=None):
42+
self.instance = instance
43+
super(InstanceObjectType, self).__init__()
44+
45+
def __getattr__(self, attr):
46+
return getattr(self.instance, attr)
47+
48+
49+
class DjangoObjectType(six.with_metaclass(DjangoObjectTypeMeta, InstanceObjectType)):
4150
pass
4251

4352

44-
class DjangoInterface(six.with_metaclass(DjangoObjectTypeMeta, BaseObjectType)):
53+
class DjangoInterface(six.with_metaclass(DjangoObjectTypeMeta, InstanceObjectType)):
4554
pass
4655

4756

graphene/core/fields.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class Field(object):
2828
creation_counter = 0
2929
required = False
3030

31-
def __init__(self, field_type, name=None, resolve=None, required=False, args=None, description='', **extra_args):
31+
def __init__(self, field_type, name=None, resolve=None, required=False, args=None, description='', default=None, **extra_args):
3232
self.field_type = field_type
3333
self.resolve_fn = resolve
3434
self.required = self.required or required
@@ -38,9 +38,13 @@ def __init__(self, field_type, name=None, resolve=None, required=False, args=Non
3838
self.name = name
3939
self.description = description or self.__doc__
4040
self.object_type = None
41+
self.default = default
4142
self.creation_counter = Field.creation_counter
4243
Field.creation_counter += 1
4344

45+
def get_default(self):
46+
return self.default
47+
4448
def contribute_to_class(self, cls, name, add=True):
4549
if not self.name:
4650
self.name = to_camel_case(name)
@@ -57,7 +61,7 @@ def resolve(self, instance, args, info):
5761
if resolve_fn:
5862
return resolve_fn(instance, args, info)
5963
else:
60-
return getattr(instance, self.field_name, None)
64+
return getattr(instance, self.field_name, self.get_default())
6165

6266
def get_resolve_fn(self, schema):
6367
object_type = self.get_object_type(schema)

graphene/core/types.py

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -129,29 +129,48 @@ def add_to_class(cls, name, value):
129129

130130
class BaseObjectType(object):
131131

132-
def __new__(cls, instance=None, **kwargs):
132+
def __new__(cls, *args, **kwargs):
133133
if cls._meta.is_interface:
134134
raise Exception("An interface cannot be initialized")
135-
if instance is None:
136-
if not kwargs:
137-
return None
138-
elif type(instance) is cls:
139-
return instance
140-
135+
if not args and not kwargs:
136+
return None
141137
return super(BaseObjectType, cls).__new__(cls)
142138

143-
def __init__(self, instance=None, **kwargs):
144-
signals.pre_init.send(self.__class__, instance=instance)
145-
assert instance or kwargs
146-
if not instance:
147-
init_kwargs = dict({k: None for k in self._meta.fields_map.keys()}, **kwargs)
148-
instance = self._meta.object(**init_kwargs)
149-
self.instance = instance
150-
signals.post_init.send(self.__class__, instance=self)
139+
def __init__(self, *args, **kwargs):
140+
signals.pre_init.send(self.__class__, args=args, kwargs=kwargs)
141+
args_len = len(args)
142+
fields = self._meta.fields
143+
if args_len > len(fields):
144+
# Daft, but matches old exception sans the err msg.
145+
raise IndexError("Number of args exceeds number of fields")
146+
fields_iter = iter(fields)
147+
148+
if not kwargs:
149+
for val, field in zip(args, fields_iter):
150+
setattr(self, field.field_name, val)
151+
else:
152+
for val, field in zip(args, fields_iter):
153+
setattr(self, field.field_name, val)
154+
kwargs.pop(field.field_name, None)
155+
156+
for field in fields_iter:
157+
try:
158+
val = kwargs.pop(field.field_name)
159+
setattr(self, field.field_name, val)
160+
except KeyError:
161+
pass
162+
163+
if kwargs:
164+
for prop in list(kwargs):
165+
try:
166+
if isinstance(getattr(self.__class__, prop), property):
167+
setattr(self, prop, kwargs.pop(prop))
168+
except AttributeError:
169+
pass
170+
if kwargs:
171+
raise TypeError("'%s' is an invalid keyword argument for this function" % list(kwargs)[0])
151172

152-
def __getattr__(self, name):
153-
if self.instance:
154-
return getattr(self.instance, name)
173+
signals.post_init.send(self.__class__, instance=self)
155174

156175
@classmethod
157176
def fields_as_arguments(cls, schema):

tests/relay/test_relay.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,3 @@ def test_node_should_have_same_connection_always():
3232

3333
def test_node_should_have_id_field():
3434
assert 'id' in OtherNode._meta.fields_map
35-
36-
37-
# def test_field_no_contributed_raises_error():
38-
# with raises(Exception) as excinfo:
39-
# class Ship(graphene.ObjectType):
40-
# name = graphene.StringField()
41-
# class Meta:
42-
# schema = schema
43-
44-
# class Faction(relay.Node):
45-
# name = graphene.StringField()
46-
# ships = relay.ConnectionField(Ship)
47-
# @classmethod
48-
# def get_node(cls):
49-
# pass
50-
# class Meta:
51-
# schema = schema
52-
# assert 'same type_name' in str(excinfo.value)

tests/relay/test_relayfields.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from pytest import raises
21
from graphql.core.type import (
32
GraphQLNonNull,
43
GraphQLID
@@ -10,11 +9,6 @@
109
schema = graphene.Schema()
1110

1211

13-
class MyType(object):
14-
name = 'my'
15-
arg = None
16-
17-
1812
class MyConnection(relay.Connection):
1913
my_custom_field = graphene.StringField(resolve=lambda instance, *_: 'Custom')
2014

@@ -24,18 +18,17 @@ class MyNode(relay.Node):
2418

2519
@classmethod
2620
def get_node(cls, id):
27-
return MyNode(MyType())
21+
return MyNode(name='mo')
2822

2923

3024
class Query(graphene.ObjectType):
3125
my_node = relay.NodeField(MyNode)
3226
all_my_nodes = relay.ConnectionField(MyNode, connection_type=MyConnection, customArg=graphene.Argument(graphene.String))
3327

3428
def resolve_all_my_nodes(self, args, info):
35-
t = MyType()
3629
custom_arg = args.get('customArg')
3730
assert custom_arg == "1"
38-
return [MyNode(t)]
31+
return [MyNode(name='my')]
3932

4033
schema.query = Query
4134

@@ -61,7 +54,7 @@ def test_nodefield_query():
6154
'''
6255
expected = {
6356
'myNode': {
64-
'name': 'my'
57+
'name': 'mo'
6558
},
6659
'allMyNodes': {
6760
'edges': [{

0 commit comments

Comments
 (0)