Skip to content

Commit

Permalink
association_proxy support
Browse files Browse the repository at this point in the history
  • Loading branch information
dpep committed Feb 12, 2020
1 parent 3c3442e commit 3b43bb7
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 5 deletions.
62 changes: 60 additions & 2 deletions graphene_sqlalchemy/converter.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
from enum import EnumMeta

from singledispatch import singledispatch
from sqlalchemy import types
from sqlalchemy import inspect, types
from sqlalchemy.dialects import postgresql
from sqlalchemy.orm import interfaces, strategies
from sqlalchemy.ext.associationproxy import AssociationProxy
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import (ColumnProperty, CompositeProperty,
interfaces, RelationshipProperty,
strategies)
from sqlalchemy.orm.attributes import InstrumentedAttribute

from graphene import (ID, Boolean, Dynamic, Enum, Field, Float, Int, List,
String)
Expand Down Expand Up @@ -33,6 +38,59 @@ def is_column_nullable(column):
return bool(getattr(column, "nullable", True))


def convert_sqlalchemy_association_proxy(association_prop, registry, connection_field_factory, batching, resolver, **field_kwargs):
model = association_prop.target_class

attr = getattr(model, association_prop.value_attr)
if isinstance(attr, InstrumentedAttribute):
attr = inspect(attr).property

def dynamic_type():
if isinstance(attr, AssociationProxy):
return convert_sqlalchemy_association_proxy(
attr,
registry,
connection_field_factory,
batching,
resolver,
**field_kwargs
)
elif isinstance(attr, ColumnProperty):
return convert_sqlalchemy_column(
attr,
registry,
resolver,
**field_kwargs
)
elif isinstance(attr, CompositeProperty):
return convert_sqlalchemy_composite(
attr,
registry,
resolver
)
elif isinstance(attr, RelationshipProperty):
batching_ = field_kwargs.pop('batching', batching)
return convert_sqlalchemy_relationship(
attr,
registry,
connection_field_factory,
batching_,
association_prop.value_attr,
**field_kwargs
# resolve Dynamic type
).get_type()
elif isinstance(attr, hybrid_property):
return convert_sqlalchemy_hybrid_method(
attr,
resolver,
**field_kwargs
)

raise NotImplementedError(attr)

return Dynamic(dynamic_type)


def convert_sqlalchemy_relationship(relationship_prop, obj_type, connection_field_factory, batching,
orm_field_name, **field_kwargs):
"""
Expand Down
4 changes: 4 additions & 0 deletions graphene_sqlalchemy/tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from sqlalchemy import (Column, Date, Enum, ForeignKey, Integer, String, Table,
func, select)
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import column_property, composite, mapper, relationship
Expand Down Expand Up @@ -65,6 +66,8 @@ class Reporter(Base):
articles = relationship("Article", backref="reporter")
favorite_article = relationship("Article", uselist=False)

pet_names = association_proxy('pets', 'name')

@hybrid_property
def hybrid_prop(self):
return self.first_name
Expand All @@ -82,6 +85,7 @@ class Article(Base):
headline = Column(String(100))
pub_date = Column(Date())
reporter_id = Column(Integer(), ForeignKey("reporters.id"))
reporter_pets = association_proxy('reporter', 'pets')


class ReflectedEditor(type):
Expand Down
31 changes: 30 additions & 1 deletion graphene_sqlalchemy/tests/test_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from graphene.types.datetime import DateTime
from graphene.types.json import JSONString

from ..converter import (convert_sqlalchemy_column,
from ..converter import (convert_sqlalchemy_association_proxy,
convert_sqlalchemy_column,
convert_sqlalchemy_composite,
convert_sqlalchemy_relationship)
from ..fields import (UnsortedSQLAlchemyConnectionField,
Expand Down Expand Up @@ -285,6 +286,34 @@ class Meta:
assert graphene_type.type == A


def test_should_convert_association_proxy():
class P(SQLAlchemyObjectType):
class Meta:
model = Pet

dynamic_field = convert_sqlalchemy_association_proxy(
Reporter.pet_names,
P,
default_connection_field_factory,
True,
mock_resolver,
)
assert isinstance(dynamic_field, graphene.Dynamic)
graphene_type = dynamic_field.get_type()
assert isinstance(graphene_type, graphene.Field)
assert graphene_type.type == graphene.String

dynamic_field = convert_sqlalchemy_association_proxy(
Article.reporter_pets,
P,
default_connection_field_factory,
True,
mock_resolver,
)
assert isinstance(dynamic_field.get_type().type, graphene.List)
assert dynamic_field.get_type().type.of_type == P


def test_should_postgresql_uuid_convert():
assert get_field(postgresql.UUID()).type == graphene.String

Expand Down
16 changes: 14 additions & 2 deletions graphene_sqlalchemy/types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections import OrderedDict

import sqlalchemy
from sqlalchemy.ext.associationproxy import AssociationProxy
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import (ColumnProperty, CompositeProperty,
RelationshipProperty)
Expand All @@ -12,7 +13,9 @@
from graphene.types.utils import yank_fields_from_attrs
from graphene.utils.orderedtype import OrderedType

from .converter import (convert_sqlalchemy_column,
from .batching import get_batch_resolver
from .converter import (convert_sqlalchemy_association_proxy,
convert_sqlalchemy_column,
convert_sqlalchemy_composite,
convert_sqlalchemy_hybrid_method,
convert_sqlalchemy_relationship)
Expand Down Expand Up @@ -113,7 +116,7 @@ def construct_fields(
inspected_model.column_attrs.items() +
inspected_model.composites.items() +
[(name, item) for name, item in inspected_model.all_orm_descriptors.items()
if isinstance(item, hybrid_property)] +
if isinstance(item, (AssociationProxy, hybrid_property))] +
inspected_model.relationships.items()
)

Expand Down Expand Up @@ -172,6 +175,15 @@ def construct_fields(
field = convert_sqlalchemy_composite(attr, registry, resolver)
elif isinstance(attr, hybrid_property):
field = convert_sqlalchemy_hybrid_method(attr, resolver, **orm_field.kwargs)
elif isinstance(attr, AssociationProxy):
field = convert_sqlalchemy_association_proxy(
attr.for_class(model),
registry,
connection_field_factory,
batching,
resolver,
**orm_field.kwargs
)
else:
raise Exception('Property type is not supported') # Should never happen

Expand Down

0 comments on commit 3b43bb7

Please sign in to comment.