mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-27 03:53:56 +03:00
Merge pull request #265 from sjhewitt/next-sql-composite
next: handle sqlalchemy composite columns
This commit is contained in:
commit
3edb4baf9b
|
@ -37,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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
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 import ChoiceType, ScalarListType
|
from sqlalchemy_utils import ChoiceType, ScalarListType
|
||||||
from sqlalchemy.dialects import postgresql
|
from sqlalchemy.dialects import postgresql
|
||||||
|
@ -8,6 +9,7 @@ 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)
|
||||||
|
@ -210,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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user