Skip to content

Improved the global registry #115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ Contents:
authorization
debug
introspection
registry
60 changes: 60 additions & 0 deletions docs/registry.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
Graphene-Django Registry
========================

Graphene-Django uses a Registry to keep track of all the Django Models
and the ``DjangoObjectTypes`` associated to them.

This way, we make the library smart enough to convert automatically the
relations between models to Graphene fields automatically (when possible).


Global registry
---------------

By default, all model/objecttype relations will live in the global registry.
You retrieve using the function ``get_global_registry`` in
``graphene_django.registry``.

.. code:: python

from graphene_django.registry get_global_registry

class Reporter(DjangoObjectType):
'''Reporter description'''
class Meta:
model = ReporterModel

global_registry = get_global_registry
global_registry.get_unique_type_for_model(ReporterModel) # == Reporter


Multiple types for one model
----------------------------

There will be some cases where we need one Django Model to
have multiple graphene ``ObjectType``s associated to it.

In this case, we can either use ``skip_global_registry`` to create
a new isolated registry for that type (so it doesn't interfere with
the global registry), or we can create a custom registry for it.

.. code:: python

from graphene_django.registry import Registry

class Reporter(DjangoObjectType):
'''Reporter description'''
class Meta:
model = ReporterModel

class Reporter2(DjangoObjectType):
'''Reporter2 description'''
class Meta:
model = ReporterModel
skip_global_registry = True
# We can also specify a custom registry with
# registry = Registry()


This way, the ``ReporterModel`` could have two different types living in the same
schema.
8 changes: 4 additions & 4 deletions graphene_django/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def convert_onetoone_field_to_djangomodel(field, registry=None):
model = get_related_model(field)

def dynamic_type():
_type = registry.get_type_for_model(model)
_type = registry.get_unique_type_for_model(model)
if not _type:
return

Expand All @@ -145,7 +145,7 @@ def convert_field_to_list_or_connection(field, registry=None):
model = get_related_model(field)

def dynamic_type():
_type = registry.get_type_for_model(model)
_type = registry.get_unique_type_for_model(model)
if not _type:
return

Expand All @@ -163,7 +163,7 @@ def convert_relatedfield_to_djangomodel(field, registry=None):
model = field.model

def dynamic_type():
_type = registry.get_type_for_model(model)
_type = registry.get_unique_type_for_model(model)
if not _type:
return

Expand All @@ -183,7 +183,7 @@ def convert_field_to_djangomodel(field, registry=None):
model = get_related_model(field)

def dynamic_type():
_type = registry.get_type_for_model(model)
_type = registry.get_unique_type_for_model(model)
if not _type:
return

Expand Down
5 changes: 5 additions & 0 deletions graphene_django/debug/tests/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@
from graphene_django.utils import DJANGO_FILTER_INSTALLED

from ...tests.models import Reporter
from ...registry import reset_global_registry
from ..middleware import DjangoDebugMiddleware
from ..types import DjangoDebug


def setup_function(function):
reset_global_registry()


class context(object):
pass

Expand Down
21 changes: 17 additions & 4 deletions graphene_django/filter/tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
GlobalIDMultipleChoiceField)
from graphene_django.tests.models import Article, Pet, Reporter
from graphene_django.utils import DJANGO_FILTER_INSTALLED
from graphene_django.registry import Registry, reset_global_registry

pytestmark = []

Expand All @@ -24,6 +25,8 @@


if DJANGO_FILTER_INSTALLED:
reset_global_registry()

class ArticleNode(DjangoObjectType):

class Meta:
Expand All @@ -47,6 +50,10 @@ class Meta:

# schema = Schema()

@pytest.fixture
def _registry():
return Registry()


def get_args(field):
return field.args
Expand Down Expand Up @@ -134,33 +141,36 @@ def test_filter_shortcut_filterset_extra_meta():
assert 'headline' not in field.filterset_class.get_fields()


def test_filter_filterset_information_on_meta():
def test_filter_filterset_information_on_meta(_registry):
class ReporterFilterNode(DjangoObjectType):

class Meta:
model = Reporter
interfaces = (Node, )
filter_fields = ['first_name', 'articles']
registry = _registry

field = DjangoFilterConnectionField(ReporterFilterNode)
assert_arguments(field, 'first_name', 'articles')
assert_not_orderable(field)


def test_filter_filterset_information_on_meta_related():
def test_filter_filterset_information_on_meta_related(_registry):
class ReporterFilterNode(DjangoObjectType):

class Meta:
model = Reporter
interfaces = (Node, )
filter_fields = ['first_name', 'articles']
registry = _registry

class ArticleFilterNode(DjangoObjectType):

class Meta:
model = Article
interfaces = (Node, )
filter_fields = ['headline', 'reporter']
registry = _registry

class Query(ObjectType):
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
Expand All @@ -174,20 +184,22 @@ class Query(ObjectType):
assert_not_orderable(articles_field)


def test_filter_filterset_related_results():
def test_filter_filterset_related_results(_registry):
class ReporterFilterNode(DjangoObjectType):

class Meta:
model = Reporter
interfaces = (Node, )
filter_fields = ['first_name', 'articles']
registry = _registry

class ArticleFilterNode(DjangoObjectType):

class Meta:
interfaces = (Node, )
model = Article
filter_fields = ['headline', 'reporter']
registry = _registry

class Query(ObjectType):
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
Expand Down Expand Up @@ -315,7 +327,7 @@ class Meta:
assert multiple_filter.field_class == GlobalIDMultipleChoiceField


def test_filter_filterset_related_results():
def test_filter_filterset_related_results(_registry):
class ReporterFilterNode(DjangoObjectType):

class Meta:
Expand All @@ -324,6 +336,7 @@ class Meta:
filter_fields = {
'first_name': ['icontains']
}
registry = _registry

class Query(ObjectType):
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
Expand Down
36 changes: 27 additions & 9 deletions graphene_django/registry.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
from collections import defaultdict


class Registry(object):

def __init__(self):
self._registry = {}
self._registry_models = {}
self._registry = defaultdict(list)

def register(self, cls):
from .types import DjangoObjectType
model = cls._meta.model
assert issubclass(
cls, DjangoObjectType), 'Only DjangoObjectTypes can be registered, received "{}"'.format(
cls.__name__)
assert cls._meta.registry == self, 'Registry for a Model have to match.'
# assert self.get_type_for_model(cls._meta.model) == cls, (
# 'Multiple DjangoObjectTypes registered for "{}"'.format(cls._meta.model)
# )
if not getattr(cls._meta, 'skip_registry', False):
self._registry[cls._meta.model] = cls

def get_type_for_model(self, model):
self._registry[model].append(cls)

def get_unique_type_for_model(self, model):
types = self.get_types_for_model(model)
if not types:
return None

# If there is more than one type for the model, we should
# raise an error so both types don't collide in the same schema.
assert len(types) == 1, (
'Found multiple ObjectTypes associated with the same Django Model "{}.{}": {}. '
'You can use a different registry for each or skip '
'the global Registry with Meta.skip_global_registry = True". '
'Read more at http://docs.graphene-python.org/projects/django/en/latest/registry/ .'
).format(
model._meta.app_label,
model._meta.object_name,
repr(types),
)
return types[0]

def get_types_for_model(self, model):
return self._registry.get(model)


Expand Down
5 changes: 3 additions & 2 deletions graphene_django/tests/test_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
from ..compat import (ArrayField, HStoreField, JSONField, MissingType,
RangeField, UUIDField, DurationField)
from ..converter import convert_django_field, convert_django_field_with_choices
from ..registry import Registry
from ..registry import Registry, reset_global_registry
from ..types import DjangoObjectType
from .models import Article, Film, FilmDetails, Reporter


# from graphene.core.types.custom_scalars import DateTime, Time, JSONString
def setup_function(function):
reset_global_registry()


def assert_conversion(django_field, graphene_field, *args, **kwargs):
Expand Down
5 changes: 5 additions & 0 deletions graphene_django/tests/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@
from ..compat import MissingType, RangeField
from ..fields import DjangoConnectionField
from ..types import DjangoObjectType
from ..registry import reset_global_registry
from .models import Article, Reporter

pytestmark = pytest.mark.django_db


def setup_function(function):
reset_global_registry()


def test_should_query_only_fields():
with raises(Exception):
class ReporterType(DjangoObjectType):
Expand Down
64 changes: 64 additions & 0 deletions graphene_django/tests/test_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from pytest import raises

from ..registry import Registry, get_global_registry, reset_global_registry
from ..types import DjangoObjectType
from .models import Reporter as ReporterModel


def setup_function(function):
reset_global_registry()


def test_registry_basic():
global_registry = get_global_registry()

class Reporter(DjangoObjectType):
'''Reporter description'''
class Meta:
model = ReporterModel

assert Reporter._meta.registry == global_registry
assert global_registry.get_unique_type_for_model(ReporterModel) == Reporter


def test_registry_multiple_types():
global_registry = get_global_registry()

class Reporter(DjangoObjectType):
'''Reporter description'''
class Meta:
model = ReporterModel

class Reporter2(DjangoObjectType):
'''Reporter2 description'''
class Meta:
model = ReporterModel

assert global_registry.get_types_for_model(ReporterModel) == [Reporter, Reporter2]

with raises(Exception) as exc_info:
global_registry.get_unique_type_for_model(ReporterModel) == [Reporter, Reporter2]

assert str(exc_info.value) == (
'Found multiple ObjectTypes associated with the same '
'Django Model "tests.Reporter": {}. You can use a different '
'registry for each or skip the global Registry with '
'Meta.skip_global_registry = True". '
'Read more at http://docs.graphene-python.org/projects/django/en/latest/registry/ .'
).format(repr([Reporter, Reporter2]))


def test_registry_multiple_types_dont_collision_if_skip_global_registry():
class Reporter(DjangoObjectType):
'''Reporter description'''
class Meta:
model = ReporterModel

class Reporter2(DjangoObjectType):
'''Reporter2 description'''
class Meta:
model = ReporterModel
skip_global_registry = True

assert Reporter._meta.registry != Reporter2._meta.registry
assert Reporter2._meta.registry != get_global_registry()
6 changes: 5 additions & 1 deletion graphene_django/tests/test_schema.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from py.test import raises

from ..registry import Registry
from ..registry import Registry, reset_global_registry
from ..types import DjangoObjectType
from .models import Reporter


def setup_function(function):
reset_global_registry()


def test_should_raise_if_no_model():
with raises(Exception) as excinfo:
class Character1(DjangoObjectType):
Expand Down
Loading