Merge branch 'next' of github.com:graphql-python/graphene into next

This commit is contained in:
Syrus Akbary 2016-09-04 22:01:06 -07:00
commit 5da0fef083
4 changed files with 114 additions and 2 deletions

View File

@ -9,11 +9,14 @@ from graphene.types.json import JSONString
from .fields import SQLAlchemyConnectionField
try:
from sqlalchemy_utils.types.choice import ChoiceType
from sqlalchemy_utils import ChoiceType, ScalarListType
except ImportError:
class ChoiceType(object):
pass
class ScalarListType(object):
pass
def convert_sqlalchemy_relationship(relationship, registry):
direction = relationship.direction
@ -34,6 +37,33 @@ def convert_sqlalchemy_relationship(relationship, registry):
return Dynamic(dynamic_type)
def convert_sqlalchemy_composite(composite, registry):
converter = registry.get_converter_for_composite(composite.composite_class)
if not converter:
try:
raise Exception(
"Don't know how to convert the composite field %s (%s)" %
(composite, composite.composite_class))
except AttributeError:
# handle fields that are not attached to a class yet (don't have a parent)
raise Exception(
"Don't know how to convert the composite field %r (%s)" %
(composite, composite.composite_class))
return converter(composite, registry)
def _register_composite_class(cls, registry=None):
if registry is None:
from .registry import get_global_registry
registry = get_global_registry()
def inner(fn):
registry.register_composite_converter(cls, fn)
return inner
convert_sqlalchemy_composite.register = _register_composite_class
def convert_sqlalchemy_column(column, registry=None):
return convert_sqlalchemy_type(getattr(column, 'type', None), column, registry)
@ -85,6 +115,11 @@ def convert_column_to_enum(type, column, registry=None):
return Enum(name, type.choices, description=column.doc)
@convert_sqlalchemy_type.register(ScalarListType)
def convert_scalar_list_to_list(type, column, registry=None):
return List(String, description=column.doc)
@convert_sqlalchemy_type.register(postgresql.ARRAY)
def convert_postgres_array_to_list(type, column, registry=None):
graphene_type = convert_sqlalchemy_type(column.type.item_type, column)

View File

@ -2,6 +2,7 @@ class Registry(object):
def __init__(self):
self._registry = {}
self._registry_models = {}
self._registry_composites = {}
def register(self, cls):
from .types import SQLAlchemyObjectType
@ -16,6 +17,12 @@ class Registry(object):
def get_type_for_model(self, model):
return self._registry.get(model)
def register_composite_converter(self, composite, converter):
self._registry_composites[composite] = converter
def get_converter_for_composite(self, composite):
return self._registry_composites.get(composite)
registry = None

View File

@ -1,13 +1,15 @@
from py.test import raises
from sqlalchemy import Column, Table, types
from sqlalchemy.orm import composite
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy_utils.types.choice import ChoiceType
from sqlalchemy_utils import ChoiceType, ScalarListType
from sqlalchemy.dialects import postgresql
import graphene
from graphene.relay import Node
from graphene.types.json import JSONString
from ..converter import (convert_sqlalchemy_column,
convert_sqlalchemy_composite,
convert_sqlalchemy_relationship)
from ..fields import SQLAlchemyConnectionField
from ..types import SQLAlchemyObjectType
@ -25,6 +27,19 @@ def assert_column_conversion(sqlalchemy_type, graphene_field, **kwargs):
return field
def assert_composite_conversion(composite_class, composite_columns, graphene_field,
registry, **kwargs):
composite_column = composite(composite_class, *composite_columns,
doc='Custom Help Text', **kwargs)
graphene_type = convert_sqlalchemy_composite(composite_column, registry)
assert isinstance(graphene_type, graphene_field)
field = graphene_type.Field()
# SQLAlchemy currently does not persist the doc onto the column, even though
# the documentation says it does....
# assert field.description == 'Custom Help Text'
return field
def test_should_unknown_sqlalchemy_field_raise_exception():
with raises(Exception) as excinfo:
convert_sqlalchemy_column(None)
@ -108,6 +123,10 @@ def test_should_choice_convert_enum():
assert graphene_type._meta.enum.__members__['en'].value == 'English'
def test_should_scalar_list_convert_list():
assert_column_conversion(ScalarListType(), graphene.List)
def test_should_manytomany_convert_connectionorlist():
registry = Registry()
dynamic_field = convert_sqlalchemy_relationship(Reporter.pets.property, registry)
@ -206,3 +225,42 @@ def test_should_postgresql_jsonb_convert():
def test_should_postgresql_hstore_convert():
assert_column_conversion(postgresql.HSTORE(), JSONString)
def test_should_composite_convert():
class CompositeClass(object):
def __init__(self, col1, col2):
self.col1 = col1
self.col2 = col2
registry = Registry()
@convert_sqlalchemy_composite.register(CompositeClass, registry)
def convert_composite_class(composite, registry):
return graphene.String(description=composite.doc)
assert_composite_conversion(CompositeClass,
(Column(types.Unicode(50)),
Column(types.Unicode(50))),
graphene.String,
registry)
def test_should_unknown_sqlalchemy_composite_raise_exception():
registry = Registry()
with raises(Exception) as excinfo:
class CompositeClass(object):
def __init__(self, col1, col2):
self.col1 = col1
self.col2 = col2
assert_composite_conversion(CompositeClass,
(Column(types.Unicode(50)),
Column(types.Unicode(50))),
graphene.String,
registry)
assert 'Don\'t know how to convert the composite field' in str(excinfo.value)

View File

@ -6,6 +6,7 @@ from sqlalchemy.orm.exc import NoResultFound
from graphene import ObjectType, Field
from graphene.relay import is_node
from .converter import (convert_sqlalchemy_column,
convert_sqlalchemy_composite,
convert_sqlalchemy_relationship)
from .utils import is_mapped
@ -35,6 +36,17 @@ def construct_fields(options):
converted_column = convert_sqlalchemy_column(column, options.registry)
fields[name] = converted_column
for name, composite in inspected_model.composites.items():
is_not_in_only = only_fields and name not in only_fields
is_already_created = name in options.fields
is_excluded = name in exclude_fields or is_already_created
if is_not_in_only or is_excluded:
# We skip this field if we specify only_fields and is not
# in there. Or when we excldue this field in exclude_fields
continue
converted_composite = convert_sqlalchemy_composite(composite, options.registry)
fields[name] = converted_composite
# Get all the columns for the relationships on the model
for relationship in inspected_model.relationships:
is_not_in_only = only_fields and relationship.key not in only_fields