From 9a84d595a1e5d151ee0e41400d2cce63fb08f974 Mon Sep 17 00:00:00 2001
From: Syrus Akbary <me@syrusakbary.com>
Date: Fri, 25 Sep 2015 16:35:17 -0700
Subject: [PATCH 1/8] First relay version

---
 .travis.yml                    |  2 +-
 graphene/__init__.py           |  4 +++
 graphene/core/fields.py        | 16 +++------
 graphene/core/options.py       |  6 ++--
 graphene/core/types.py         | 25 ++++++++++----
 graphene/core/utils.py         | 30 +++++++++++++++++
 graphene/relay/__init__.py     |  2 ++
 graphene/signals.py            |  5 +++
 setup.py                       |  1 +
 tests/starwars/schema.py       |  2 +-
 tests/starwars_relay/schema.py | 61 ++++++++++++++++++++++++++++++++++
 tox.ini                        |  2 ++
 12 files changed, 134 insertions(+), 22 deletions(-)
 create mode 100644 graphene/core/utils.py
 create mode 100644 graphene/signals.py
 create mode 100644 tests/starwars_relay/schema.py

diff --git a/.travis.yml b/.travis.yml
index d254bd80..0435df42 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,7 @@ sudo: false
 python:
 - 2.7
 install:
-- pip install pytest pytest-cov coveralls flake8 six
+- pip install pytest pytest-cov coveralls flake8 six blinker
 - pip install git+https://github.com/dittos/graphqllib.git # Last version of graphqllib
 - pip install graphql-relay
 - python setup.py develop
diff --git a/graphene/__init__.py b/graphene/__init__.py
index 42442952..6024313c 100644
--- a/graphene/__init__.py
+++ b/graphene/__init__.py
@@ -26,3 +26,7 @@ from graphene.core.types import (
 from graphene.decorators import (
     resolve_only_args
 )
+
+from graphene.relay import (
+    Relay
+)
diff --git a/graphene/core/fields.py b/graphene/core/fields.py
index 876416b1..d37fed1d 100644
--- a/graphene/core/fields.py
+++ b/graphene/core/fields.py
@@ -1,4 +1,3 @@
-import inspect
 from graphql.core.type import (
     GraphQLField,
     GraphQLList,
@@ -9,8 +8,8 @@ from graphql.core.type import (
     GraphQLID,
     GraphQLArgument,
 )
-from graphene.core.types import ObjectType, Interface
 from graphene.utils import cached_property
+from graphene.core.utils import get_object_type
 
 class Field(object):
     def __init__(self, field_type, resolve=None, null=True, args=None, description='', **extra_args):
@@ -45,16 +44,11 @@ class Field(object):
 
     @cached_property
     def type(self):
-        field_type = self.field_type
-        _is_class = inspect.isclass(field_type)
-        if _is_class and issubclass(field_type, ObjectType):
-            field_type = field_type._meta.type
-        elif isinstance(field_type, Field):
-            field_type = field_type.type
-        elif field_type == 'self':
-            field_type = self.object_type._meta.type
+        if isinstance(self.field_type, Field):
+            field_type = self.field_type.type
+        else:
+            field_type = get_object_type(self.field_type, self.object_type)
         field_type = self.type_wrapper(field_type)
-
         return field_type
 
     def type_wrapper(self, field_type):
diff --git a/graphene/core/options.py b/graphene/core/options.py
index f55cd494..5a016dff 100644
--- a/graphene/core/options.py
+++ b/graphene/core/options.py
@@ -1,6 +1,8 @@
 from graphene.utils import cached_property
 
-DEFAULT_NAMES = ('description', 'name', 'interface', 'type_name', 'interfaces',  'proxy')
+DEFAULT_NAMES = ('description', 'name', 'interface',
+                 'type_name', 'interfaces',  'proxy')
+
 
 class Options(object):
     def __init__(self, meta=None):
@@ -62,7 +64,7 @@ class Options(object):
 
     @cached_property
     def fields_map(self):
-        return {f.field_name:f for f in self.fields}
+        return {f.field_name: f for f in self.fields}
 
     @cached_property
     def type(self):
diff --git a/graphene/core/types.py b/graphene/core/types.py
index 250559e5..a5645b16 100644
--- a/graphene/core/types.py
+++ b/graphene/core/types.py
@@ -8,8 +8,10 @@ from graphql.core.type import (
 )
 from graphql.core import graphql
 
+from graphene import signals
 from graphene.core.options import Options
 
+
 class ObjectTypeMeta(type):
     def __new__(cls, name, bases, attrs):
         super_new = super(ObjectTypeMeta, cls).__new__
@@ -20,7 +22,10 @@ class ObjectTypeMeta(type):
 
         module = attrs.pop('__module__')
         doc = attrs.pop('__doc__', None)
-        new_class = super_new(cls, name, bases, {'__module__': module, '__doc__': doc})
+        new_class = super_new(cls, name, bases, {
+            '__module__': module,
+            '__doc__': doc
+        })
         attr_meta = attrs.pop('Meta', None)
         if not attr_meta:
             meta = getattr(new_class, 'Meta', None)
@@ -51,7 +56,7 @@ class ObjectTypeMeta(type):
             # moment).
             for field in parent_fields:
                 if field.field_name in field_names:
-                    raise FieldError(
+                    raise Exception(
                         'Local field %r in class %r clashes '
                         'with field of similar name from '
                         'base class %r' % (field.field_name, name, base.__name__)
@@ -61,8 +66,12 @@ class ObjectTypeMeta(type):
                 new_class._meta.interfaces.append(base)
             # new_class._meta.parents.extend(base._meta.parents)
 
+        new_class._prepare()
         return new_class
 
+    def _prepare(cls):
+        signals.class_prepared.send(cls)
+
     def add_to_class(cls, name, value):
         # We should call the contribute_to_class method only if it's bound
         if not inspect.isclass(value) and hasattr(value, 'contribute_to_class'):
@@ -73,15 +82,17 @@ class ObjectTypeMeta(type):
 
 class ObjectType(six.with_metaclass(ObjectTypeMeta)):
     def __init__(self, instance=None):
+        signals.pre_init.send(self.__class__, instance=instance)
         self.instance = instance
+        signals.post_init.send(self.__class__, instance=self)
 
     def get_field(self, field):
         return getattr(self.instance, field, None)
 
     def resolve(self, field_name, args, info):
         if field_name not in self._meta.fields_map.keys():
-            raise Exception('Field %s not found in model'%field_name)
-        custom_resolve_fn = 'resolve_%s'%field_name
+            raise Exception('Field %s not found in model' % field_name)
+        custom_resolve_fn = 'resolve_%s' % field_name
         if hasattr(self, custom_resolve_fn):
             resolve_fn = getattr(self, custom_resolve_fn)
             return resolve_fn(args, info)
@@ -104,13 +115,13 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta)):
                 cls._meta.type_name,
                 description=cls._meta.description,
                 resolve_type=cls.resolve_type,
-                fields=lambda: {name:field.field for name, field in fields.items()}
+                fields=lambda: {name: field.field for name, field in fields.items()}
             )
         return GraphQLObjectType(
             cls._meta.type_name,
             description=cls._meta.description,
             interfaces=[i._meta.type for i in cls._meta.interfaces],
-            fields=lambda: {name:field.field for name, field in fields.items()}
+            fields=lambda: {name: field.field for name, field in fields.items()}
         )
 
 
@@ -125,7 +136,7 @@ class Schema(object):
         self.query = query
         self.query_type = query._meta.type
         self._schema = GraphQLSchema(query=self.query_type, mutation=mutation)
-    
+
     def execute(self, request='', root=None, vars=None, operation_name=None):
         return graphql(
             self._schema,
diff --git a/graphene/core/utils.py b/graphene/core/utils.py
new file mode 100644
index 00000000..2441e644
--- /dev/null
+++ b/graphene/core/utils.py
@@ -0,0 +1,30 @@
+import inspect
+
+from graphene.core.types import ObjectType
+from graphene import signals
+
+registered_object_types = []
+
+
+def get_object_type(field_type, object_type=None):
+    _is_class = inspect.isclass(field_type)
+    if _is_class and issubclass(field_type, ObjectType):
+        field_type = field_type._meta.type
+    elif isinstance(field_type, basestring):
+        if field_type == 'self':
+            field_type = object_type._meta.type
+        else:
+            object_type = get_registered_object_type(field_type)
+            field_type = object_type._meta.type
+    return field_type
+
+
+def get_registered_object_type(name):
+    for object_type in registered_object_types:
+        if object_type._meta.type_name == name:
+            return object_type
+    return None
+
+@signals.class_prepared.connect
+def object_type_created(sender):
+    registered_object_types.append(sender)
diff --git a/graphene/relay/__init__.py b/graphene/relay/__init__.py
index e69de29b..80feb122 100644
--- a/graphene/relay/__init__.py
+++ b/graphene/relay/__init__.py
@@ -0,0 +1,2 @@
+class Relay(object):
+	pass
diff --git a/graphene/signals.py b/graphene/signals.py
new file mode 100644
index 00000000..954d02a1
--- /dev/null
+++ b/graphene/signals.py
@@ -0,0 +1,5 @@
+from blinker import Signal
+
+class_prepared = Signal()
+pre_init = Signal()
+post_init = Signal()
diff --git a/setup.py b/setup.py
index b5222dc0..705d1ac3 100644
--- a/setup.py
+++ b/setup.py
@@ -48,6 +48,7 @@ setup(
 
     install_requires=[
         'six',
+        'blinker',
         'graphqllib',
         'graphql-relay'
     ],
diff --git a/tests/starwars/schema.py b/tests/starwars/schema.py
index 1a7e570d..018a1fc8 100644
--- a/tests/starwars/schema.py
+++ b/tests/starwars/schema.py
@@ -18,7 +18,7 @@ def wrap_character(character):
 class Character(graphene.Interface):
     id = graphene.IDField()
     name = graphene.StringField()
-    friends = graphene.ListField('self')
+    friends = graphene.ListField('Character')
     appearsIn = graphene.ListField(Episode)
 
     def resolve_friends(self, args, *_):
diff --git a/tests/starwars_relay/schema.py b/tests/starwars_relay/schema.py
new file mode 100644
index 00000000..1a7e570d
--- /dev/null
+++ b/tests/starwars_relay/schema.py
@@ -0,0 +1,61 @@
+import graphene
+from graphene import resolve_only_args
+
+from .data import getHero, getHuman, getCharacter, getDroid, Human as _Human, Droid as _Droid
+
+Episode = graphene.Enum('Episode', dict(
+    NEWHOPE = 4,
+    EMPIRE = 5,
+    JEDI = 6
+))
+
+def wrap_character(character):
+    if isinstance(character, _Human): 
+        return Human(character)
+    elif isinstance(character, _Droid):
+        return Droid(character)
+
+class Character(graphene.Interface):
+    id = graphene.IDField()
+    name = graphene.StringField()
+    friends = graphene.ListField('self')
+    appearsIn = graphene.ListField(Episode)
+
+    def resolve_friends(self, args, *_):
+        return [wrap_character(getCharacter(f)) for f in self.instance.friends]
+
+class Human(Character):
+    homePlanet = graphene.StringField()
+
+
+class Droid(Character):
+    primaryFunction = graphene.StringField()
+
+
+class Query(graphene.ObjectType):
+    hero = graphene.Field(Character,
+        episode = graphene.Argument(Episode)
+    )
+    human = graphene.Field(Human,
+        id = graphene.Argument(graphene.String)
+    )
+    droid = graphene.Field(Droid,
+        id = graphene.Argument(graphene.String)
+    )
+
+    @resolve_only_args
+    def resolve_hero(self, episode):
+        return wrap_character(getHero(episode))
+
+    @resolve_only_args
+    def resolve_human(self, id):
+        return wrap_character(getHuman(id))
+        if human:
+            return Human(human)
+
+    @resolve_only_args
+    def resolve_droid(self, id):
+        return wrap_character(getDroid(id))
+
+
+Schema = graphene.Schema(query=Query)
diff --git a/tox.ini b/tox.ini
index 09e27276..735a042d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -6,6 +6,8 @@ deps=
     pytest>=2.7.2
     django>=1.8.0,<1.9
     flake8
+    six
+    blinker
     singledispatch
 commands=
     py.test

From cd216447c256f4aef8501653955ea2d80eb993a0 Mon Sep 17 00:00:00 2001
From: Syrus Akbary <me@syrusakbary.com>
Date: Fri, 25 Sep 2015 16:36:18 -0700
Subject: [PATCH 2/8] Added test relay schema

---
 tests/starwars_relay/schema.py | 36 +++++++++++++++++-----------------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/tests/starwars_relay/schema.py b/tests/starwars_relay/schema.py
index 1a7e570d..a6a0a750 100644
--- a/tests/starwars_relay/schema.py
+++ b/tests/starwars_relay/schema.py
@@ -1,47 +1,49 @@
 import graphene
-from graphene import resolve_only_args
+from graphene import resolve_only_args, relay
 
-from .data import getHero, getHuman, getCharacter, getDroid, Human as _Human, Droid as _Droid
+from .data import (
+    getHero, getHuman, getCharacter, getDroid,
+    Human as _Human, Droid as _Droid)
 
 Episode = graphene.Enum('Episode', dict(
-    NEWHOPE = 4,
-    EMPIRE = 5,
-    JEDI = 6
+    NEWHOPE=4,
+    EMPIRE=5,
+    JEDI=6
 ))
 
+
 def wrap_character(character):
-    if isinstance(character, _Human): 
+    if isinstance(character, _Human):
         return Human(character)
     elif isinstance(character, _Droid):
         return Droid(character)
 
+
 class Character(graphene.Interface):
-    id = graphene.IDField()
     name = graphene.StringField()
-    friends = graphene.ListField('self')
+    friends = relay.Connection('self')
     appearsIn = graphene.ListField(Episode)
 
     def resolve_friends(self, args, *_):
         return [wrap_character(getCharacter(f)) for f in self.instance.friends]
 
-class Human(Character):
+
+class Human(relay.Node, Character):
     homePlanet = graphene.StringField()
 
 
-class Droid(Character):
+class Droid(relay.Node, Character):
     primaryFunction = graphene.StringField()
 
 
 class Query(graphene.ObjectType):
     hero = graphene.Field(Character,
-        episode = graphene.Argument(Episode)
-    )
+                          episode=graphene.Argument(Episode))
     human = graphene.Field(Human,
-        id = graphene.Argument(graphene.String)
-    )
+                           id=graphene.Argument(graphene.String))
     droid = graphene.Field(Droid,
-        id = graphene.Argument(graphene.String)
-    )
+                           id=graphene.Argument(graphene.String))
+    node = graphene.Field(relay.Node)
 
     @resolve_only_args
     def resolve_hero(self, episode):
@@ -50,8 +52,6 @@ class Query(graphene.ObjectType):
     @resolve_only_args
     def resolve_human(self, id):
         return wrap_character(getHuman(id))
-        if human:
-            return Human(human)
 
     @resolve_only_args
     def resolve_droid(self, id):

From 1ec2f5a4c30304abfe13d6f411cca2330a29d84d Mon Sep 17 00:00:00 2001
From: Syrus Akbary <me@syrusakbary.com>
Date: Fri, 25 Sep 2015 19:41:11 -0700
Subject: [PATCH 3/8] Added app_label

---
 graphene/core/options.py       |  5 +++--
 graphene/core/types.py         |  2 +-
 graphene/core/utils.py         | 30 ++++++++++++++++++++++++------
 tests/starwars_relay/schema.py |  2 +-
 4 files changed, 29 insertions(+), 10 deletions(-)

diff --git a/graphene/core/options.py b/graphene/core/options.py
index 5a016dff..a277be15 100644
--- a/graphene/core/options.py
+++ b/graphene/core/options.py
@@ -1,17 +1,18 @@
 from graphene.utils import cached_property
 
-DEFAULT_NAMES = ('description', 'name', 'interface',
+DEFAULT_NAMES = ('app_label', 'description', 'name', 'interface',
                  'type_name', 'interfaces',  'proxy')
 
 
 class Options(object):
-    def __init__(self, meta=None):
+    def __init__(self, meta=None, app_label=None):
         self.meta = meta
         self.local_fields = []
         self.interface = False
         self.proxy = False
         self.interfaces = []
         self.parents = []
+        self.app_label = app_label
 
     def contribute_to_class(self, cls, name):
         cls._meta = self
diff --git a/graphene/core/types.py b/graphene/core/types.py
index a5645b16..9b136458 100644
--- a/graphene/core/types.py
+++ b/graphene/core/types.py
@@ -33,7 +33,7 @@ class ObjectTypeMeta(type):
             meta = attr_meta
         base_meta = getattr(new_class, '_meta', None)
 
-        new_class.add_to_class('_meta', Options(meta))
+        new_class.add_to_class('_meta', Options(meta, module))
         if base_meta and base_meta.proxy:
             new_class._meta.interface = base_meta.interface
         # Add all attributes to the class.
diff --git a/graphene/core/utils.py b/graphene/core/utils.py
index 2441e644..d6ad10d4 100644
--- a/graphene/core/utils.py
+++ b/graphene/core/utils.py
@@ -14,16 +14,34 @@ def get_object_type(field_type, object_type=None):
         if field_type == 'self':
             field_type = object_type._meta.type
         else:
-            object_type = get_registered_object_type(field_type)
+            object_type = get_registered_object_type(field_type, object_type)
             field_type = object_type._meta.type
     return field_type
 
 
-def get_registered_object_type(name):
-    for object_type in registered_object_types:
-        if object_type._meta.type_name == name:
-            return object_type
-    return None
+def get_registered_object_type(name, object_type=None):
+    app_label = None
+    object_type_name = name
+
+    if '.' in name:
+        app_label, object_type_name = name.split('.', 1)
+    elif object_type:
+        app_label = object_type._meta.app_label
+
+    # Filter all registered object types which have the same name
+    ots = [ot for ot in registered_object_types if ot._meta.type_name == name]
+    # If the list have more than one object type with the name, filter by
+    # the app_label
+    if len(ots)>1 and app_label:
+        ots = [ot for ot in ots if ot._meta.app_label == app_label]
+
+    if len(ots)>1:
+        raise Exception('Multiple ObjectTypes returned with the name %s' % name)
+    if not ots:
+        raise Exception('No ObjectType found with name %s' % name)
+
+    return ots[0]
+
 
 @signals.class_prepared.connect
 def object_type_created(sender):
diff --git a/tests/starwars_relay/schema.py b/tests/starwars_relay/schema.py
index a6a0a750..b2e65db5 100644
--- a/tests/starwars_relay/schema.py
+++ b/tests/starwars_relay/schema.py
@@ -21,7 +21,7 @@ def wrap_character(character):
 
 class Character(graphene.Interface):
     name = graphene.StringField()
-    friends = relay.Connection('self')
+    friends = relay.Connection('Character')
     appearsIn = graphene.ListField(Episode)
 
     def resolve_friends(self, args, *_):

From e9cf8616ba5949810e0f8436e946bb8749840c5e Mon Sep 17 00:00:00 2001
From: Syrus Akbary <me@syrusakbary.com>
Date: Fri, 25 Sep 2015 19:54:14 -0700
Subject: [PATCH 4/8] Improved app_label logic

---
 graphene/core/types.py | 7 ++++++-
 graphene/core/utils.py | 4 ++--
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/graphene/core/types.py b/graphene/core/types.py
index 9b136458..e83591b1 100644
--- a/graphene/core/types.py
+++ b/graphene/core/types.py
@@ -33,7 +33,12 @@ class ObjectTypeMeta(type):
             meta = attr_meta
         base_meta = getattr(new_class, '_meta', None)
 
-        new_class.add_to_class('_meta', Options(meta, module))
+        if '.' in module:
+            app_label, _ = module.rsplit('.', 1)
+        else:
+            app_label = module
+
+        new_class.add_to_class('_meta', Options(meta, app_label))
         if base_meta and base_meta.proxy:
             new_class._meta.interface = base_meta.interface
         # Add all attributes to the class.
diff --git a/graphene/core/utils.py b/graphene/core/utils.py
index d6ad10d4..69039d89 100644
--- a/graphene/core/utils.py
+++ b/graphene/core/utils.py
@@ -24,12 +24,12 @@ def get_registered_object_type(name, object_type=None):
     object_type_name = name
 
     if '.' in name:
-        app_label, object_type_name = name.split('.', 1)
+        app_label, object_type_name = name.rsplit('.', 1)
     elif object_type:
         app_label = object_type._meta.app_label
 
     # Filter all registered object types which have the same name
-    ots = [ot for ot in registered_object_types if ot._meta.type_name == name]
+    ots = [ot for ot in registered_object_types if ot._meta.type_name == object_type_name]
     # If the list have more than one object type with the name, filter by
     # the app_label
     if len(ots)>1 and app_label:

From d2dc25cc07a60c9bfaa1cd33463fd87609f297da Mon Sep 17 00:00:00 2001
From: Syrus Akbary <me@syrusakbary.com>
Date: Fri, 25 Sep 2015 20:01:14 -0700
Subject: [PATCH 5/8] Improved syntax in starwars

---
 tests/starwars/schema.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/tests/starwars/schema.py b/tests/starwars/schema.py
index 018a1fc8..9fbee302 100644
--- a/tests/starwars/schema.py
+++ b/tests/starwars/schema.py
@@ -9,21 +9,24 @@ Episode = graphene.Enum('Episode', dict(
     JEDI = 6
 ))
 
+
 def wrap_character(character):
     if isinstance(character, _Human): 
         return Human(character)
     elif isinstance(character, _Droid):
         return Droid(character)
 
+
 class Character(graphene.Interface):
     id = graphene.IDField()
     name = graphene.StringField()
-    friends = graphene.ListField('Character')
+    friends = graphene.ListField('self')
     appearsIn = graphene.ListField(Episode)
 
     def resolve_friends(self, args, *_):
         return [wrap_character(getCharacter(f)) for f in self.instance.friends]
 
+
 class Human(Character):
     homePlanet = graphene.StringField()
 
@@ -50,8 +53,6 @@ class Query(graphene.ObjectType):
     @resolve_only_args
     def resolve_human(self, id):
         return wrap_character(getHuman(id))
-        if human:
-            return Human(human)
 
     @resolve_only_args
     def resolve_droid(self, id):

From 1b7caac39b255035ce04cccb07c54e85f8bb07a8 Mon Sep 17 00:00:00 2001
From: Syrus Akbary <me@syrusakbary.com>
Date: Fri, 25 Sep 2015 23:25:10 -0700
Subject: [PATCH 6/8] =?UTF-8?q?First=20working=20version=20with=20relay=20?=
 =?UTF-8?q?=F0=9F=92=AA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 graphene/__init__.py                          |   5 -
 graphene/core/fields.py                       |   6 +
 graphene/core/options.py                      |   1 -
 graphene/core/types.py                        |   4 +
 graphene/core/utils.py                        |  16 ++-
 graphene/relay/__init__.py                    |  87 ++++++++++++++-
 tests/core/test_types.py                      |   4 +-
 tests/starwars_relay/__init__.py              |   0
 tests/starwars_relay/data.py                  |  98 ++++++++++++++++
 tests/starwars_relay/schema.py                |  74 ++++++------
 tests/starwars_relay/schema_other.py          |  60 ++++++++++
 tests/starwars_relay/test_connections.py      |  37 ++++++
 .../test_objectidentification.py              | 105 ++++++++++++++++++
 13 files changed, 441 insertions(+), 56 deletions(-)
 create mode 100644 tests/starwars_relay/__init__.py
 create mode 100644 tests/starwars_relay/data.py
 create mode 100644 tests/starwars_relay/schema_other.py
 create mode 100644 tests/starwars_relay/test_connections.py
 create mode 100644 tests/starwars_relay/test_objectidentification.py

diff --git a/graphene/__init__.py b/graphene/__init__.py
index 6024313c..c84eeb8c 100644
--- a/graphene/__init__.py
+++ b/graphene/__init__.py
@@ -1,7 +1,6 @@
 from graphql.core.type import (
     GraphQLEnumType as Enum,
     GraphQLArgument as Argument,
-    # GraphQLSchema as Schema,
     GraphQLString as String,
     GraphQLInt as Int,
     GraphQLID as ID
@@ -26,7 +25,3 @@ from graphene.core.types import (
 from graphene.decorators import (
     resolve_only_args
 )
-
-from graphene.relay import (
-    Relay
-)
diff --git a/graphene/core/fields.py b/graphene/core/fields.py
index d37fed1d..478e3630 100644
--- a/graphene/core/fields.py
+++ b/graphene/core/fields.py
@@ -99,6 +99,12 @@ class Field(object):
         return '<%s>' % path
 
 
+class NativeField(Field):
+    def __init__(self, field=None):
+        super(NativeField, self).__init__(None)
+        self.field = field or getattr(self, 'field')
+
+
 class TypeField(Field):
     def __init__(self, *args, **kwargs):
         super(TypeField, self).__init__(self.field_type, *args, **kwargs)
diff --git a/graphene/core/options.py b/graphene/core/options.py
index a277be15..c3dc13af 100644
--- a/graphene/core/options.py
+++ b/graphene/core/options.py
@@ -54,7 +54,6 @@ class Options(object):
 
     def add_field(self, field):
         self.local_fields.append(field)
-        setattr(self.parent, field.field_name, field)
 
     @cached_property
     def fields(self):
diff --git a/graphene/core/types.py b/graphene/core/types.py
index e83591b1..3ffb466d 100644
--- a/graphene/core/types.py
+++ b/graphene/core/types.py
@@ -91,6 +91,10 @@ class ObjectType(six.with_metaclass(ObjectTypeMeta)):
         self.instance = instance
         signals.post_init.send(self.__class__, instance=self)
 
+    def __getattr__(self, name):
+        if self.instance:
+            return getattr(self.instance, name)
+
     def get_field(self, field):
         return getattr(self.instance, field, None)
 
diff --git a/graphene/core/utils.py b/graphene/core/utils.py
index 69039d89..b50a7664 100644
--- a/graphene/core/utils.py
+++ b/graphene/core/utils.py
@@ -7,17 +7,23 @@ registered_object_types = []
 
 
 def get_object_type(field_type, object_type=None):
+    native_type = get_type(field_type, object_type)
+    if native_type:
+        field_type = native_type._meta.type
+    return field_type 
+
+
+def get_type(field_type, object_type=None):
     _is_class = inspect.isclass(field_type)
     if _is_class and issubclass(field_type, ObjectType):
-        field_type = field_type._meta.type
+        return field_type
     elif isinstance(field_type, basestring):
         if field_type == 'self':
-            field_type = object_type._meta.type
+            return object_type
         else:
             object_type = get_registered_object_type(field_type, object_type)
-            field_type = object_type._meta.type
-    return field_type
-
+            return object_type
+    return None
 
 def get_registered_object_type(name, object_type=None):
     app_label = None
diff --git a/graphene/relay/__init__.py b/graphene/relay/__init__.py
index 80feb122..d93aa7ac 100644
--- a/graphene/relay/__init__.py
+++ b/graphene/relay/__init__.py
@@ -1,2 +1,85 @@
-class Relay(object):
-	pass
+import collections
+
+from graphene import signals
+from graphene.core.fields import Field, NativeField
+from graphene.core.types import Interface
+from graphene.core.utils import get_type
+from graphene.utils import cached_property
+
+from graphql_relay.node.node import (
+    nodeDefinitions,
+    globalIdField,
+    fromGlobalId
+)
+from graphql_relay.connection.arrayconnection import (
+    connectionFromArray
+)
+from graphql_relay.connection.connection import (
+    connectionArgs,
+    connectionDefinitions
+)
+
+registered_nodes = {}
+
+
+def getNode(globalId, *args):
+    resolvedGlobalId = fromGlobalId(globalId)
+    _type, _id = resolvedGlobalId.type, resolvedGlobalId.id
+    if _type in registered_nodes:
+        object_type = registered_nodes[_type]
+        return object_type.get_node(_id)
+
+
+def getNodeType(obj):
+    return obj._meta.type
+
+
+_nodeDefinitions = nodeDefinitions(getNode, getNodeType)
+
+
+class Node(Interface):
+    @classmethod
+    def get_graphql_type(cls):
+        if cls is Node:
+            # Return only nodeInterface when is the Node Inerface
+            return _nodeDefinitions.nodeInterface
+        return super(Node, cls).get_graphql_type()
+
+
+class NodeField(NativeField):
+    field = _nodeDefinitions.nodeField
+
+
+class ConnectionField(Field):
+    def __init__(self, field_type, resolve=None, description=''):
+        super(ConnectionField, self).__init__(field_type, resolve=resolve, 
+                                              args=connectionArgs, description=description)
+
+    def resolve(self, instance, args, info):
+        resolved = super(ConnectionField, self).resolve(instance, args, info)
+        if resolved:
+            assert isinstance(resolved, collections.Iterable), 'Resolved value from the connection field have to be iterable'
+            return connectionFromArray(resolved, args)
+
+    @cached_property
+    def type(self):
+        object_type = get_type(self.field_type, self.object_type)
+        assert issubclass(object_type, Node), 'Only nodes have connections.'
+        return object_type.connection
+
+
+@signals.class_prepared.connect
+def object_type_created(object_type):
+    if issubclass(object_type, Node):
+        type_name = object_type._meta.type_name
+        assert type_name not in registered_nodes, 'Two nodes with the same type_name: %s' % type_name
+        registered_nodes[type_name] = object_type
+        # def getId(*args, **kwargs):
+        #     print '**GET ID', args, kwargs
+        #     return 2
+        field = NativeField(globalIdField(type_name))
+        object_type.add_to_class('id', field)
+        assert hasattr(object_type, 'get_node'), 'get_node classmethod not found in %s Node' % type_name
+
+        connection = connectionDefinitions(type_name, object_type._meta.type).connectionType
+        object_type.add_to_class('connection', connection)
diff --git a/tests/core/test_types.py b/tests/core/test_types.py
index e8d3ec69..b17cae48 100644
--- a/tests/core/test_types.py
+++ b/tests/core/test_types.py
@@ -29,7 +29,7 @@ def test_interface():
     assert Character._meta.type_name == 'Character'
     assert isinstance(object_type, GraphQLInterfaceType)
     assert object_type.description == 'Character description'
-    assert object_type.get_fields() == {'name': Character.name.field}
+    assert object_type.get_fields() == {'name': Character._meta.fields_map['name'].field}
 
 def test_object_type():
     object_type = Human._meta.type
@@ -37,5 +37,5 @@ def test_object_type():
     assert Human._meta.type_name == 'Human'
     assert isinstance(object_type, GraphQLObjectType)
     assert object_type.description == 'Human description'
-    assert object_type.get_fields() == {'name': Character.name.field, 'friends': Human.friends.field}
+    assert object_type.get_fields() == {'name': Character._meta.fields_map['name'].field, 'friends': Human._meta.fields_map['friends'].field}
     assert object_type.get_interfaces() == [Character._meta.type]
diff --git a/tests/starwars_relay/__init__.py b/tests/starwars_relay/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/starwars_relay/data.py b/tests/starwars_relay/data.py
new file mode 100644
index 00000000..31ac591d
--- /dev/null
+++ b/tests/starwars_relay/data.py
@@ -0,0 +1,98 @@
+from collections import namedtuple
+
+Ship = namedtuple('Ship',['id', 'name'])
+Faction = namedtuple('Faction',['id', 'name', 'ships'])
+
+xwing = Ship(
+    id='1',
+    name='X-Wing',
+)
+
+ywing = Ship(
+    id='2',
+    name='Y-Wing',
+)
+
+awing = Ship(
+    id='3',
+    name='A-Wing',
+)
+
+# Yeah, technically it's Corellian. But it flew in the service of the rebels,
+# so for the purposes of this demo it's a rebel ship.
+falcon = Ship(
+    id='4',
+    name='Millenium Falcon',
+)
+
+homeOne = Ship(
+    id='5',
+    name='Home One',
+)
+
+tieFighter = Ship(
+    id='6',
+    name='TIE Fighter',
+)
+
+tieInterceptor = Ship(
+    id='7',
+    name='TIE Interceptor',
+)
+
+executor = Ship(
+    id='8',
+    name='Executor',
+)
+
+rebels = Faction(
+    id='1',
+    name='Alliance to Restore the Republic',
+    ships=['1', '2', '3', '4', '5']
+)
+
+empire = Faction(
+    id='2',
+    name='Galactic Empire',
+    ships= ['6', '7', '8']
+)
+
+data = {
+    'Faction': {
+        '1': rebels,
+        '2': empire
+    },
+    'Ship': {
+        '1': xwing,
+        '2': ywing,
+        '3': awing,
+        '4': falcon,
+        '5': homeOne,
+        '6': tieFighter,
+        '7': tieInterceptor,
+        '8': executor
+    }
+}
+
+def createShip(shipName, factionId):
+    nextShip = len(data['Ship'].keys())+1
+    newShip = Ship(
+        id=str(nextShip),
+        name=shipName
+    )
+    data['Ship'][newShip.id] = newShip
+    data['Faction'][factionId].ships.append(newShip.id)
+    return newShip
+
+
+def getShip(_id):
+    return data['Ship'][_id]
+
+def getFaction(_id):
+    return data['Faction'][_id]
+
+def getRebels():
+    return rebels
+
+def getEmpire():
+    return empire
diff --git a/tests/starwars_relay/schema.py b/tests/starwars_relay/schema.py
index b2e65db5..55e03e6c 100644
--- a/tests/starwars_relay/schema.py
+++ b/tests/starwars_relay/schema.py
@@ -2,60 +2,52 @@ import graphene
 from graphene import resolve_only_args, relay
 
 from .data import (
-    getHero, getHuman, getCharacter, getDroid,
-    Human as _Human, Droid as _Droid)
-
-Episode = graphene.Enum('Episode', dict(
-    NEWHOPE=4,
-    EMPIRE=5,
-    JEDI=6
-))
+    getFaction,
+    getShip,
+    getRebels,
+    getEmpire,
+)
 
 
-def wrap_character(character):
-    if isinstance(character, _Human):
-        return Human(character)
-    elif isinstance(character, _Droid):
-        return Droid(character)
+class Ship(relay.Node):
+    '''A ship in the Star Wars saga'''
+    name = graphene.StringField(description='The name of the ship.')
+
+    @classmethod
+    def get_node(cls, id):
+        ship = getShip(id)
+        if ship:
+            return Ship(ship)
 
 
-class Character(graphene.Interface):
-    name = graphene.StringField()
-    friends = relay.Connection('Character')
-    appearsIn = graphene.ListField(Episode)
+class Faction(relay.Node):
+    '''A faction in the Star Wars saga'''
+    name = graphene.StringField(description='The name of the faction.')
+    ships = relay.ConnectionField(Ship, description='The ships used by the faction.')
 
-    def resolve_friends(self, args, *_):
-        return [wrap_character(getCharacter(f)) for f in self.instance.friends]
+    @resolve_only_args
+    def resolve_ships(self, **kwargs):
+        return [Ship(getShip(ship)) for ship in self.instance.ships]
 
-
-class Human(relay.Node, Character):
-    homePlanet = graphene.StringField()
-
-
-class Droid(relay.Node, Character):
-    primaryFunction = graphene.StringField()
+    @classmethod
+    def get_node(cls, id):
+        faction = getFaction(id)
+        if faction:
+            return Faction(faction)
 
 
 class Query(graphene.ObjectType):
-    hero = graphene.Field(Character,
-                          episode=graphene.Argument(Episode))
-    human = graphene.Field(Human,
-                           id=graphene.Argument(graphene.String))
-    droid = graphene.Field(Droid,
-                           id=graphene.Argument(graphene.String))
-    node = graphene.Field(relay.Node)
+    rebels = graphene.Field(Faction)
+    empire = graphene.Field(Faction)
+    node = relay.NodeField()
 
     @resolve_only_args
-    def resolve_hero(self, episode):
-        return wrap_character(getHero(episode))
+    def resolve_rebels(self):
+        return Faction(getRebels())
 
     @resolve_only_args
-    def resolve_human(self, id):
-        return wrap_character(getHuman(id))
-
-    @resolve_only_args
-    def resolve_droid(self, id):
-        return wrap_character(getDroid(id))
+    def resolve_empire(self):
+        return Faction(getEmpire())
 
 
 Schema = graphene.Schema(query=Query)
diff --git a/tests/starwars_relay/schema_other.py b/tests/starwars_relay/schema_other.py
new file mode 100644
index 00000000..dd33bba7
--- /dev/null
+++ b/tests/starwars_relay/schema_other.py
@@ -0,0 +1,60 @@
+import graphene
+from graphene import resolve_only_args, relay
+
+from .data import (
+    getHero, getHuman, getCharacter, getDroid,
+    Human as _Human, Droid as _Droid)
+
+Episode = graphene.Enum('Episode', dict(
+    NEWHOPE=4,
+    EMPIRE=5,
+    JEDI=6
+))
+
+def wrap_character(character):
+    if isinstance(character, _Human):
+        return Human(character)
+    elif isinstance(character, _Droid):
+        return Droid(character)
+
+
+class Character(graphene.Interface):
+    name = graphene.StringField()
+    friends = relay.Connection('Character')
+    appearsIn = graphene.ListField(Episode)
+
+    def resolve_friends(self, args, *_):
+        return [wrap_character(getCharacter(f)) for f in self.instance.friends]
+
+
+class Human(relay.Node, Character):
+    homePlanet = graphene.StringField()
+
+
+class Droid(relay.Node, Character):
+    primaryFunction = graphene.StringField()
+
+
+class Query(graphene.ObjectType):
+    hero = graphene.Field(Character,
+                          episode=graphene.Argument(Episode))
+    human = graphene.Field(Human,
+                           id=graphene.Argument(graphene.String))
+    droid = graphene.Field(Droid,
+                           id=graphene.Argument(graphene.String))
+    node = relay.NodeField()
+
+    @resolve_only_args
+    def resolve_hero(self, episode):
+        return wrap_character(getHero(episode))
+
+    @resolve_only_args
+    def resolve_human(self, id):
+        return wrap_character(getHuman(id))
+
+    @resolve_only_args
+    def resolve_droid(self, id):
+        return wrap_character(getDroid(id))
+
+
+Schema = graphene.Schema(query=Query)
diff --git a/tests/starwars_relay/test_connections.py b/tests/starwars_relay/test_connections.py
new file mode 100644
index 00000000..1bac0a4d
--- /dev/null
+++ b/tests/starwars_relay/test_connections.py
@@ -0,0 +1,37 @@
+from pytest import raises
+from graphql.core import graphql
+
+from .schema import Schema
+
+def test_correct_fetch_first_ship_rebels():
+    query = '''
+    query RebelsShipsQuery {
+      rebels {
+        name,
+        ships(first: 1) {
+          edges {
+            node {
+              name
+            }
+          }
+        }
+      }
+    }
+    '''
+    expected = {
+      'rebels': {
+        'name': 'Alliance to Restore the Republic',
+        'ships': {
+          'edges': [
+            {
+              'node': {
+                'name': 'X-Wing'
+              }
+            }
+          ]
+        }
+      }
+    }
+    result = Schema.execute(query)
+    assert result.errors == None
+    assert result.data == expected
diff --git a/tests/starwars_relay/test_objectidentification.py b/tests/starwars_relay/test_objectidentification.py
new file mode 100644
index 00000000..85050b6f
--- /dev/null
+++ b/tests/starwars_relay/test_objectidentification.py
@@ -0,0 +1,105 @@
+from pytest import raises
+from graphql.core import graphql
+
+from .schema import Schema
+
+def test_correctly_fetches_id_name_rebels():
+    query = '''
+      query RebelsQuery {
+        rebels {
+          id
+          name
+        }
+      }
+    '''
+    expected = {
+      'rebels': {
+        'id': 'RmFjdGlvbjox',
+        'name': 'Alliance to Restore the Republic'
+      }
+    }
+    result = Schema.execute(query)
+    assert result.errors == None
+    assert result.data == expected
+
+def test_correctly_refetches_rebels():
+    query = '''
+      query RebelsRefetchQuery {
+        node(id: "RmFjdGlvbjox") {
+          id
+          ... on Faction {
+            name
+          }
+        }
+      }
+    '''
+    expected = {
+      'node': {
+        'id': 'RmFjdGlvbjox',
+        'name': 'Alliance to Restore the Republic'
+      }
+    }
+    result = Schema.execute(query)
+    assert result.errors == None
+    assert result.data == expected
+
+def test_correctly_fetches_id_name_empire():
+    query = '''
+      query EmpireQuery {
+        empire {
+          id
+          name
+        }
+      }
+    '''
+    expected = {
+      'empire': {
+        'id': 'RmFjdGlvbjoy',
+        'name': 'Galactic Empire'
+      }
+    }
+    result = Schema.execute(query)
+    assert result.errors == None
+    assert result.data == expected
+
+def test_correctly_refetches_empire():
+    query = '''
+      query EmpireRefetchQuery {
+        node(id: "RmFjdGlvbjoy") {
+          id
+          ... on Faction {
+            name
+          }
+        }
+      }
+    '''
+    expected = {
+      'node': {
+        'id': 'RmFjdGlvbjoy',
+        'name': 'Galactic Empire'
+      }
+    }
+    result = Schema.execute(query)
+    assert result.errors == None
+    assert result.data == expected
+
+def test_correctly_refetches_xwing():
+    query = '''
+      query XWingRefetchQuery {
+        node(id: "U2hpcDox") {
+          id
+          ... on Ship {
+            name
+          }
+        }
+      }
+    '''
+    expected = {
+      'node': {
+        'id': 'U2hpcDox',
+        'name': 'X-Wing'
+      }
+    }
+    result = Schema.execute(query)
+    assert result.errors == None
+    assert result.data == expected

From 2d87f527bf5f6d74f271b5b6649119a781243d55 Mon Sep 17 00:00:00 2001
From: Syrus Akbary <me@syrusakbary.com>
Date: Fri, 25 Sep 2015 23:31:53 -0700
Subject: [PATCH 7/8] Added Relay Schema example

---
 README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/README.md b/README.md
index 73290509..187481ed 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,62 @@ query = '''
 result = Schema.execute(query)
 ```
 
+### Relay Schema
+
+Graphene also supports Relay, check the (Starwars Relay example)[/tests/starwars_relay]!
+
+```python
+import graphene
+from graphene import relay
+
+class Ship(relay.Node):
+    '''A ship in the Star Wars saga'''
+    name = graphene.StringField(description='The name of the ship.')
+
+    @classmethod
+    def get_node(cls, id):
+        ship = getShip(id)
+        if ship:
+            return Ship(ship)
+
+
+class Faction(relay.Node):
+    '''A faction in the Star Wars saga'''
+    name = graphene.StringField(description='The name of the faction.')
+    ships = relay.ConnectionField(Ship, description='The ships used by the faction.')
+
+    @resolve_only_args
+    def resolve_ships(self, **kwargs):
+        return [Ship(getShip(ship)) for ship in self.instance.ships]
+
+    @classmethod
+    def get_node(cls, id):
+        faction = getFaction(id)
+        if faction:
+            return Faction(faction)
+
+
+class Query(graphene.ObjectType):
+    rebels = graphene.Field(Faction)
+    empire = graphene.Field(Faction)
+    node = relay.NodeField()
+
+    @resolve_only_args
+    def resolve_rebels(self):
+        return Faction(getRebels())
+
+    @resolve_only_args
+    def resolve_empire(self):
+        return Faction(getEmpire())
+
+
+Schema = graphene.Schema(query=Query)
+
+# Later on, for querying
+Schema.execute('''rebels { name }''')
+
+```
+
 ## Contributing
 
 After cloning this repo, ensure dependencies are installed by running:

From dde58ae4b1800ecc072d7c8ea4c64704d9f5aff3 Mon Sep 17 00:00:00 2001
From: Syrus Akbary <me@syrusakbary.com>
Date: Fri, 25 Sep 2015 23:48:53 -0700
Subject: [PATCH 8/8] Added some relay tests.

---
 tests/relay/test_relay.py | 41 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)
 create mode 100644 tests/relay/test_relay.py

diff --git a/tests/relay/test_relay.py b/tests/relay/test_relay.py
new file mode 100644
index 00000000..c532949f
--- /dev/null
+++ b/tests/relay/test_relay.py
@@ -0,0 +1,41 @@
+from pytest import raises
+
+import graphene
+from graphene import relay
+
+
+class OtherNode(relay.Node):
+    name = graphene.StringField()
+
+    @classmethod
+    def get_node(cls, id):
+        pass
+
+
+def test_field_no_contributed_raises_error():
+    with raises(Exception) as excinfo:
+        class Part(relay.Node):
+            x = graphene.StringField()
+
+    assert 'get_node' in str(excinfo.value)
+
+
+def test_node_should_have_connection():
+    assert OtherNode.connection
+
+
+def test_node_should_have_id_field():
+    assert 'id' in OtherNode._meta.fields_map
+
+
+def test_field_no_contributed_raises_error():
+    with raises(Exception) as excinfo:
+        class Ship(graphene.ObjectType):
+            name = graphene.StringField()
+
+
+        class Faction(relay.Node):
+            name = graphene.StringField()
+            ships = relay.ConnectionField(Ship)
+
+    assert 'same type_name' in str(excinfo.value)