graphene-sqlalchemy icon indicating copy to clipboard operation
graphene-sqlalchemy copied to clipboard

object of type 'SQLAlchemyConnectionField' has no len()

Open docelic opened this issue 6 years ago • 2 comments

Hello,

I am implementing a connection field.

The excerpts from the code are:

# Classes:
class Common(graphene_sqlalchemy.SQLAlchemyObjectType):
  class Meta:
    abstract = True
  ...

class MyGraphQLModel(Common):
  class Meta:
    model = MyModel
    interfaces = (graphene.relay.Node, )

# Fields:
  mymodels = graphene.List(MyGraphQLModel)
  def mymodels(p,i):
    query = MyGraphQLModel.get_query(i)
    return query.all()

  connection_mymodels = graphene_sqlalchemy.SQLAlchemyConnectionField(MyGraphQLModel)

The first field (mymodels) which is an array without a connection works fine. The second field which is a connection gives error:

      "message": "object of type 'SQLAlchemyConnectionField' has no len()",

Any ideas? Thanks.

docelic avatar Jul 11 '19 07:07 docelic

I have determined that this issue is happening when the field is defined in the root of the query.

If it is defined on some other/deeper level, it works fine.

Ideas/suggestions welcome.

docelic avatar Oct 04 '19 17:10 docelic

The issue seems to be in fields.py in this code:

    @classmethod
    def resolve_connection(cls, connection_type, model, info, args, resolved):
        if resolved is None:
            resolved = cls.get_query(model, info, **args)
        if isinstance(resolved, Query):
            _len = resolved.count()
        else:
            _len = len(resolved)

When a field is not at the top level (and so the underlying property often exists on the object), then resolved above is true, and the first if (if isinstance(resolved, Query)) runs.

When a field is at the top level (and so when there is usually no property on the object), the else() executes.

However, resolved is not None as it should have been, and so the first if (if resolved is None) does not run when it should.

The solution can be to extend the if into if resolved is None or cls == SQLALchemyConnectionField. (If you use module graphql-sqlalchemy-filter, you can also add or cls == FilterableConnectionField to that list.)

You can make this modification by importing the necessary modules, copying the content of the original function, and then modifying it slightly to include the extended if:

from sqlalchemy.orm.query import Query
from graphene_sqlalchemy.fields import UnsortedSQLAlchemyConnectionField
#from graphene_sqlalchemy_filter import FilterableConnectionField
from graphql_relay.connection.arrayconnection import connection_from_list_slice
from graphene.relay.connection import PageInfo

@classmethod
def resolve_connection2(cls, connection_type, model, info, args, resolved):
    # These 2 lines are modified:
    t = type(resolved)
    #if resolved is None or t==FilterableConnectionField or t==SQLAlchemyConnectionField:
    if resolved is None or t==SQLAlchemyConnectionField:
        resolved = cls.get_query(model, info, **args)
    if isinstance(resolved, Query):
        _len = resolved.count()
    else:
        _len = len(resolved)
    connection = connection_from_list_slice(
        resolved,
        args,
        slice_start=0,
        list_length=_len,
        list_slice_length=_len,
        connection_type=connection_type,
        pageinfo_type=PageInfo,
        edge_type=connection_type.Edge,
    )
    connection.iterable = resolved
    connection.length = _len
    return connection

UnsortedSQLAlchemyConnectionField.resolve_connection = resolve_connection2

docelic avatar Oct 28 '19 09:10 docelic