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 from .fields import SQLAlchemyConnectionField
try: try:
from sqlalchemy_utils.types.choice import ChoiceType from sqlalchemy_utils import ChoiceType, ScalarListType
except ImportError: except ImportError:
class ChoiceType(object): class ChoiceType(object):
pass pass
class ScalarListType(object):
pass
def convert_sqlalchemy_relationship(relationship, registry): def convert_sqlalchemy_relationship(relationship, registry):
direction = relationship.direction direction = relationship.direction
@ -34,6 +37,33 @@ def convert_sqlalchemy_relationship(relationship, registry):
return Dynamic(dynamic_type) 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): def convert_sqlalchemy_column(column, registry=None):
return convert_sqlalchemy_type(getattr(column, 'type', None), column, registry) 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) 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) @convert_sqlalchemy_type.register(postgresql.ARRAY)
def convert_postgres_array_to_list(type, column, registry=None): def convert_postgres_array_to_list(type, column, registry=None):
graphene_type = convert_sqlalchemy_type(column.type.item_type, column) graphene_type = convert_sqlalchemy_type(column.type.item_type, column)

View File

@ -2,6 +2,7 @@ class Registry(object):
def __init__(self): def __init__(self):
self._registry = {} self._registry = {}
self._registry_models = {} self._registry_models = {}
self._registry_composites = {}
def register(self, cls): def register(self, cls):
from .types import SQLAlchemyObjectType from .types import SQLAlchemyObjectType
@ -16,6 +17,12 @@ class Registry(object):
def get_type_for_model(self, model): def get_type_for_model(self, model):
return self._registry.get(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 registry = None

View File

@ -1,13 +1,15 @@
from py.test import raises from py.test import raises
from sqlalchemy import Column, Table, types from sqlalchemy import Column, Table, types
from sqlalchemy.orm import composite
from sqlalchemy.ext.declarative import declarative_base 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 from sqlalchemy.dialects import postgresql
import graphene import graphene
from graphene.relay import Node from graphene.relay import Node
from graphene.types.json import JSONString from graphene.types.json import JSONString
from ..converter import (convert_sqlalchemy_column, from ..converter import (convert_sqlalchemy_column,
convert_sqlalchemy_composite,
convert_sqlalchemy_relationship) convert_sqlalchemy_relationship)
from ..fields import SQLAlchemyConnectionField from ..fields import SQLAlchemyConnectionField
from ..types import SQLAlchemyObjectType from ..types import SQLAlchemyObjectType
@ -25,6 +27,19 @@ def assert_column_conversion(sqlalchemy_type, graphene_field, **kwargs):
return field 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(): def test_should_unknown_sqlalchemy_field_raise_exception():
with raises(Exception) as excinfo: with raises(Exception) as excinfo:
convert_sqlalchemy_column(None) convert_sqlalchemy_column(None)
@ -108,6 +123,10 @@ def test_should_choice_convert_enum():
assert graphene_type._meta.enum.__members__['en'].value == 'English' 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(): def test_should_manytomany_convert_connectionorlist():
registry = Registry() registry = Registry()
dynamic_field = convert_sqlalchemy_relationship(Reporter.pets.property, 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(): def test_should_postgresql_hstore_convert():
assert_column_conversion(postgresql.HSTORE(), JSONString) 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 import ObjectType, Field
from graphene.relay import is_node from graphene.relay import is_node
from .converter import (convert_sqlalchemy_column, from .converter import (convert_sqlalchemy_column,
convert_sqlalchemy_composite,
convert_sqlalchemy_relationship) convert_sqlalchemy_relationship)
from .utils import is_mapped from .utils import is_mapped
@ -35,6 +36,17 @@ def construct_fields(options):
converted_column = convert_sqlalchemy_column(column, options.registry) converted_column = convert_sqlalchemy_column(column, options.registry)
fields[name] = converted_column 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 # Get all the columns for the relationships on the model
for relationship in inspected_model.relationships: for relationship in inspected_model.relationships:
is_not_in_only = only_fields and relationship.key not in only_fields is_not_in_only = only_fields and relationship.key not in only_fields